{ "argumentSchema": { "$defs": { "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.", "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" }, "sentAt": { "default": 0, "type": "number" } }, "required": [ "body", "sentAt" ], "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" }, "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" ] }, "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/TopicComment" }, "type": "array" }, "createdAt": { "default": 0, "type": "number" }, "createdBy": { "$ref": "#/$defs/TopicAuthor" }, "links": { "asCell": [ "cell" ], "default": [], "items": { "$ref": "#/$defs/TopicLink" }, "type": "array" }, "mentionable": { "asCell": [ "cell" ], "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 two-string rows; `TopicMentionableRow` in `main.tsx`), and\nrewired onto a piece from before the index as a one-time link-bind.\nA plain list of the pieces themselves also satisfies the demand — a\nboard not yet rewired, or a composer without a board. Absent, the\neditor simply offers no completions.", "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." }, "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." } }, "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" }, "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" }, "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.", "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" }, "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" }, "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 append-only, point-in-time\n`addComment` records. Sign every authored-content mutation with `agentName`:\nFabric records the human principal behind the key; the name says which\nagent acted under it. Reference-only `mention` and `unmention` calls carry\nno content signature. The session-draft cells and `submit*` streams below\nbelong to the rendered page, not the headless contract — they read state\nonly 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: append-only point-in-time records, each\ncarrying its author snapshot and `sentAt`.", "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": "" } }, "editingBody": { "scope": "session", "type": "boolean" }, "editingTitle": { "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\nsnapshot and `addedAt`.", "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" }, "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." }, "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." }, "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", "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" } }