{ "argumentSchema": { "$defs": { "StoredTopicComment": { "properties": { "author": { "$ref": "#/$defs/TopicAuthor", "description": "Snapshot taken at write time (profile enrichment comes later; never gate\nauthorship on a profile wish — CT-1879). Comments carry no minted id:\narray elements have stable entity identity; future editing addresses\nelements by reference (`equals()`), not by a synthetic key.\n\nOptional because durable Topics may contain a comment without structured\nauthorship. Current `addComment` calls require `agentName` and always write\nthis snapshot." }, "authorName": { "description": "Unrestricted legacy storage; only nonblank strings supply a name.", "type": "unknown" }, "body": { "default": "", "type": "string" }, "editedAt": { "description": "When the body was last revised, absent on a comment never edited. The\noriginal `sentAt` and `author` are left alone: an edit changes what was\nsaid, not who said it or when the thread reached this point.", "type": "number" }, "removedAt": { "description": "Set when the comment was retracted, and the retraction is the whole of\nit — the record stays, carrying what it always said. A reader hides it;\n`commentCount` stops counting it; `lastActivityOf` keeps reading its\n`sentAt`, which is what stops a retraction moving a topic backwards in\nthe board's ordering.", "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "sentAt": { "default": 0, "type": "number" } }, "required": [ "body", "sentAt" ], "type": "object" }, "TopicAuthor": { "properties": { "avatar": { "type": "string" }, "kind": { "description": "Open for the same reason `TopicLinkKind` is: this is provided data, so a\nclosed set here could never gain `\"service\"` or whatever acts next.\nWell-known values are `\"person\"` and `\"agent\"`, the latter marking an\nagent acting with its human user's key. `\"legacy\"` records a display name\nwhose author kind is unknown.", "type": "string" }, "name": { "type": "string" } }, "required": [ "kind", "name" ], "type": "object" }, "TopicCrossrefRow": { "properties": { "mentionedBy": { "items": { "type": "unknown" }, "type": "array" }, "topic": { "description": "The topic this row is about. `unknown` because it is written as a\nreference and only ever compared — `equals` takes the raw link. Anything\nwider retrieves the piece instead of pointing at it: declared `object`,\nthis field reads back as the whole expanded topic, `$UI` tree included.", "type": "unknown" } }, "required": [ "topic", "mentionedBy" ], "type": "object" }, "TopicLink": { "properties": { "addedAt": { "type": "number" }, "addedBy": { "$ref": "#/$defs/TopicAuthor" }, "kind": { "default": "web", "type": "string" }, "label": { "default": "", "type": "string" }, "removedAt": { "description": "As on a comment, and with one consequence of its own: a retracted link\nstops resolving into `mentions`, so the reference it contributed goes\nwith it rather than outliving the link that made it.", "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "url": { "default": "", "type": "string" } }, "required": [ "kind", "url", "label" ], "type": "object" }, "TopicMentionRef": { "properties": { "destination": { "type": "unknown" }, "modifiedTitle": { "type": "boolean" } }, "required": [ "destination", "modifiedTitle" ], "type": "object" }, "TopicMentionRefMap": { "additionalProperties": { "$ref": "#/$defs/TopicMentionRef" }, "properties": {}, "type": "object" }, "TopicMentionable": { "properties": { "$NAME": { "default": "", "type": [ "string", "undefined" ] }, "shortName": { "type": "string" }, "title": { "default": "", "type": "string" } }, "required": [ "title", "$NAME" ], "type": "object" } }, "properties": { "boardCrossrefs": { "asCell": [ "readonly" ], "default": [], "description": "The board's mention pivot, one row per topic. A topic reads its own row\nout of it and nothing else; see `backlinksOf`. Absent, a topic simply shows\nno inbound references.\n\nReadable, not writable: the pivot is the board's derivation, and a topic\nhas no business writing into it. Declaring the narrower cell says so where\na reader can see it, rather than leaving it to convention.", "items": { "$ref": "#/$defs/TopicCrossrefRow" }, "type": "array" }, "body": { "asCell": [ "cell" ], "default": "", "description": "The topic's living document: durable conclusions get folded up into the\nbody; the comment thread below holds the deliberation.", "type": "string" }, "bodyUpdatedAt": { "asCell": [ "cell" ], "default": 0, "type": "number" }, "bodyUpdatedBy": { "$ref": "#/$defs/TopicAuthor", "asCell": [ "cell" ], "default": { "kind": "person", "name": "" } }, "comments": { "asCell": [ "cell" ], "default": [], "items": { "$ref": "#/$defs/StoredTopicComment" }, "type": "array" }, "createdAt": { "default": 0, "type": "number" }, "createdBy": { "$ref": "#/$defs/TopicAuthor" }, "createdByName": { "description": "Creator display name recorded without an author kind.", "type": "unknown" }, "links": { "asCell": [ "cell" ], "default": [], "items": { "$ref": "#/$defs/TopicLink" }, "type": "array" }, "mentionable": { "asCell": [ "readonly" ], "default": [], "description": "The board's mention universe — what the body editor autocompletes\nover. Wired at creation to the board's mention index (one derived\ndocument of copies; `MentionableRow` in\n`../collection-naming/mentionable.ts`), and rewired onto a piece from\nbefore the index as a one-time link-bind. A plain list of the pieces\nthemselves also satisfies the demand — a board not yet rewired, or a\ncomposer without a board. Absent, the editor simply offers no\ncompletions.\n\nDeclared readable, which is what settles the retained link's proof. A\nwritable handle adds a write-back leg to that proof, asking this\nthree-field projection to accept every field the board's row publishes,\nand the row carries a required `piece` this projection deliberately omits\n— so a topic wired to a board is refused even the source it is running.\nThe declaration is not a runtime gate: a write through the handle is\nrefused by nothing, and where the universe is a plain list of pieces the\neditor's name write-back reaches a member's own title through it.", "items": { "$ref": "#/$defs/TopicMentionable" }, "type": "array" }, "mentioned": { "asCell": [ "cell" ], "default": [], "description": "Pieces this topic references outside its prose — what `mention` records.\n\nIts own list rather than an entry in `references`, because that map belongs\nto the body editor: the editor mints its keys, rewrites its labels, and\ncollects entries whose token has left the document. A verb writing there\nwould be writing into somebody else's bookkeeping. Here there is nothing to\nkey by, because there is nothing to point back at from the prose — a\nreference outside a sentence is just a link in a list.", "items": { "type": "unknown" }, "type": "array" }, "references": { "$ref": "#/$defs/TopicMentionRefMap", "asCell": [ "cell" ], "default": {}, "description": "Where this topic's `[Label][key]` mentions point, keyed by the token that\nappears in the body. The editor owns the contents; this pattern owns the\ncell, which is what makes a mention durable and — because each entry holds\nthe destination as a REFERENCE — what makes the reference graph a question\nabout cell identity rather than about text.\n\nThe default has to match the one `TopicOutput` publishes. A map published\nunder a different default than its input carries cannot be materialized." }, "shortName": { "description": "The number the board calls this topic by, stored here and published as\nthe topic's `shortName`. The board's create allocates it over the board's\nnamespace and passes it in, in the transaction that creates the topic;\n`recordName` writes it onto a topic filed before the board numbered\nanything. A number is permanent and never reused, so this copy stays the\nnumber the board's namespace holds for the topic.\n\nAbsent on a topic nobody has numbered: one filed before the namespace\nexisted and not yet recorded, and one composed with no board at all. Such\na topic publishes no number and renders without failing.\n\nPlain rather than `Writable`, because every reader of it wants the string\n— the topic's own report of its number, a board's row demand, a mention\nuniverse row. The one writer is `recordName`, which takes the writable\nhandle in its own state schema, beside the write that needs it.", "type": "string" }, "title": { "asCell": [ "cell" ], "default": "", "type": "string" }, "titleUpdatedAt": { "asCell": [ "cell" ], "default": 0, "type": "number" }, "titleUpdatedBy": { "$ref": "#/$defs/TopicAuthor", "asCell": [ "cell" ], "default": { "kind": "person", "name": "" }, "description": "Attribution of the last rename, stamped by `setTitle` — the same pair\nthe body keeps, because a title is the other editable scalar." }, "topicStateVersion": { "asCell": [ "cell" ], "default": 0, "description": "Number of ordered state upgrades completed by this topic.", "type": "number" } }, "type": "object" }, "pattern": "topics/topic.tsx", "resultSchema": { "$defs": { "AddCommentEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "body": { "description": "The comment text, appended verbatim after trimming. Must be non-empty:\nan empty body rejects rather than recording a blank thread entry.", "type": "string" } }, "required": [ "body", "agentName" ], "type": "object" }, "AddLinkEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "kind": { "description": "What the link points at — a rendering hint, not a behavior switch.\nOptional because the handler defaults it to `\"web\"`, and a caller should\nnot be made to supply a field the verb never needed.", "type": "string" }, "label": { "description": "Display label. Optional, and a blank one falls back to the URL.", "type": "string" }, "url": { "description": "The link target. Must be http(s): anything else rejects, because a\nuser-supplied scheme on a shared surface is script execution in every\nviewer's session.", "type": "string" } }, "required": [ "url", "agentName" ], "type": "object" }, "EditCommentEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "body": { "description": "The complete replacement text, trimmed. Must be non-empty: emptying a\ncomment is a retraction, and `removeComment` is what says so.", "type": "string" }, "comment": { "description": "The comment to revise, named and checked as in `RemoveCommentEvent`,\nand additionally naming `body`, which this verb writes.", "properties": { "body": { "default": "", "type": "string" }, "editedAt": { "type": "number" }, "removedAt": { "type": "number" }, "sentAt": { "default": 0, "type": "number" } }, "required": [ "sentAt", "body" ], "type": "object" } }, "required": [ "comment", "body", "agentName" ], "type": "object" }, "MentionEvent": { "properties": { "topic": { "description": "The piece to reference — the piece itself, not an address. Identity here\nis the cell, so this is what a caller passes and what gets stored.\n\nDeclared through the one field every Topic has rather than `unknown`. The\ncell declaration marks this as a reference position, so the CLI resolves a\ncanonical `/of:...` value from an inline JSON event into the live piece\nlink. It also bounds the validation read: `topic.get()` is `undefined` for\na value that is not a reference and an object for any piece, and `mention`\nchecks it before storing anything.\n\n`title` is also the most this can safely name: this schema reaches every\ntopic in `mentionable`, so a property without a default would be demanded\nof topics written before it existed and refuse their update. `title`\ncarries one, and that default does double duty — it is also why the check\nadmits a piece that is not a topic at all, which reads back `{ title: \"\" }`\nrather than `undefined`. `deno task pattern-vintage` proves the update side\nby replaying a real board.\n\nNothing reads THROUGH it beyond that one field — the value is stored and\ncompared.", "properties": { "title": { "default": "", "type": "string" } }, "required": [ "title" ], "type": "object" } }, "required": [ "topic" ], "type": "object" }, "RecordNameEvent": { "properties": { "name": { "description": "The number to store, as the board's namespace holds it. Required, and\nchecked against the name grammar rather than stored as given: a topic has\nno way to derive its own number, so a caller that could omit this would be\nasking the topic to invent one.\n\nNo `agentName` accompanies it, unlike every authored-content verb here,\nand the absence is the honest one for the reason `MentionEvent` gives\nabout itself: the number is the board's allocation, so a content-level\nsignature would be accepted and then dropped. Fabric still records the\nprincipal that made the write.", "type": "string" } }, "required": [ "name" ], "type": "object" }, "RemoveCommentEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "comment": { "description": "The comment to retract — the stored element, not a copy of its fields.\nNames only what this verb reads and writes, for the reason\n`MentionEvent.topic` names only `title`: the declaration bounds the\nvalidation read, and a payload that is not a reference reads back\n`undefined` and is refused rather than silently matching nothing.\n`sentAt` carries a default, so it is what makes a real element read back\nas an object; the stamp fields are optional, which is what lets this\nschema reach comments written before they existed.", "properties": { "removedAt": { "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "sentAt": { "default": 0, "type": "number" } }, "required": [ "sentAt" ], "type": "object" } }, "required": [ "comment", "agentName" ], "type": "object" }, "RemoveLinkEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "link": { "description": "The stored link element. What a reader passes, from the row it renders.\nNamed as in `RemoveCommentEvent`, with `url` playing the defaulted-field\nrole `sentAt` plays there.", "properties": { "removedAt": { "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "url": { "default": "", "type": "string" } }, "required": [ "url" ], "type": "object" }, "url": { "description": "The link's URL, for a caller that cannot pass a reference. Retracts the\nmost recently added link still present with this URL, so retracting twice\nretracts two rather than re-stamping one.\n\nSequentially. Two concurrent retractions naming the same URL both resolve\nthe same newest-present element and merge into one stamped record, so\nthey retract one link between them rather than two. Naming the link by\nreference is what makes a caller's target unambiguous under concurrency;\nthis spelling trades that for being reachable at all.", "type": "string" } }, "required": [ "agentName" ], "type": "object" }, "SetBodyEvent": { "properties": { "agentName": { "description": "Explicit content-level signature for an agent using its human user's\nidentity key. Fabric authenticates the write with that key; this says\nwhich agent acted under it.\n\nRequired so every authored-content call carries structured attribution.\nAuthenticated execution provenance can supersede the field by relaxing it\nto optional without breaking existing callers.", "type": "string" }, "body": { "description": "The complete replacement body, persisted verbatim — no trimming, so\nwhitespace-sensitive Markdown survives. An empty body is legal: clearing\na living document is an edit, not an error.", "type": "string" } }, "required": [ "body", "agentName" ], "type": "object" }, "SetTitleEvent": { "properties": { "agentName": { "description": "The agent making this mutation, stored as structured attribution beside\nthe write time. Required so every successful rename is attributed.", "type": "string" }, "title": { "description": "The new title, trimmed before it is stored. Must be non-empty.", "type": "string" } }, "required": [ "title", "agentName" ], "type": "object" }, "TopicAuthor": { "properties": { "avatar": { "type": "string" }, "kind": { "description": "Open for the same reason `TopicLinkKind` is: this is provided data, so a\nclosed set here could never gain `\"service\"` or whatever acts next.\nWell-known values are `\"person\"` and `\"agent\"`, the latter marking an\nagent acting with its human user's key. `\"legacy\"` records a display name\nwhose author kind is unknown.", "type": "string" }, "name": { "type": "string" } }, "required": [ "kind", "name" ], "type": "object" }, "TopicComment": { "properties": { "author": { "$ref": "#/$defs/TopicAuthor", "description": "Snapshot taken at write time (profile enrichment comes later; never gate\nauthorship on a profile wish — CT-1879). Comments carry no minted id:\narray elements have stable entity identity; future editing addresses\nelements by reference (`equals()`), not by a synthetic key.\n\nOptional because durable Topics may contain a comment without structured\nauthorship. Current `addComment` calls require `agentName` and always write\nthis snapshot." }, "body": { "default": "", "type": "string" }, "editedAt": { "description": "When the body was last revised, absent on a comment never edited. The\noriginal `sentAt` and `author` are left alone: an edit changes what was\nsaid, not who said it or when the thread reached this point.", "type": "number" }, "removedAt": { "description": "Set when the comment was retracted, and the retraction is the whole of\nit — the record stays, carrying what it always said. A reader hides it;\n`commentCount` stops counting it; `lastActivityOf` keeps reading its\n`sentAt`, which is what stops a retraction moving a topic backwards in\nthe board's ordering.", "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "sentAt": { "default": 0, "type": "number" } }, "required": [ "body", "sentAt" ], "type": "object" }, "TopicLink": { "properties": { "addedAt": { "type": "number" }, "addedBy": { "$ref": "#/$defs/TopicAuthor" }, "kind": { "default": "web", "type": "string" }, "label": { "default": "", "type": "string" }, "removedAt": { "description": "As on a comment, and with one consequence of its own: a retracted link\nstops resolving into `mentions`, so the reference it contributed goes\nwith it rather than outliving the link that made it.", "type": "number" }, "removedBy": { "$ref": "#/$defs/TopicAuthor" }, "url": { "default": "", "type": "string" } }, "required": [ "kind", "url", "label" ], "type": "object" }, "TopicMentionRef": { "properties": { "destination": { "type": "unknown" }, "modifiedTitle": { "type": "boolean" } }, "required": [ "destination", "modifiedTitle" ], "type": "object" }, "TopicMentionRefMap": { "additionalProperties": { "$ref": "#/$defs/TopicMentionRef" }, "properties": {}, "type": "object" }, "TopicSummary": { "properties": { "commentCount": { "default": 0, "type": [ "number", "undefined" ] }, "createdAt": { "description": "When the topic was filed (epoch milliseconds), stamped at create.", "type": "number" }, "createdBy": { "anyOf": [ { "$ref": "#/$defs/TopicAuthor" }, { "type": "undefined" } ], "default": { "kind": "person", "name": "" } }, "lastActivityAt": { "default": 0, "type": [ "number", "undefined" ] }, "title": { "default": "", "description": "The topic's title. Defaulted rather than required, like every other\nfield a board card renders: the card list is a mapped sub-pattern, so\nthis type reaches a piece holding topics written before the field\nexisted as the argument schema its update is checked against, and a\nrequired property those topics lack refuses that update outright.\n`deno task pattern-vintage` is what catches it, by replaying a real\ndeployed board.", "type": "string" } }, "required": [ "title", "createdAt", "commentCount", "lastActivityAt" ], "type": "object" }, "UnmentionEvent": { "properties": { "topic": { "description": "Declared and checked exactly as `MentionEvent.topic` is, and for the same\nreason: a payload that is not a reference matches no stored entry, so\nwithout the check it would remove nothing and report success.", "properties": { "title": { "default": "", "type": "string" } }, "required": [ "title" ], "type": "object" } }, "required": [ "topic" ], "type": "object" } }, "description": "A #topic — one durable unit of shared attention: a title, a living body\ndocument, a flat chronological comment thread, typed links out to other\ncore objects (PRs, agent sessions, web pages), and the references it makes\nto sibling pieces.\n\nDurable conclusions get folded up into the body — revise it whole with\n`setBody`; the thread holds the deliberation as point-in-time `addComment`\nrecords. The thread only ever grows: `removeComment` and `removeLink` stamp\na record as retracted and leave it where it is, and `editComment` revises a\nbody in place, so what a reader shows is the array filtered rather than the\narray itself. Sign every authored-content mutation with `agentName`:\nFabric records the human principal behind the key; the name says which\nagent acted under it. `mention`, `unmention` and `recordName` carry no\ncontent signature: the first two record where a reference points, and the\nthird records a number the board allocated. The session-draft cells and\n`submit*` streams below belong to the rendered page, not the headless\ncontract — they read state only this session holds.", "properties": { "$NAME": { "default": "", "description": "The topic's display name. Like the other derived display fields, a cold\nretained topic may not have produced this path yet; its persisted title\nremains authoritative until it does.", "type": [ "string", "undefined" ] }, "$UI": { "$ref": "https://commonfabric.org/schemas/vnode.json" }, "addComment": { "$ref": "#/$defs/AddCommentEvent", "asCell": [ "stream" ], "description": "Append to the thread — a point-in-time record of progress or\ndeliberation; durable conclusions belong in the body. Returns the\ncomment as recorded, resolved author and `sentAt` included." }, "addLink": { "$ref": "#/$defs/AddLinkEvent", "asCell": [ "stream" ], "description": "Attach a typed outbound link — a PR, an agent session, a web page.\nReturns the link as recorded, resolved `addedBy` and `addedAt`\nincluded. Appends merge and nothing dedupes: a repeated URL is two\nentries." }, "body": { "default": "", "description": "The living document, verbatim Markdown. `setBody` replaces it whole.", "type": "string" }, "bodyDraft": { "type": "string" }, "bodyUpdatedAt": { "type": [ "number", "undefined" ] }, "bodyUpdatedBy": { "anyOf": [ { "type": "undefined" }, { "$ref": "#/$defs/TopicAuthor" } ] }, "cancelEditBody": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: close the body editor; the session drafts are the discard.", "tier": "wrapper" }, "cancelEditTitle": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: close the rename editor; the session draft is the discard.", "tier": "wrapper" }, "commentCount": { "default": 0, "type": [ "number", "undefined" ] }, "commentDraft": { "description": "Session-local composer/edit state. These controls belong to a direct Topic\ninstance, not the shared TopicPiece projection used by the tracker's list.", "type": "string" }, "comments": { "description": "The thread, in arrival order, each record carrying its author snapshot\nand `sentAt`.\n\nMEMBERSHIP is append-only; a record is not. Nothing is ever removed from\nthis array — `removeComment` stamps `removedAt` and leaves the record in\nplace, and `editComment` revises a body and stamps `editedAt` — so a\nreader that wants the live thread filters on `removedAt` rather than\ntaking the array as it stands. `commentCount` is that filtered count.", "items": { "$ref": "#/$defs/TopicComment" }, "type": "array" }, "createdAt": { "description": "When the topic was filed (epoch milliseconds), stamped at create.", "type": "number" }, "createdBy": { "anyOf": [ { "$ref": "#/$defs/TopicAuthor" }, { "type": "undefined" } ], "default": { "kind": "person", "name": "" } }, "editComment": { "$ref": "#/$defs/EditCommentEvent", "asCell": [ "stream" ], "description": "Revise a comment's body in place, naming it by reference. Stamps\n`editedAt` and leaves `author` and `sentAt` as they were. Outside the\nprojection for the reason `removeComment` states." }, "editingBody": { "description": "Whether the body editor is open.\n\nA bare session-scoped value rather than a cell-wrapped one, and that\nspelling costs a capability worth knowing about before reaching for it: a\nverb cannot take a whole Topic as captured state while any required\nproperty is published this way. The piece does not materialize through the\nverb's state schema, so the argument arrives undefined and the runtime\ndeclines to run the verb at all — \"stream action argument is undefined\n(potential schema mismatch)\", logged rather than thrown, with the write\nsilently absent.\n\nNothing needs that today. A board names a member by writing the piece into\nits namespace, and every path that does so builds the piece inside the\nverb (`addTopic`, the browser composer) or resolves it out of the board's\nown list (`backfillNames`); none of them takes a Topic as an argument, so\nnone reads one through a state schema. Cell-wrapping these two would buy\nthe capability at the cost of a `scope changed` break on every deployed\nTopic, so it waits for a verb that needs it.", "scope": "session", "type": "boolean" }, "editingTitle": { "description": "Whether the rename editor is open. Bare for the reason `editingBody`\nstates, and the constraint is per property: cell-wrapping one and not the\nother buys nothing.", "scope": "session", "type": "boolean" }, "lastActivityAt": { "default": 0, "type": [ "number", "undefined" ] }, "linkKindDraft": { "type": "string" }, "linkLabelDraft": { "type": "string" }, "linkUrlDraft": { "type": "string" }, "links": { "description": "Typed outbound links, in arrival order, each carrying its author snapshot\nand `addedAt`. Append-only in membership on the same terms as `comments`:\n`removeLink` stamps rather than removes, and a stamped link additionally\nstops resolving into `mentions`.", "items": { "$ref": "#/$defs/TopicLink" }, "type": "array" }, "mention": { "$ref": "#/$defs/MentionEvent", "asCell": [ "stream" ], "description": "Reference another piece from this topic — the payload is the piece\nitself, stored as a reference. Takes no `agentName` and returns no value;\nFabric retains the authenticated principal behind the edge." }, "mentioned": { "default": [], "description": "Pieces referenced outside the prose, recorded by `mention`.", "items": { "type": "unknown" }, "type": "array" }, "mentions": { "default": [], "description": "Every piece this topic's prose and links point at, as references.\n\nThe board's pivot is declared over this and nothing else, so what one topic\ncosts a full-board scan is exactly this list of identities. Declared\n`unknown[]` because these are references, and comparing them never reads\nwhat is behind them.\n\nThe cell annotation that makes them comparable belongs to the READER, not\nhere: the pivot declares its own view of this list as\n`TopicMentionSource.mentions: ComparableCell[]`, and that is what\nmakes its graph settle rather than depend on load order. A published\n`unknown[]` is what each reader annotates for itself.\n\nThe default stands alone: a declared default and `| undefined` collide,\nand the default is what a topic deployed before this path existed\nmaterializes against.", "items": { "type": "unknown" }, "type": "array" }, "recordName": { "$ref": "#/$defs/RecordNameEvent", "asCell": [ "stream" ], "description": "Store the number the board calls this topic by, which is the only way a\nnumber reaches a topic the board did not pass one to at create: a board\nwrites a member's result and never a member's argument, so the topic has\nto make the write itself.\n\nIdempotent, and permanent: a call naming the stored number writes nothing\nand reports `wrote: false`, and a call naming a different one is refused.\nThe board's `backfillNames` sends this verb one event per topic it holds\nthat reports no number, and an operator can send it directly.\n\nTHE NUMBER MUST BE THE ONE THE BOARD'S NAMESPACE HOLDS FOR THIS TOPIC, and\nthe verb cannot check that: a topic holds no namespace to look itself up\nin, and giving it one is the board-derived table this design exists to\nremove — `shortName` above says why. So the check is the caller's, and\n`cf cell get --cell names` is where the answer is. A number the\nboard did not allocate is stored as readily as one it did, and a number is\npermanent: the step then allocates a different one, writes it into the\nnamespace, and is refused here for as long as the disagreement stands, so\n`top/` and the topic's own badge name different numbers and nothing in\nthe board reconciles them. `skills/topics/references/namespace-backfill.md`\ncarries that repair, which is by hand.\n\nOutside the projection for the reason `removeComment` states: a board\nstores `TopicPiece`, so a required verb added there would refuse every\ntopic deployed before the verb existed." }, "referencedBy": { "default": [], "description": "The topics that mention this one, read out of the board's pivot.\n\nDeclared through `TopicSummary` rather than `TopicPiece`, and that is\nload-bearing rather than stingy: a topic whose backlinks were topics would\nbe a type that contains itself, and resolving one from a list would walk\nthe graph. The summary carries no reference of its own, so it terminates.\nA reader that wants more follows the link, which resolves whole.", "items": { "$ref": "#/$defs/TopicSummary" }, "type": "array" }, "references": { "$ref": "#/$defs/TopicMentionRefMap", "default": {}, "description": "Where this topic's `[Label][key]` mentions point. Durable content, like\n`links`. The body editor works on a session copy of this map, which a save\npublishes here whole, beside the prose whose tokens name its entries." }, "referencesDraft": { "$ref": "#/$defs/TopicMentionRefMap", "description": "The body draft's mention map, staged so Cancel can discard it.\n\n`cf-code-editor` writes an entry the moment a mention is inserted and\ndrops one the moment its token leaves the document, neither of them\nwaiting for Save. Pointed at the durable map while `$value` holds a\nsession draft, those writes would outlive a discarded edit: a canceled\ninsertion would leave an edge no token names, and a canceled deletion\nwould strip the destination from a token the durable body still carries.\nThe editor's own ordering assumes its two bindings share a lifetime, so\nthis gives them one." }, "removeComment": { "$ref": "#/$defs/RemoveCommentEvent", "asCell": [ "stream" ], "description": "Retract a comment, naming it by reference. The record is stamped rather\nthan deleted, so the thread keeps its evidence while a reader stops\nshowing it and `commentCount` stops counting it.\n\nHere rather than on `TopicPiece`, for the reason `setTitle` is: boards\nstore that projection, so it is a demand on every topic they already\nhold, and a required verb added to it would refuse every one deployed\nbefore the verb existed. A verb has no default to be rescued by, so there\nis no compatible way to demand a new one. Addressing the topic directly\nreaches all three of these." }, "removeLink": { "$ref": "#/$defs/RemoveLinkEvent", "asCell": [ "stream" ], "description": "Retract a link, by reference or by `url`. Stamped like a comment, and a\nretracted link stops resolving into `mentions`. The url spelling exists\nbecause a link record carries no fid for a CLI caller to name. Outside\nthe projection for the reason `removeComment` states." }, "saveBody": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: publish the session body and mention-map drafts whole,\nattributed to the viewer's Profile — the contract verb is `setBody`.", "tier": "wrapper" }, "saveTitle": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: save the session title draft under the viewer's Profile —\nthe contract verb is `setTitle`, and both land through the same core, so\na browser rename stamps the same attribution and moves the same activity\nclock.", "tier": "wrapper" }, "setBody": { "$ref": "#/$defs/SetBodyEvent", "asCell": [ "stream" ], "description": "Replace the living document whole — read it, revise it, write it back\ncomplete; the body is one value with whole-value conflict semantics.\nRequires `agentName` and returns the persisted body with its attribution." }, "setTitle": { "$ref": "#/$defs/SetTitleEvent", "asCell": [ "stream" ], "description": "Rename the topic. Requires `agentName` and returns the persisted title\nwith its attribution. This direct-address verb stays outside the board's\nnarrow `TopicPiece` demand." }, "shortName": { "description": "The number the board calls this topic by, out of the `shortName` INPUT\nthe topic stores. The display name stays the title, and this rides beside\nit: where a topic publishes one, its header and the board's card render it\nas a badge, and a mention universe carries it, which is what a mention\npill and a `#42` query read.\n\nAbsent for a topic whose input holds no number: the property is simply\nnot there. Every consumer treats that as no name — the badge renders\nnothing, and the topic's universe row carries the empty name, which\nmatches no `#42` query and gives a mention pill no number.\n\nOptional rather than defaulted, and the difference is the compatibility\nproof's: this is what a board's stored list is validated against, and a\ndefaulted property there moves the demand's defaults below an array\nconstraint the proof cannot show stable under default insertion. It is not\nwhat lets the projection apply over a board whose members predate the\nproperty, and nothing is; `TopicDemand.shortName` in `./main.tsx` states\nwhy.", "tags": [ "42" ], "type": "string" }, "startEditBody": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: open the body editor, seeding the session drafts from the\ndurable body and mention map.", "tier": "wrapper" }, "startEditTitle": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: open the rename editor, seeding the session draft from the\ndurable title.", "tier": "wrapper" }, "submitComment": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: append the session comment draft under the viewer's\nProfile. Reads session-local state, so it is a silent no-op headless —\nthe contract verb is `addComment`.", "tier": "wrapper" }, "submitLink": { "asCell": [ "stream", "opaque" ], "description": "UI wrapper: append the session link drafts under the viewer's Profile —\nthe contract verb is `addLink`.", "tier": "wrapper" }, "title": { "default": "", "description": "The topic's title. Defaulted rather than required, like every other\nfield a board card renders: the card list is a mapped sub-pattern, so\nthis type reaches a piece holding topics written before the field\nexisted as the argument schema its update is checked against, and a\nrequired property those topics lack refuses that update outright.\n`deno task pattern-vintage` is what catches it, by replaying a real\ndeployed board.", "type": "string" }, "titleDraft": { "type": "string" }, "titleUpdatedAt": { "type": [ "number", "undefined" ] }, "titleUpdatedBy": { "anyOf": [ { "type": "undefined" }, { "$ref": "#/$defs/TopicAuthor" } ], "description": "Attribution of the last rename; unset until the first `setTitle`.\nBeside `setTitle` rather than on the projection, for the same reason." }, "unmention": { "$ref": "#/$defs/UnmentionEvent", "asCell": [ "stream" ], "description": "Stop referencing a piece: removes every `mention`-made entry naming it.\nReferences made in the prose are retracted by editing the prose, not by\nthis. Takes no `agentName` and returns no value." } }, "required": [ "commentDraft", "bodyDraft", "editingBody", "titleDraft", "editingTitle", "linkUrlDraft", "linkLabelDraft", "linkKindDraft", "referencesDraft", "setTitle", "removeComment", "editComment", "removeLink", "recordName", "startEditTitle", "saveTitle", "cancelEditTitle", "submitComment", "startEditBody", "saveBody", "cancelEditBody", "submitLink", "$UI", "body", "comments", "links", "mentions", "references", "mentioned", "referencedBy", "addComment", "addLink", "setBody", "mention", "unmention", "$NAME", "title", "createdAt", "commentCount", "lastActivityAt" ], "tags": [ "topic" ], "type": "object" } }