Update
Update patches an existing object. Unlike Create, dispatch is not by objectType -- it's by parsing the target ID's prefix (kowloonId()) and looking up MODELS[parsed.type]. If the client sends objectType, it's cross-checked against the same MODELS map, but it isn't load-bearing for dispatch.
{ "type": "Update", "target": "post:64f0...@kwln.org", "object": { "title": "Edited title", "source": { "content": "new body" }, "to": "@public" }}activity.object here is a patch, not a full replacement -- only fields present are touched. This is why routes/outbox/post.js explicitly skips injecting default to/canReply/canReact into object for Update (and Delete) -- those handlers treat object as the source of truth for what's actually changing, not something to fill in defaults on.
Allowed fields per type
Section titled “Allowed fields per type”Anything not in the list below is silently stripped from the patch.
| Type | Allowed fields |
|---|---|
User |
profile, prefs, to, canReply, canReact, email, username |
Post |
title, summary, source, body, type, tags, to, canReply, canReact, image, attachments, href, target, location, event |
Reply |
source, body, tags -- note: no to/canReply/canReact, matching the invariant that a Reply's own addressing fields always stay blank (see Reply) |
Page |
title, summary, source, body, slug, tags, to, canReply, canReact, image, attachments, href, parentId, order |
Bookmark |
title, summary, source, body, type, tags, to, canReply, canReact, href, target, parentFolder, image |
Circle |
name, summary, icon, to, canReply, canReact |
Group |
name, summary, icon, image, to, canReply, canReact, rsvpPolicy, location |
React |
emoji, name |
Owner (current.actorId, or current.id for User targets) or a server admin (isServerAdmin). There is no group-admin exception here -- editing a Group's own fields is owner/admin-only, distinct from isGroupAdmin, which is used by Add/Remove.
Special cases
Section titled “Special cases”User.tois capped to@publicor@<own-domain>only. Any other value -- including a circle ID -- is rejected with"Update: User.to must be '@public' or '@<own-domain>'".- System Circles (
type: "System") rejectname/summary/iconpatches -- those are load-bearing identity fields, not user-editable. Membership (viaAdd/Remove) stays editable. - Circle
canReply/canReactalways mirrortoiftois in the patch; otherwise they're dropped entirely from the patch if sent withoutto. - Password change is a separate, owner-only path:
object.password = { current, new }, requiresbcrypt.compareagainst the stored hash,newmust be >=8 chars. Admins cannot change another user's password throughUpdate. save()vsfindOneAndUpdate: content-bearing types (Post/Reply/Page) whosesource.contentchanged go through.save()-- notfindOneAndUpdate-- specifically so the model's pre-save hook re-rendersbody/summary/textPreview/signature from the new source. Every other patch usesfindOneAndUpdate.- Raw HTML in
source.contentis stripped the same way asCreate. Post.targetchanges trigger a server-sidetargetActorrecompute (same non-client-trusted resolution asCreate).
Side effects
Section titled “Side effects”FeedItemscache synced forPost/Reply/Page/Group/Circle(Bookmarks excluded -- they're personal utility, not feed content). Theto/canReply/canReactvalues are coarsened intopublic/server/audiencetiers for the cache -- this coarsening is display-only; real enforcement always reads the raw value viaauthorizeInteraction(see Architecture).- User profile
name/iconchanges propagate to all denormalized actor-embed copies across the DB viarefreshActorCache(fire-and-forget). - The result object has
password/privateKey/publicKeyJwk/signaturestripped before returning, forUsertargets.
Response
Section titled “Response”{ activity, created: <updated doc, sanitized>, result: same, federation }.
Client mapping
Section titled “Client mapping”updatePost, updateReply, updateCircle, updateGroup, updateBookmark, updatePage, updateProfile all map correctly. updatePost sends patch.content wrapped as object.source = { content } -- not as a top-level content field -- and to/canReply/canReact are nested inside object, not at the activity's top level. This matches the handler's filterPatch/ALLOWED_FIELDS logic exactly.