gosalyn: Case 2 design ruling after r11, agents never write story (Q223, Q226, mos-1309), with Webby's round webby-0151 and Mos's mos-1320 folded in. Supporting prefix lists agents may write .md files under through library.doc.write and library.doc.delete, fenced by M12 supportingPath, plumbing commits pushed fast-forward only with a Mosaic-Agent trailer, a notice and a human revert authored by her editor on slot library-human. Chat is free text with optional citations, replyLimits keys requireCitation and citationsPerReply, and an unreadable business file resolves requireCitation to true (R2). A send over 64 KiB gets the one retry and then a fixed text (R1). The fetch falls back to slot library-gitea only while no chief grant record has ever existed, and Jason deletes the slot once status shows the chief's grant (R3). A missing editor or slot refuses before any git call (N1), runner and tools cites fixed at aca16404c (N2, N3), two record-only Gitea facts at C2-21 (N4, mos-1320 item 5), mos-1163 narrowed to gitea library projects (mos-1320 item 2), M3 at eight or ten (mos-1320 item 4). C2-05b is row 224 as built and C2-05c carries the supporting scope and blob (sage-2833). New rows C2-01d, C2-05c, C2-08b, C2-12b and C2-21

This commit is contained in:
2026-10-22 12:16:44 -05:00
parent f164dcef8d
commit b68d796631
+429 -152
View File
@@ -178,7 +178,8 @@ organization reader, not from the broker's copy loaded at start (`broker.mjs:419
at `:570`). A change applies to the next reply with no restart.
A failed read never skips the check. If the business file is missing, can't be read, isn't JSON or isn't
an object at check time, the guard checks the reply at the defaults and `library.status` shows
an object at check time, the guard checks the reply at the defaults, except `requireCitation`, which
resolves to `true` (webby-0151 R2, ruling after r11), and `library.status` shows
`reply-limits-unreadable`. The reply is neither refused for that alone nor let through unchecked.
Fallback. Each key resolves on its own. The guard reads a key only if `Object.hasOwn(replyLimits, key)`,
@@ -200,9 +201,198 @@ every value. `library.status` shows the residual, and the setup sheet says so in
What agents see. The contracts don't state numbers. A refusal reason names the key and its value in
force, so the runner's retry (C2-07) tells the agent the limit it broke.
Hold. Q29 asks Jason whether "Agents NEVER write prose for the user" means story text for her books, with
long conversational answers allowed. A yes reworks M5's reply format, and that waits for his answer. The
settings carry over either way. C2-06 isn't built until Mos relays Q29.
Hold, now lifted. Q29 is answered (Q223, Q226). The next ruling replaces this table's seven keys with
`requireCitation` and `citationsPerReply`, and C2-06 builds that.
## Ruling after r11: agents never write story (Q223, Q226)
Jason answered Q29, and Mos's reading of it, Q30, is confirmed (DECISIONS Q223 and Q226, mos-1305,
mos-1309). F40 to F43 come with it, and mos-1308 sets the tool set. This ruling follows my impact list,
gosalyn-0148, with Mos's eight points, Mos's rulings on my flags in mos-1320, and Webby's round,
webby-0151 R1 to R3 and N1 to N5. It replaces D3, D5, M5, most of 5.2, the Q222 hold and 7.3's
"after the slice". Sections 0, 1.1, 1.2, 2.1, 3, 4, 5, 7, 8, 9.2 and 10 say so.
Story and supporting. Her chapters and story files stay read-only to agents. A human marks folders as
supporting through a new `supporting` list in each universe entry, beside `playground` (1.1). An agent
may create, edit and delete `.md` files under a supporting prefix, and nothing else in her repo. A folder
she hasn't marked is story. A folder in both `playground` and `supporting` is writable. A playground
folder alone isn't (Mos point 4). The prefix rules are the playground's: a prefix inside a book folder is
book level, one outside every book folder is universe level, no prefix holds a book folder or another
supporting prefix, and one inside or holding another universe's book folder refuses the business file
with `library-supporting-other-universe`. M9 already keeps the list to hand edits, since it sits in
`projects`.
Chat. A reply is free text of any length, within the broker's message limits as they are today. The item
list goes, and with it the item kinds, `CREATIVE_KINDS`, the M5 caps, the three notices,
`cited-reply-required`, `reply-kind-not-allowed` and `correction-unbound`. A reply may carry citations
(5.2). Jason's "NEVER" now reads as story in her files, and M12 enforces it in code.
Citations are tunable (F43). `replyLimits` keeps the Q222 mechanism: the object, `library.limits.set`, M11
and the read at check time. The seven Q222 keys go, since their subject is gone. The keys now are:
| Key | Default | Valid value |
|---|---|---|
| `requireCitation` | `false` | `true` or `false` |
| `citationsPerReply` | 20 | an integer from 1 to 1,000 |
The fallback rule holds. A key is read only if `Object.hasOwn` finds it, `requireCitation` only when
`typeof` gives `boolean`, and `citationsPerReply` only when `Number.isInteger` is true and the value is in
range. Anything else resolves to the default, and `library.status` names the key. So a bad
`requireCitation` hand edit reads as off, which is Jason's default and not a weaker guard than he chose.
The verb refuses it with `invalid-request`. One case differs from the defaults. When the business file
can't be read at check time (the Q222 rule), `requireCitation` resolves to `true`, so a setting she turned
on doesn't fail open, and `citationsPerReply` takes its default. `library.status` shows
`reply-limits-unreadable`, and the runner's fixed texts still pass (webby-0151 R2). When
`requireCitation` is true, a reply with no citation refuses `citation-missing`. When a reply carries
citations, the four checks in 5.2 run whatever the setting.
Links. Wiki links already in her files stay. The hub never rewrites a file it wasn't asked to write, and
agents can't write a chapter at all, so they add no link inside one. Their cross-links go in supporting
docs, and Obsidian's graph shows those. The `[CH]` alternative, items 42 to 46 of my impact list, is
dropped. A Console brain map is a later option and has no row.
The write path. Two agent verbs, each with a typed tool:
- `library.doc.write {universe, path, base, content}`, tool `library_doc_write`. `base` is the blob id the
agent read, from `library_get`, or `null` to create.
- `library.doc.delete {universe, path, base}`, tool `library_doc_delete`.
Only a session of a `CREATIVE_DEFINITIONS` instance holding the new action `library.write` and a live
Gitea grant on the library project may call them (`library-write-refused`, `library-write-no-grant`).
The tester gets `tester-refused`. M12 checks the path before anything else. `content` is UTF-8 with no
NUL, at most 1 MiB, the reader's R1 limit. An agent's call passes the runner's tool socket and the bus
socket, and both cap a request at 64 KiB today (`runner.mjs:294`, `server.mjs:62` at aca16404c). So an
agent write over that refuses `request-too-large` before the broker sees it, and the 1 MiB limit binds
only her saves. `library.file.save` joins the `project.file.save` exception at `client.mjs:19` and
`server.mjs:9` and `:62`, with the same `PROJECT_SAVE_REQUEST_MAX`.
Each write is a plumbing commit on the bare mirror, with no working tree. Under the library lock the bus
fetches, reads the path at the remote tip, and compares it with `base`:
1. Equal, or both absent for a create: it writes the blob, builds the tree from the tip's tree with
`GIT_INDEX_FILE` pointing at a temporary index outside the mirror, and runs `commit-tree` with the tip
as the one parent. The author and committer are the seat's `identity`. The message is a fixed line,
`Mosaic: write <path>` or `Mosaic: delete <path>`, and a trailer `Mosaic-Agent: <business>/<instance>`.
The agent supplies no message text.
2. Not equal: it refuses `stale-base` and writes nothing.
3. It pushes `<commit>:refs/heads/<branch>` with no `+`, so the push is fast-forward only (D6). A
rejection for a moved tip fetches once more and goes back to step 1 one time. A blob still equal to
`base` re-applies on the new tip. Otherwise the write refuses `stale-base`.
4. Any other push failure refuses `library-push-failed`, and the commit is left unreferenced. Nothing is
kept to push later, so there is no `unpushed` state and C2-17 drops.
5. After the push, the bus moves `refs/remotes/library/<branch>` and the served ref to the new commit,
which is a fast-forward of the served commit, rebuilds the index, records `lastPush` in the sync state
and stores a notice.
The notice is a message from the writing instance to `human` in that instance's thread. The broker
builds its body, `{"notice": "library-write", "op", "path", "commit"}`, and no agent text reaches it.
The Console shows it as a fixed line with a Revert button (7.4).
`library.revert {commit}` is the human's. It refuses `library-revert-not-agent` unless `commit` carries
this business's `Mosaic-Agent` trailer and is reachable from the served commit. For the one path that
commit touched, it puts back the parent's blob, or deletes the path if the parent had none, as a new
commit by the same steps, with `base` the commit's own blob. A later change to that path makes it refuse
`stale-base`. A revert is her act, so its author and committer are her `editor`, and it pushes with slot
`library-human`, as her saves do. It still works after the agent's grant is revoked or the seat is fired.
A cite to a file whose last commit at the cited commit has a `Mosaic-Agent` trailer is labelled
agent-written (Mos point 3). The broker reads the trailer with `git log -1 --format=%(trailers:key=Mosaic-Agent,valueonly) <commit> -- <path>`,
and the label rides in the stored reply's check result, so no agent can set it.
M12 is the fence. A new pure function in `packages/library`, `supportingPath(mapping, universe, path,
tree)`, and the broker calls it on every agent write and delete. It refuses `library-write-story-path`
for a path that isn't a `.md` file under a supporting prefix of `universe`. That covers an unmapped path,
a `.canvas` or `.base` file, an attachment, any story or canon path, a playground path that isn't also
supporting, and a path under another universe's prefix. It also refuses an absolute path, a `..`, `.` or
empty segment, a backslash, a control character, a path whose any prefix in the tip's tree is a symlink
or a gitlink, a path that differs only in case from a tree entry it doesn't equal, and a path over 1,024
bytes. A delete also needs the path to exist at the tip as a `100644` blob.
Who writes, with what (F40). The shared bot account goes. Each writing seat writes with its own Gitea
account and token from the per-seat grant path (Q190 O1, #436b, row 236). The token stays in the bus's
credential store, out of every sandbox (#436b section 3c), and no agent session holds it. The askpass
helper from 7.2 reads the seat's store file for that one push, with the seat's Gitea user as the only
username it answers.
The chief of staff of her business is enrolled at install and grants the other seats through the
existing step: the chief raises a `credential.mint` decision with a gitea grant for `books`, and the
executor runs on its existing approve (436b section 4, step 0). Whether each such grant also needs
Jason's Approve is Q31, asked of him. This design adds no step for it, and a yes adds none here.
The executor today forks each project and refuses any other reach (436b steps 4 and 5). A library
project can't work that way, since the writes must land in her repo. C2-21 changes the two steps for a
project of `kind: "library"` only:
- The raise check. `#giteaRole` (`broker.mjs:2122-2135` at aca16404c) refuses `gitea-not-declared` for
any creative role, whatever its definition declares (mos-1163). F40 narrows that ruling. A creative
role passes only when every project the raise names is `kind: "library"`. A raise for a creative role
that names any other project still refuses `gitea-not-declared`, so a creative seat never gets a fork.
The other checks stay: the definition's gitea credential, `identity.gitea` and a non-empty `publish`,
which C2-08b supplies. Mos ruled the narrowing in mos-1320 item 2: mos-1163 still covers every gitea
project except `kind: "library"`, and covers host grants whole. The host grant writer, the executor's
step 1 recheck and `host.run` keep refusing a creative role with `host-creative-role`. The comment at
`broker.mjs:2120` cites mos-1320.
- Step 4 adds the seat's user as a collaborator on the project's own repository with the admin token,
`PUT /repos/{owner}/{repo}/collaborators/{user}`, in place of a fork. The permission is `write` for a
`CREATIVE_DEFINITIONS` instance and `read` for any other, the chief included. The definition decides
it, and the raise can't name one.
- Step 5 accepts that repository in the reach list when its `permissions` show exactly that level, with
`admin` false. Anything wider still refuses `gitea-reach-wider`.
- The rendered block names the repository and the level instead of a fork URL, and adds the line "The
token can write every path of this repository. Only the bus's path check limits it to supporting
folders."
- A revoke deletes the token as today, which ends the write. The collaborator entry stays, as forks do.
A fire's delete sets `prohibit_login` as today.
Two facts aren't proven yet, and C2-21's first test proves them on a scratch Gitea 1.27.1 before any code,
as F1 to F11 were: that the admin token's scopes (436b 3b) can add a collaborator to a repository the
admin account doesn't own, and that `/user/repos` with the seat token shows that repository with the
granted `permissions`. If either fails, C2-21 stops and goes to Mos.
The mirror's fetch uses the chief's token, whose grant is `read`, and the chief's user in the askpass
helper. That replaces the bot slot `library-gitea` from C2-03a. C2-03b makes the change. The fetch falls
back to slot `library-gitea` only while no gitea grant record for the chief on the library project has
ever existed, in any state, so an install that runs before C2-21 still syncs. Once one exists, a revoked
or `held` grant, or a fired chief, refuses the fetch with `library-fetch-no-grant`, and `library.status`
shows it. A write, a revert and a save never fall back to the slot. When `library.status` shows the fetch
on the chief's grant, Jason deletes slot `library-gitea` (10 step 4b, webby-0151 R3).
Her edits (F41). She edits in the Console. Each save is one commit by the same steps, through the human
verb `library.file.save {path, base, content}`. The author is a new `editor` object in the library entry,
`{name, email}`, which the setup sheet asks her for, and the push uses her own Gitea token from a new slot
`library-human` (mos-1320 item 3). A save or a revert with no `editor` refuses `library-mapping-invalid`.
One with slot `library-human` missing or failing the helper's file checks refuses `library-human-missing`
before any git call. Her saves reach any mapped `.md` file. M12's other path rules hold for them, and only
the supporting rule doesn't, since the files are hers. They still refuse
`stale-base` when the file moved, because an agent write or an Obsidian push can race her save. She sees
an agent's write at once in the Console, and Obsidian sees it on its next pull. Agents see her save at
once too, since the bus moves the served ref after its own push. Her Obsidian pushes reach them through
the 60-second sync. The editor is a Q195 add-on, so C2-11a and C2-11b are slice rows that don't gate
10-27.
Rows (F42). C2-02, C2-03b and C2-11b join the slice, and C2-11a with them, since C2-11b needs it. Sage
queues them. C2-07 folds into C2-06. C2-17 drops. Five new rows carry what landed or built rows can't:
C2-01d, the `supporting` list and scope, C2-05c, the `supporting` scope and `blob` in the lookups,
C2-08b, the role changes, C2-12b, the setup sheet rewrite, and C2-21, the grant change. C2-05b is row
224, built to its earlier brief, and it lands as built (sage-2833). Its tools already match M3 below.
Section 8 lists them. Mos asked for a PRD row too: REQ-LIB-6 rewritten to Q223 and Q226, before C2-01d
and C2-02 (mos-1320 item 8).
M3 (mos-1308, Mos point 8). A creative session's typed tools are exactly the five lookups,
`send_message`, `library_doc_write` and `library_doc_delete`. `companion` and `research` also get
`web_search` and `web_fetch` through the existing web grant. So the per-role snapshot holds eight names,
or ten for those two. mos-1308 said six or eight, before Q226 added the two write tools, and Mos
accepted eight and ten in mos-1320 item 4. None of `read_spec`, `list_agents`, `inbox`, `trail`,
`write_record`, `raise_decision` or a tracker read reaches a creative session.
`packages/harness/src/tools.mjs` at aca16404c adds all but `raise_decision` to every session, and
already skips `raise_decision` for a creative definition (`:241`, webby-0151 N3).
Residual. A supporting doc can hold any text, a drafted scene included, and code can't tell a scene from
a note. Every agent write is visible as a notice and a trailer, and she can revert it. Nothing an agent
writes lands in a story path (M12). At Gitea the seat token can write any path of her repo, and the bus's
check is the only path limit. The token never leaves the bus, so the limit holds while the bus code does.
C2-21's scratch Gitea run also records two facts that don't gate it: whether a protected file pattern
refuses a seat user's push to a story path while her push still passes (mos-1320 item 5), and whether
branch protection refuses a seat user's force-push while her normal push passes (webby-0151 N4). If the
first works, Mos files a task for it as a second fence after the slice. The second would back D6 at the
remote and wouldn't narrow the paths.
## Changes from r10
@@ -532,10 +722,12 @@ the line says so.
Melanie's writing already lives in a git repository, her books repo on git.mosaicstack.dev, and her
Obsidian syncs it with obsidian-git. The hub reads that repo. The bus keeps a bare mirror of it, fetched
with a read-only token, and the business file maps her folders onto books, types, canon and playground.
Agents can't write to it at all. They read it through typed lookup tools. Every message an agent sends
her must be a short list of items, each item must cite her repo or a page the agent fetched, and the
broker checks each citation before it stores the message. Each creative agent holds its own thread.
with the chief of staff's read token, and the business file maps her folders onto books, types, canon,
playground and supporting folders. Agents never write her story. They read it through typed lookup
tools, and they may create, edit and delete `.md` files only in folders she marked as supporting. Each
such write is a commit she sees as a notice and can revert. An agent's reply is free text. Any citation
it carries must be real, and the broker checks each one before it stores the message. Each creative
agent holds its own thread.
Bean and Chet, her two agents from Hermes, keep their names and personalities as Mosaic agents, and the
specialist roles sit beside them. She talks to them in the Console and sees only stored messages that
passed the guard. Hermes keeps running until she has moved over.
@@ -545,14 +737,16 @@ Decisions (D-codes are this document's own):
- D1. Her repo stays as it is. The design adapts to her layout through a mapping in the business file,
which agents can't write.
- D2. Scope comes from the path. No file content can change a file's scope.
- D3. Agents get no write path into her repo. That covers canon, playground and everything else.
- D3. Agents never write story. An agent writes only `.md` files under a supporting prefix, and M12
enforces that in code (ruling after r11, Q223, Q226). Canon, playground that isn't also supporting, and
everything else stay read-only to agents.
- D4. One chat is a project. Each creative agent holds its own thread in it. The `threads` table and its
triggers don't change (section 6).
- D5. In the slice the bus reads a bare mirror and never writes. Bus writes come later, through the
record repo commit path (`packages/project/src/index.mjs:195-234`).
- D5. The bus writes to her repo only as plumbing commits on the bare mirror, pushed fast-forward only:
an agent's supporting doc write, the human's revert and her own Console save (ruling after r11).
- D6. The Gitea remote is where the bus and Obsidian meet. The bus only fast-forwards and never merges.
- D7. Anything fetched from the remote is untrusted input. The bus reads it as blobs, never checks it out
in the slice, and never runs anything from it.
and never runs anything from it.
- D8. Every channel to Melanie shows only messages the broker stored after the guard. A channel that
shows a model's raw output is refused on a library business.
@@ -577,8 +771,10 @@ The mapping is a new form of business file project entry. Business files live in
"vaultPrefix": "<repo folder name under ~/src>",
"universes": {
"<u>": {"name": "<display name>", "books": ["<book folder>", "<book folder>"],
"playground": ["<book folder>/<playground folder>/"]}
"playground": ["<book folder>/<playground folder>/"],
"supporting": ["<book folder>/<notes folder>/"]}
},
"editor": {"name": "<her name>", "email": "<her email>"},
"types": {"<characters folder>": "character", "<locations folder>": "place",
"<research folder>": "research", "<tracker folder>": "tracker",
"<chapter summaries folder>": "summary"},
@@ -599,6 +795,12 @@ The mapping is a new form of business file project entry. Business files live in
cite check (5.2) has something to compare (ruling after r11, three levels). Her repo has no playground
today as far as I know. The setup sheet suggests one folder per book, for example
`<book>/playground/`. Until she makes one, everything mapped is canon.
- `supporting` is a second list of path prefixes in each universe entry (ruling after r11, Q223). An
agent may write `.md` files under a supporting prefix and nowhere else (M12). The prefix rules are the
playground's, and the refusal for another universe is `library-supporting-other-universe`. A prefix
may sit in both lists, and that folder is writable. A playground prefix alone isn't. Her repo has no
supporting folder until she names one, and until then agents write nothing.
- `editor` is the name and email her Console saves commit under (F41). The setup sheet asks her for it.
- `types` maps a type folder name to a type. A file's front matter `type`, if present, is shown next to
it but doesn't change scope.
@@ -618,14 +820,16 @@ Each path in her repo, relative to the repo root, gets one scope:
| Scope | Rule |
|---|---|
| `playground` | A `.md` file that starts with a playground prefix of any universe entry, drafts included |
| `supporting` | A `.md` file that starts with a supporting prefix of any universe entry, drafts included. This comes first, so a folder in both lists is supporting. Agents may write it (M12) |
| `playground` | Otherwise, a `.md` file that starts with a playground prefix of any universe entry, drafts included |
| `canon` | Otherwise, a `.md` file inside a configured book folder |
| `unmapped` | Everything else: top-level notes, `.base`, `.canvas` (under a playground prefix too), attachments, anything outside a book folder and outside every playground prefix |
| `unmapped` | Everything else: top-level notes, `.base`, `.canvas` (under a playground or supporting prefix too), attachments, anything outside a book folder and outside every playground and supporting prefix |
Lookups default to canon and return playground only on request. Unmapped files are never returned or
citable. The universe of a canon file is the universe whose `books` list holds its book folder. The
universe of a playground file is the entry whose prefix holds it, and its book is the book folder that
holds the prefix, or `null` at universe level and in a draft (ruling after r11, three levels).
Lookups default to canon and return playground or supporting files only on request. Unmapped files are
never returned or citable. The universe of a canon file is the universe whose `books` list holds its book
folder. The universe of a playground or supporting file is the entry whose prefix holds it, and its book
is the book folder that holds the prefix, or `null` at universe level and in a draft (ruling after r11,
three levels).
### 1.3 Reading and reporting
@@ -662,7 +866,7 @@ setup sheet says so (C2-12).
Why reporting is enough for separation. The guarantee Jason asked for is that agents keep playground and
canon apart. The code that does that is the scope rule, the lookups' default and the cite check in 5.2,
which refuses a playground cite on a `fact`, `finding` or `correction`. None of those needs her files to
which refuses a playground or supporting cite marked as canon. None of those needs her files to
pass a rule. Refusing her latest commit would only stop the agents seeing her newest work.
### 1.4 Hybrid crossover (after the slice)
@@ -681,9 +885,8 @@ C2-15 builds this.
### 1.5 Promotion (after the slice)
Promotion moves files from a playground prefix into a book folder. Only Melanie does it, in Obsidian
today. A Console verb `library.promote {from, to}` comes with the write rows (C2-19). It is human only,
moves every file in one commit through a multi-path form of `commitFile` (today that commits one path,
`index.mjs:218-219`), and pushes.
today. A Console verb `library.promote {from, to}` comes later (C2-19). It is human only and moves every
file in one plumbing commit, by the steps of the ruling after r11 with several paths, and pushes.
## 2. What exists and how Case 2 uses it
@@ -708,12 +911,11 @@ moves every file in one commit through a multi-path form of `commitFile` (today
sets `protocol.allow=never` (`git.mjs:8`), and `inspectGit` refuses any `remote.*` key (`git.mjs:44`).
The mirror keeps both rules. The URL goes on the command line from the business file, and fetch uses
its own command builder (7.2).
- Bus writes, later (C2-02): `commitFile` (`index.mjs:195-234`) handles the lock, `inspectGit`,
`requireClean`, the layout check, publish, commit and rollback. `writeHumanFile` (`index.mjs:248-261`)
is the model for `writeLibraryFile`. C2-02 refactors `commitFile` to take the check function (it calls
`checkProject` at `:209`) and a list of paths, and adds r1's S0 rule for files in the working tree.
- Bus writes (C2-02) don't use `commitFile` (`index.mjs:195-234`), which commits in a working tree. The
mirror has none (D7). C2-02 writes plumbing commits on the bare mirror by the steps of the ruling after
r11, under the library lock and after M12. `commitFile` and `writeHumanFile` stay as they are.
### 2.2 ProseMirror editor (`packages/webui/editor`), after the slice
### 2.2 ProseMirror editor (`packages/webui/editor`)
- It edits Markdown (`model.mjs:17-18`). Blocks it can't draw, front matter included, are kept byte for
byte (`model.mjs:3-11`, `:220-231`).
@@ -747,8 +949,9 @@ moves every file in one commit through a multi-path form of `commitFile` (today
- `typedTools` (`tools.mjs:91-261`) builds the typed tools per session from held actions. `write_record`
exists only for `record.write` holders (`:211-220`), and `send_message` for `message.send` holders
(`:95-102`). `raise_decision` goes to every agent (`:221`), with free-text `question` and option `text`.
- Case 2 adds the lookup tools (section 3) when the business has a library project, and drops
`raise_decision` for creative definitions (5.1 M6).
- Case 2 adds the lookup tools (section 3) when the business has a library project, adds the two write
tools for a creative session holding `library.write`, and drops `raise_decision` and the other
non-creative tools for creative definitions (5.1 M3, M6).
### 2.5 Threads, messages, conversations and Discord
@@ -761,9 +964,10 @@ moves every file in one commit through a multi-path form of `commitFile` (today
(`packages/bus/schema.sql:296-299`) and deliver only to the thread's holder (`:300-306`). `threads`
can't be updated (`:310`). Thread names are unique per project (`:249`).
- The runner sends the model's final text as a `message.send` RESULT to the sender
(`packages/harness/src/runner.mjs:446-450`). It sends `(the session gave no answer)` for an empty turn
and `The turn failed: <reason>` for a failed one (`:449`). A refusal is logged and the message is still
marked read (`:451-456`), so the answer is lost.
(`packages/harness/src/runner.mjs:571-574` at aca16404c). It sends `(the session gave no answer)` for
an empty turn and `The turn failed: <reason>` for a failed one (`:572`). A refused send is logged and
`message.read` is skipped (`:577-583`), so the message stays unread and the answer never reaches her
(webby-0151 N2).
- Sessions are per thread: `threadStore(dataRoot, business, thread)` (`packages/seat/src/session.mjs:59-64`)
and the Claude session id file under it (`adapters/claude/adapter.sh:47-53`). One thread per agent
means one session per agent.
@@ -787,8 +991,8 @@ reads the mirror at its current served commit (7.2) and returns that commit.
| Tool | Arguments | Returns |
|---|---|---|
| `library_find` | `universe` (required), `query`, `type?`, `book?`, `scope?` (`canon` default, or `playground`) | up to 50 `{path, type, name, appears_in, scope, universe, book}` |
| `library_get` | `path` | `{path, commit, scope, frontmatter, lines: [[n, text]], links_out, links_in}` |
| `library_find` | `universe` (required), `query`, `type?`, `book?`, `scope?` (`canon` default, `playground` or `supporting`) | up to 50 `{path, type, name, appears_in, scope, universe, book}` |
| `library_get` | `path` | `{path, commit, blob, scope, frontmatter, lines: [[n, text]], links_out, links_in}` |
| `library_search` | `universe`, `text`, `book?`, `scope?` | up to 50 `{path, line, excerpt, scope}` |
| `library_manuscript` | `universe`, `book`, `scope?` (`canon` default), `chapter?`, `from?`, `to?` | `{path, commit, scope, lines}`, one chapter file, at most 400 lines per call |
| `library_universe` | `universe` | its books, type folders, and for a hybrid the sources, constraints and notes |
@@ -803,22 +1007,32 @@ reads the mirror at its current served commit (7.2) and returns that commit.
Her repo has 203 Markdown files today. I estimate a rebuild well under a second, and C2-05a's gate
measures it on a 3,000-file fixture.
Writes. `library_doc_write {universe, path, base, content}` and `library_doc_delete {universe, path,
base}` call the agent verbs of the ruling after r11. Only a creative session holding `library.write` gets
them (M3). `base` is the `blob` that `library_get` returned, or `null` to create. Each returns `{commit,
path}` or a refusal code, and a successful write also stores the notice.
## 4. Creative roles, Bean and Chet
Six new role definitions. Each is a version 2 role file plus a `.md` contract.
| Role | Purpose | `tools` | Reply item kinds |
|---|---|---|---|
| `companion` | Her general writing partner. Backs Bean and Chet | `read`, `web` | `idea`, `question`, `finding`, `fact` |
| `ideas` | Brainstorming, mostly in the playground | `read` | `idea`, `question` |
| `research` | Real-world facts from the web | `read`, `web` | `fact`, `question` |
| `proofreader` | Spelling, grammar, punctuation | `read` | `correction`, `question` |
| `fact-checker` | Text against canon and her research notes | `read` | `finding`, `question` |
| `continuity` | Consistency across books, timeline and her tracker | `read` | `finding`, `question` |
| Role | Purpose | `tools` |
|---|---|---|
| `companion` | Her general writing partner. Backs Bean and Chet | `read`, `web` |
| `ideas` | Brainstorming, mostly in the playground | `read` |
| `research` | Real-world facts from the web | `read`, `web` |
| `proofreader` | Spelling, grammar, punctuation | `read` |
| `fact-checker` | Text against canon and her research notes | `read` |
| `continuity` | Consistency across books, timeline and her tracker | `read` |
All six have `authority: {withinRole: ["message.send"], crossRole: []}`, `credentials: []`, and
`messaging: {sendTo: ["human"], receiveFrom: ["human"]}`, so they can't relay through each other.
`network` is `none`, except `api-only` for `companion` and `research`.
All six have `messaging: {sendTo: ["human"], receiveFrom: ["human"]}`, so they can't relay through each
other. `network` is `none`, except `api-only` for `companion` and `research`. C2-08 landed them with
`authority: {withinRole: ["message.send"], crossRole: []}` and `credentials: []`. C2-08b changes both,
which is a role change Mos authorized in mos-1309 item 5: `withinRole` becomes `["message.send",
"library.write"]`, and `credentials` becomes `[{"service": "gitea"}]`, the definition credential the
grant path needs (`business.mjs:132-167`). Each creative instance also gets the `identity` and `publish`
fields that path reads. `roles/chief-of-staff.json` gets the same credential and no `library.write`, so
the chief can hold the read grant the mirror fetches with.
Bean and Chet. Jason's ruling (Q180): "She will want to keep those agent names and personalities." They
are two business role instances, `bean` and `chet`, both with definition `companion`. The personas are
@@ -935,12 +1149,13 @@ wins on any conflict, and the broker checks every reply whatever the persona say
It is fenced the way skills are (`SKILL_END`, `bundle.mjs:107`, `:170`). The bundle refuses
`persona-invalid` for text that holds `</persona-file>` or has a line starting `# ` or `## `, so the text
can't close its fence or open a section of its own. C2-08's snapshot pins contract, persona, session, in
that order. The order is a hint to the model. The enforcement is the broker guard in 5.2, which doesn't
read the persona, so no persona text can lift a guard.
that order. The order is a hint to the model. The enforcement is in code, M12 on writes and the
citation guard in 5.2, and neither reads the persona, so no persona text can lift a guard.
What changes for her. In Hermes, Bean and Chet answer in free text and may write story text when she
asks. In Mosaic they answer in short cited items, and a request for prose gets a `question` or an `idea`
item, not a passage. That follows from Jason's "NEVER" and Q176. She should hear it before the move, not
What changes for her. In Hermes, Bean and Chet answer in free text and may write story text into her
files when she asks. In Mosaic they still answer in free text, of any length, and they never write into
her story files. Whatever they write lands in a supporting folder she marked, as a commit she sees and
can revert. That follows from Jason's "NEVER" as Q223 reads it. She should hear it before the move, not
discover it (10 step 7).
The five specialists stay available as further instances. Whether the slice starts with Bean and Chet
@@ -964,67 +1179,73 @@ New constants in `vocabulary.mjs`, each changed only by a reviewed commit:
as needed; unless user forbids per agent"). If Melanie wants it forbidden for an agent, the request
comes through Jason (mos-1038).
- `CREATIVE_DEFINITIONS`, the six above.
- `CREATIVE_KINDS`, mapping each definition to the item kinds in the table.
- `CREATIVE_TOOLS = ["read", "grep", "find", "ls", "web"]`. `validateRoleDocument` refuses a creative
definition whose `tools` include anything else, whose authority holds anything but `message.send`, that
has no `messaging` key, or whose `messaging` names anything but `human` (N6).
definition whose `tools` include anything else, whose authority holds anything but `message.send` and,
after C2-08b, `library.write`, that has no `messaging` key, or whose `messaging` names anything but
`human` (N6).
- C2-08 also landed `CREATIVE_KINDS`. C2-08b removes it, since replies have no item kinds now.
## 5. Guardrails enforced in code
### 5.1 Agents never write prose for the user
### 5.1 Agents never write story
| Mechanism | What it refuses | Test |
|---|---|---|
| M1 role validator (`role.mjs`, `CREATIVE_TOOLS`) | A creative role file with `write`, `edit` or `bash`, any authority but `message.send`, or a missing or wider `messaging` | C2-08: each rejected variant refuses with its message |
| M1 role validator (`role.mjs`, `CREATIVE_TOOLS`) | A creative role file with `write`, `edit` or `bash`, any authority but `message.send` and `library.write`, or a missing or wider `messaging` | C2-08: each rejected variant refuses with its message |
| M2 tool gate (`gate.mjs:137-138`, unchanged) | `Write`, `Edit` or `Bash` from a creative session | C2-08: `decide()` with each creative policy refuses all three, on pi and claude-code |
| M3 typed tool set (`tools.mjs`) | The tool list is exact: lookups and `send_message`. No write tool, no `raise_decision` | C2-05b: a snapshot of tool names per creative role |
| M4 library verbs are human only | `library.file`, `library.sync` and `library.status` from an agent session, with `human-required` as at `broker.mjs:1434-1435`. Later write verbs join the list | C2-04 and C2-03a: each verb from an agent session refuses |
| M5 cited-reply guard (5.2) | A reply that isn't a list of short cited items, within the `replyLimits` settings read at check time (Q222). The defaults: at most 10 items and 1,200 characters of item text per reply, an `idea` item at most 120 characters and at most 3 per reply (Q219), a `fact`, `finding`, `correction` or `question` item at most 300, and a `correction`'s `to` within edit distance max(12, 25% of `from`) of its `from`. A missing or invalid setting uses its default | C2-06: each default limit refused one past the boundary and accepted at it. A changed value takes effect on the next reply. Each invalid value in the fallback list uses the default, `true`, `[7]`, `{}` and `"5"` included. With the business file unreadable, an over-limit reply refuses and `messages` is unchanged |
| M3 typed tool set (`packages/harness/src/tools.mjs`) | The tool list is exact: the five lookups, `send_message`, `library_doc_write` and `library_doc_delete`, plus `web_search` and `web_fetch` for `companion` and `research` (mos-1308, ruling after r11). No `raise_decision`, `read_spec`, `list_agents`, `inbox`, `trail`, `write_record` or tracker read | C2-05b: a snapshot of tool names per creative role (row 224, six or eight). C2-02 adds the write tools to it: eight names, or ten for `companion` and `research` |
| M4 human verbs and agent verbs | `library.file`, `library.file.save`, `library.sync`, `library.status`, `library.limits.set` and `library.revert` from an agent session refuse `human-required`, as at `broker.mjs:1434-1435`. `library.doc.write` and `library.doc.delete` refuse every caller but a creative session holding `library.write` and a live grant | C2-04, C2-03a, C2-06 and C2-02: each human verb from an agent session refuses, and each agent verb from the human, the tester, the chief of staff and a creative session without a grant refuses |
| M5 citation guard (5.2) | A cite that fails a check in 5.2, whenever a reply carries one. More than `citationsPerReply` citations. A reply with none when `requireCitation` is true. Both keys are `replyLimits` settings read at check time (Q222, Q226), and a missing or invalid one uses its default. An unreadable business file resolves `requireCitation` to true | C2-06: each check refused once and passed once. `citationsPerReply` refused one past the limit and accepted at it. `requireCitation` true refuses an uncited reply and false accepts it. A changed value takes effect on the next reply. Each invalid value uses the default, `"true"`, `1`, `null`, `[true]`, `{}`, `0`, `1001`, `2.5`, `true` for the count and `"5"` included. With the business file unreadable, a reply with 21 citations refuses, an uncited reply refuses `citation-missing`, and `messages` is unchanged in both |
| M6 no free-text decision channel | `decision.raise` from a creative definition refuses `decision-not-allowed` in the broker. The typed tool is also absent (M3) | C2-08: a creative session's raise refuses and no decision row appears. An assistant's raise still works |
| M7 no raw session view | On a business with a library project, the conversation reader leaves creative sessions out of `catalogue()`, and `open()` refuses them with `conversation-not-listed` | C2-10a: a creative session file under the root isn't listed and won't open by id. An assistant session still lists |
| M8 no raw Discord engine | On a business with a library project, `discord check` and `discord run` refuse a binding whose engine isn't `mosaic-bus` (`discord-engine-not-allowed`), and a `mosaic-bus` binding with `tools` | C2-10a: both refuse on a library business. A pi binding on a business without a library still checks clean |
| M9 no agent write to the mapping | An organization write whose `projects` differ from the file's refuses `organization-projects-protected`, in `organizationWriter.write` | C2-04: `write` called directly with a changed playground prefix, and again with a changed hybrid entry, refuses, and the file and `changes.jsonl` are unchanged |
| M10 personas and creative instances are the human's | Any write by an actor other than the business file's `human` that touches a creative instance: the chief of staff's `assistants`, `instances` and `models` keys through `organization.set`, and the tester's `instances.set` and `routes.set`. A persona file that fails its digest, mode or fence check. A harness argv that holds the prompt text | C2-08: each path refuses with its code (4) and the file is unchanged. The human's `instances.set` and `routes.set` on `bean` work, and so does her `limits.set` change to Bean's `launch.models` key. Tester cases through a bound tester session. A mutant without the `models` check fails. No `instances` read, `changes.jsonl` line or adapter argv holds a marker string from a fixture persona |
| M11 reply limits are the human's | `library.limits.set` from an agent session (`human-required`) or the tester (`tester-refused`). Any organization write whose `replyLimits` differs from the on-disk file's refuses `organization-reply-limits-human-only` in `organizationWriter.write`, on `document` and on `written`, unless the actor is the on-disk `human` and not an agent actor shape | C2-06: the human's `library.limits.set` works. The chief of staff's `organization.set`, the tester's `instances.set` and a direct `write` with a changed `replyLimits` refuse. A direct `write` that changes both `human`, to its own actor, and `replyLimits` refuses. With a dotted human id equal to a role's actor, that role's write refuses. `library.limits.set` with an unknown key refuses `invalid-request`. In each refusal the file and `changes.jsonl` are unchanged. A mutant without the writer check fails, and so does one that compares `document.human` |
| M11 reply settings are the human's | `library.limits.set` from an agent session (`human-required`) or the tester (`tester-refused`). Any organization write whose `replyLimits` differs from the on-disk file's refuses `organization-reply-limits-human-only` in `organizationWriter.write`, on `document` and on `written`, unless the actor is the on-disk `human` and not an agent actor shape | C2-06: the human's `library.limits.set` works. The chief of staff's `organization.set`, the tester's `instances.set` and a direct `write` with a changed `replyLimits` refuse. A direct `write` that changes both `human`, to its own actor, and `replyLimits` refuses. With a dotted human id equal to a role's actor, that role's write refuses. `library.limits.set` with an unknown key refuses `invalid-request`. In each refusal the file and `changes.jsonl` are unchanged. A mutant without the writer check fails, and so does one that compares `document.human` |
Residual, in numbers. At the defaults one reply can carry up to 3 `idea` items of 120 characters, which
is 360 characters of free suggestion text inside a total of 1,200 characters of item text. The numbers
are settings now (Q222), so the residual is `ideasPerReply` times `ideaLength`, within
`itemTextPerReply`, and only the human sets them (M11). Each item must quote
a real line of her repo, but the quote can be any line. Over a chat that adds up. Code can't tell a
sentence of story from a sentence of advice, and this design doesn't propose a classifier. Jason wrote
"NEVER", and Q176 says the system enforces it. He chose these caps over 5 ideas of 200 characters
(Q219), which closes 10 step 8. What lands in her repo stays hers alone. M2 to M4 make an agent write
there impossible.
| M12 agents write supporting docs only | `supportingPath` refuses `library-write-story-path` on `library.doc.write` and `library.doc.delete` for every path class the ruling after r11 lists, before any git call. A write commits only by the plumbing steps, pushes fast-forward only and carries the `Mosaic-Agent` trailer | C2-01d: one refusal per path class against a fixture tree with a symlink, a gitlink and a case variant. C2-02: each refusal through the broker leaves the remote ref, the served ref and the mirror's refs unchanged. One write and one delete land, each with one parent, the seat's identity and the trailer. A moved tip with an unchanged blob re-applies and a changed blob refuses `stale-base`. A mutant without the supporting check fails |
Residual. The ruling after r11 states it. A supporting doc can hold any text, and the seat token's reach
at Gitea is wider than M12. Chat has no length or kind limit now (Q223), so the 360-character residual of
Q219 is gone with the caps. What lands in her story files stays hers alone. M2, M4 and M12 make an agent
write there impossible through the hub.
### 5.2 Agents always refer to the source material
The cited-reply guard runs in the broker. It covers every message from an agent session to `human` in a
The citation guard runs in the broker. It covers every message from an agent session to `human` in a
business with a library project, from the runner's RESULT and from `send_message` alike. It doesn't look
at the sender's definition, so a misconfigured role can't skip it. Citation checks need git, so they run
before the insert, outside the SQL transaction, as `#requestProject` does (`broker.mjs:1378-1430`).
Every channel to Melanie shows stored messages only (D8), so this one check covers the Console, Discord
and any later channel.
The reply body is JSON:
The reply body is free text, or a JSON object when the reply carries citations:
```json
{"items": [{"kind": "finding", "text": "Mara's eyes are grey in chapter 3 but green on her character page.",
"cites": [{"path": "<book>/<chapters>/03.md", "commit": "<40 hex>", "lines": [41, 41], "quote": "her grey eyes"},
{"path": "<book>/<characters>/Mara.md", "commit": "<40 hex>", "lines": [9, 9], "quote": "green eyes"}]}]}
{"text": "Mara's eyes are grey in chapter 3 but green on her character page.",
"citations": [{"path": "<book>/<chapters>/03.md", "commit": "<40 hex>", "lines": [41, 41], "quote": "her grey eyes", "as": "canon"},
{"path": "<book>/<characters>/Mara.md", "commit": "<40 hex>", "lines": [9, 9], "quote": "green eyes", "as": "canon"}]}
```
A body that parses as a JSON object must fit this schema: `text` a string, `citations` a list of library
cites `{path, commit, lines, quote, as?}` or web cites `{url, quote}`, and no other keys. Any other body
is a reply whose text is the whole body and which carries no citation. `as` is optional and its one value
is `"canon"`, which says the agent states the cited line as fact about her world.
| Check | Refusal code |
|---|---|
| The body parses as the schema, within the M5 limits | `cited-reply-required` |
| The item kind is allowed for the sender's definition (`CREATIVE_KINDS`). A sender outside `CREATIVE_DEFINITIONS` may send only `question` and `finding` | `reply-kind-not-allowed` |
| Every item has at least one cite | `citation-missing` |
| The path is canon or playground (not unmapped) and indexed, it exists at `commit`, `commit` is reachable from the served commit, the lines are in range, and `quote` occurs in those lines after whitespace normalization | `citation-invalid` |
| A `correction`'s `from` equals one cite's `quote`, after the same normalization, inside that cite's lines | `correction-unbound` |
| `fact`, `finding` and `correction` cite only canon paths | `playground-cited-as-canon` |
| All library cites in an item are in one universe, or a hybrid plus its sources | `citation-cross-universe` |
| A JSON object body fits the schema | `reply-invalid` |
| `requireCitation` is true and the reply carries no citation | `citation-missing` |
| The reply carries at most `citationsPerReply` citations | `citations-over-limit` |
| The path is canon, playground or supporting (not unmapped) and indexed, it exists at `commit`, `commit` is reachable from the served commit, the lines are in range, and `quote` occurs in those lines after whitespace normalization | `citation-invalid` |
| A cite with `"as": "canon"` names a canon path | `playground-cited-as-canon` |
| All library cites in a reply are in one universe, or a hybrid plus its sources | `citation-cross-universe` |
| A `url` cite is allowed only for `research` and `companion`, only for a URL this session fetched through the broker, and its quote must occur in the kept text of that fetch | `citation-unfetched` |
The guard doesn't read what the text says, only its citations (Q223). The check result stored with the
message names each library cite's scope and whether the file's last commit at `commit` carries a
`Mosaic-Agent` trailer, so the Console can label it agent-written (7.4).
Fetched text (B7). The web trail keeps the URL (`broker.mjs:1521`) and a summary of status, bytes and
redirects (`packages/bus/src/web.mjs:46`), not the page. Case 2 adds a page store in the broker's memory,
keyed by session: the text of the last 20 broker fetches, each capped at 200 KB, dropped when the session
@@ -1033,21 +1254,42 @@ runner retries, and the agent fetches again. Fetches through the harness's own w
broker, so `webPlan` (`bundle.mjs:28-35`) refuses the `harness-builtin` source for a creative definition
(`web-source-not-allowed`).
Notices (N3). The guard accepts exactly three bodies with no items: `{"items": [], "notice": "no-cited-answer"}`,
`{"items": [], "notice": "no-answer"}` and `{"items": [], "notice": "turn-failed"}`. Any other notice
value is refused. In a library business the runner sends `no-answer` and `turn-failed` instead of its two
texts at `runner.mjs:449`. The failure reason stays in the run log.
Runner texts. The runner's two fixed texts at `runner.mjs:572` (aca16404c) are plain text, which the
guard accepts while `requireCitation` is false. In a library business the runner sends `(the session
gave no answer)` and `(the turn failed)`, with the reason only in the run log, and the guard accepts
exactly those two bodies, and the two retry texts below, when `requireCitation` is true. An agent that
sends one of them gains nothing it couldn't say anyway.
Runner retry (C2-07). When `message.send` refuses with one of the codes above, the runner starts a new
turn in the same session with the code and reason, at most twice. Then it sends `no-cited-answer`. Today
the answer would be dropped (`runner.mjs:451-456`).
Runner retry (C2-06, which takes in C2-07). When `message.send` refuses with a code from the table, the
runner starts one new turn in the same session with the code and reason. If that reply also refuses, it
sends `(no answer passed the citation check)`. Today a refused send is logged and `message.read` is
skipped, so she gets no answer (`runner.mjs:577-583` at aca16404c). A send that refuses
`request-too-large` at the client (`packages/bus/src/client.mjs:19-20`), before the broker, gets the same
one retry, and the new turn names the 64 KiB limit. If that reply is still too long, the runner sends
`(the answer was too long to send)`. If it refuses with a code from the table, the runner sends the
citation text. Only one retry runs per answer (webby-0151 R1).
The unslop note and the clip. The runner appends `unslop.note()` to every RESULT body, and `clip` cuts a
body over 32,000 characters (`runner.mjs:130-131`, `:572-573` at aca16404c). Either one turns a JSON body
into plain text and drops its citations, and either one breaks the fixed texts. In a library business
C2-06 changes both. A body that parses as a JSON object gets the note added to the end of its `text` and
is serialized again, and is never clipped. A body too long for the 64 KiB request limit gets the retry
above. A fixed text goes out with no note, and the note waits for the next
reply, as it does after a failed send. A plain body keeps today's clip and note.
Tests (C2-06): one passing and one refused case per row of the table, through both the runner's RESULT
path and `send_message`, and once with a non-creative sender in a library business (N8). The fixture is
a repo the test builds in her layout's shape, with spaces and capitals in folder names, a `.canvas` file
and an unmapped top-level note. Each refusal must leave the `messages` count unchanged. C2-07 adds a
runner test with a fake harness that answers free text first and then a valid reply, and asserts one
stored message.
path and `send_message`, and once with a non-creative sender in a library business (N8). Plain text
passes with `requireCitation` false and refuses with it true. The fixture is a repo the test builds in
her layout's shape, with spaces and capitals in folder names, a `.canvas` file, a supporting folder and
an unmapped top-level note. Each refusal must leave the `messages` count unchanged. A runner test with a
fake harness that answers with a bad cite and then a valid reply stores one message. One that refuses
twice stores the retry's fixed text. With `requireCitation` true, an empty and a failed turn store their
fixed texts. With a flagged unslop event, a cited reply is stored with its citations whole and the note
at the end of `text`, a fixed text is stored with no note, and the next reply carries it. A cited reply
over 32,000 characters isn't cut. A cited reply over 64 KiB, then a valid one, stores the second. Two
replies over 64 KiB store `(the answer was too long to send)`, and with `requireCitation` true the guard
accepts it. With the business file unreadable, an uncited reply refuses `citation-missing` and
`messages` is unchanged.
### 5.3 Tool use is guarded
@@ -1060,6 +1302,9 @@ stored message.
- `research` web use goes through the broker's web tools (`tools.mjs:197-206`), which check the grant on
every call (`broker.mjs:1516`).
- Discord: the relay has no tools of its own, and M8 refuses one that does.
- Writes: M12 and the grant check on `library.doc.write` and `library.doc.delete`. The broker re-checks
every call, so a session whose grant was revoked gets `library-write-no-grant` from a tool it still
holds.
## 6. One chat with N agents and @tag addressing
@@ -1103,17 +1348,19 @@ the first word is plain text. `room.open` against a same-name thread on another
### 7.1 Obsidian
She has it working already. `~/src` is her vault, the books repo is a folder in it, and obsidian-git
pushes it to git.mosaicstack.dev (mos-1000). The hub changes nothing on her machine and commits nothing
to her repo. The setup sheet (C2-12) tells her which folders the agents read, that a folder named in
`playground` is her playground, and how to add a graph colour group for that path in her own vault
settings if she wants one. The graph view and backlinks stay Obsidian's own. The sheet also says two
pushes it to git.mosaicstack.dev (mos-1000). The hub changes nothing on her machine. It commits to her
repo only an agent's supporting doc write, her revert and her own Console save, and obsidian-git pulls
each on its next pull. The setup sheet (C2-12, rewritten by C2-12b) tells her which folders the agents
read, that a folder named in `playground` is her playground, that a folder named in `supporting` is where
agents may write, and how to add a graph colour group for those paths in her own vault settings if she
wants one. The graph view and backlinks stay Obsidian's own. The sheet also says two
things she might not expect. Moving a note into a book folder makes it canon, so agents may cite it as
fact (N2). And a short link to a name that also exists in another of her repos can point somewhere else
for the agents than in Obsidian. A full-path link avoids that (N4, 1.3).
### 7.2 Sync (bus side)
One function, `syncCommand(kind, url)`, builds every fetch (and later push). Its argv is `SAFE`
One function, `syncCommand(kind, url)`, builds every fetch and push. Its argv is `SAFE`
(`git.mjs:7-9`), then `-c protocol.https.allow=always`, `-c http.followRedirects=false` and
`-c credential.helper=`, then `-C <mirror>`, then the command with the URL as an argument. Its env is
`gitEnv()` plus `LC_ALL=C` and `GIT_ASKPASS` naming a helper script outside the mirror, which reads the
@@ -1138,8 +1385,12 @@ written atomically, so it survives a bus restart. The human verb `library.status
`library.sync` runs on a 60-second timer in the bus, and before any lookup that finds the last fetch
more than 60 seconds old.
Later (C2-03b, with C2-02): push of bus commits, the push race retried once, `unpushed`, and
`library-remote-invalid` refusals of bus writes (B9).
C2-03b adds the push for C2-02's commits, the one race retry, `library-push-failed`, and
`library-remote-invalid` refusals of writes (B9). It also changes whose token the helper answers with: the
chief of staff's grant token and Gitea user for the fetch, in place of slot `library-gitea`, the writing
seat's own grant token for its write, and slot `library-human` for her save and revert (ruling after
r11). The fetch falls back to slot `library-gitea` only while no chief grant record has ever existed, and
nothing else ever reads that slot. There is no `unpushed` state.
Tests (C2-03a): `syncCommand` argv equals `SAFE` plus the fixed sequence above, with no other
`protocol.*` key. Its env holds `LC_ALL=C` and `GIT_ASKPASS` pointing outside the mirror. The helper
@@ -1148,30 +1399,37 @@ refuses a wrong host, a wrong port and another user. A 302 to another host fails
a bare repo: fast-forward, R-problems recorded without holding the head, `library-diverged` kept until
`--accept`, a failed fetch kept serving. A mutant that sets `protocol.allow=always` fails the argv test.
### 7.3 Console WYSIWYG (after the slice)
### 7.3 Console WYSIWYG (slice rows that don't gate 10-27)
- Library pages (C2-11b) show a tree of universes, books, type folders, manuscript and playground, with
the playground in its own section and colour. They open files in the existing editor through
`library.file` and `library.file.save`, with `base` as today. Saving needs the write rows.
- Library pages (C2-11b) show a tree of universes, books, type folders, manuscript, playground and
supporting folders, the last two each in their own section and colour. They open files in the existing
editor through `library.file`. Each save is one commit through `library.file.save {path, base,
content}` (ruling after r11, F41). A file that moved refuses `stale-base`, and the page keeps her text
and offers a reload.
- Wikilinks render as links in the editor (C2-11a). Clicking one opens the target.
- Promote (C2-19) and crossover (C2-15) buttons.
- Promote (C2-19) and crossover (C2-15) buttons come later.
### 7.4 Console chat (C2-10b)
- The chat view renders a cited reply as items. Each item shows its kind, its text and its cites as
path, lines and quote, with a scope badge (canon, playground or web). It marks a cite "changed since"
when the file's last commit is newer than the cited one. A notice shows a fixed line ("No cited
answer", "No answer", "The turn failed").
- The chat view renders a reply as its text, then its citations as path, lines and quote, each with a
scope badge (canon, playground, supporting or web). A cite whose file's last commit carries a
`Mosaic-Agent` trailer gets an agent-written label (Mos point 3). It marks a cite "changed since" when
the file's last commit is newer than the cited one.
- A `library-write` notice shows a fixed line, "<name> wrote <path>" or "<name> deleted <path>", with the
commit and a Revert button that calls `library.revert`.
- A status line reads `library.status`: the served commit and its age, `library-diverged`, a fetch
error, and the count of R-problems with a link to the list.
- `renderItems(body)` is one pure function in `packages/library`, shared with the Discord relay.
- `renderReply(body)` is one pure function in `packages/library`, shared with the Discord relay. It
replaces C2-01's `renderItems`.
### 7.5 User and agent sync
- Agents read the served commit through the broker. They never have a private copy (2.1).
- Every reply cites a commit, and the Console marks stale cites.
- Her push is visible to agents within about 60 seconds.
- The bus token is read-only on the books repo in the slice. Nothing the hub does can change her repo.
- Agents write only supporting docs, each a commit with a notice she can revert. Her Console saves are
commits too. Obsidian sees both on its next pull. A race with her Obsidian push refuses `stale-base`
and never merges (D6).
### 7.6 Discord relay (C2-18, after the slice, optional)
@@ -1182,13 +1440,14 @@ stays and its engine changes, as below.
- A new engine module, `engine-bus.mjs`, next to `engine-pi.mjs`. Binding field `engine.provider:
"mosaic-bus"`, plus a map from each channel id to one thread id. `prompt(text)` calls `thread.send` on
the bound thread with her message as the body. It then waits for the next message in that thread from
the instance to `human`, up to the binding's turn timeout, and returns `renderItems(body)` as `text`.
the instance to `human`, up to the binding's turn timeout, and returns `renderReply(body)` as `text`.
The connector's loop, journal, delivery and authorization stay as they are (`connector.mjs:381-387`).
- The relay posts only bodies the broker stored, and those passed the guard (D8). It never sees a model's
output. A timeout posts the fixed line "No answer yet. It will appear here when it arrives", and a later
poll posts the stored reply.
- Rendered items can pass Discord's 2,000-character limit, because cites add to the 1,200 characters of
item text. The relay splits at item boundaries, each part with its own nonce.
- A rendered reply can pass Discord's 2,000-character limit, since replies have no length cap now. The
relay splits at paragraph boundaries, and a paragraph over the limit at line and then character
boundaries, each part with its own nonce.
- On each change of `library.status`, the relay posts one line to her channels: diverged, a fetch error,
a new R-problem count, or "in sync again".
- M8 refuses any other engine on a library business, and a `mosaic-bus` binding that sets `tools`.
@@ -1199,11 +1458,11 @@ stays and its engine changes, as below.
Tests (C2-18), against a fake bus and fake REST, with no Discord server:
- A posted Discord message equals `renderItems` of the stored body, byte for byte.
- A posted Discord message equals `renderReply` of the stored body, byte for byte.
- A refused agent reply (nothing stored) makes the relay post nothing but the fixed notice the runner
then stores.
- A pi-engine binding and a relay binding with `tools` both refuse on a library business.
- A long reply splits at item boundaries, and no part goes over 2,000 characters.
- A long reply splits at paragraph boundaries, and no part goes over 2,000 characters.
- Each `library.status` change posts exactly one line.
## 8. Row list
@@ -1213,7 +1472,8 @@ work as Sage-dispatched subagents (lead decision 129). Codex gpt-6.1-sol (Rocko)
gpt-6-luna takes low-level work. An independent reviewer (Filbert, Dewey or Webby) reviews every row.
Each gate is its package's changed test files run singly plus the full gate before landing. No test may
touch `~/.mosaic-dev`, `~/.config/mosaic-dev`, live units, slot files, her repo or any Discord server.
Chains follow Q177 merge-chaining. S marks a slice row.
Chains follow Q177 merge-chaining. S marks a slice row. C2-11a and C2-11b are slice rows that don't gate
10-27 (Q195).
| Row | S | Scope | After | Owner | Test gate |
|---|---|---|---|---|---|
@@ -1222,36 +1482,41 @@ Chains follow Q177 merge-chaining. S marks a slice row.
| C2-04 | S | Business file `kind: "library"` entry, skips at each consumer in 2.1, M9, mirror creation at the `projectRepo` path, human verbs `library.file` (read) and `library.status` | C2-01 | Sol (Herb, sage-1482) | business file tests. One test per consumer skip. A launcher test that the workspace has no clone. M9 test. Each verb from an agent session refuses (M4) |
| C2-03a | S | Fetch-only sync (7.2): `syncCommand`, askpass helper, served ref, `library-diverged`, `--accept`, sync state file, timer, `library.sync` | C2-04 | Opus | the C2-03a tests in 7.2 |
| C2-05a | S | Lookup index and queries: find, get, search, manuscript with scope, universe, isolation | C2-01 | Haiku high | isolation, scope labels on every tool, unmapped files never returned, paging under `REPLY_CAP`, rebuild time on a 3,000-file fixture |
| C2-05b | S | Broker lookup verbs and typed tools. Sync before a read older than 60 s | C2-05a, C2-03a | Sol (Rocko) | broker verb tests. Typed tool name snapshot per creative role (M3) |
| C2-06 | S | Cited-reply guard (5.2) in `packages/library`, the broker's pre-check on agent-to-human `message.send`, the page store, the three notices, the `replyLimits` settings and M11 (Q222) | C2-05a, C2-04 | Opus | one pass and one refusal per check in M5, M11 and 5.2, through the RESULT path and `send_message`, and with a non-creative sender. `messages` count unchanged on each refusal |
| C2-07 | S | Runner retry on a guard refusal (at most 2), then `no-cited-answer`. `no-answer` and `turn-failed` in a library business | C2-06 | Haiku high | fake harness: free text then a valid reply stores one message. An empty and a failed turn store the fixed notices |
| C2-01d | S | `supporting` lists and their prefix rules, `library-supporting-other-universe`, the `supporting` scope ahead of playground and canon, the `editor` field, and `supportingPath` (M12) | C2-01c | Opus | one test per prefix rule and refusal, a folder in both lists, scope order, every M12 path class, `editor` refused when not two non-empty strings |
| C2-05b | S | Broker lookup verbs and typed tools. Sync before a read older than 60 s. Row 224, built to the earlier brief and landing as built (sage-2833) | C2-05a, C2-03a | Sol (Rocko) | broker verb tests. Typed tool name snapshot per creative role without the write tools (M3) |
| C2-05c | S | `scope` with `supporting` in the lookup verbs and tools, `blob` in `library_get` (sage-2833) | C2-05b, C2-01d | Sage picks | a `supporting` lookup in find, search and get, canon by default leaves supporting files out, `blob` equals the served blob id, the M3 snapshot unchanged |
| C2-06 | S | Citation guard (5.2) in `packages/library`, the broker's pre-check on agent-to-human `message.send`, the page store, `replyLimits` with `requireCitation` and `citationsPerReply` and M11 (Q222, Q226), the runner texts, and the runner retry that was C2-07 | C2-05a, C2-04 | Opus | one pass and one refusal per check in M5, M11 and 5.2, through the RESULT path and `send_message`, and with a non-creative sender. `messages` count unchanged on each refusal. The runner tests in 5.2 |
| C2-07 | | Folded into C2-06 (ruling after r11) | | | |
| C2-08 | S | Six role files and contracts, the new constants, M1, M6, the `webPlan` refusal, gate tests (M2), messaging test. `persona.name` and `persona.sha256` in `REGISTRY`, the persona file read and checks, and the fenced persona block between the contract and "## This session" in `prompt()`. M10: the `organization.set` refusals, the `models` check, the `routes.set` and `instances.set` creative branches (4). Both adapters pass the prompt as a file, not its text (4) | none | Opus | role tests, gate tests, a creative raise refused, `harness-builtin` refused for `research` and `companion`, `scripts/test-config.sh`. Persona vars refused at other layers, malformed and on a non-creative definition. The persona file refused when missing, a symlink, another uid's, group or other readable, over 8,000 characters, off its digest, holding `</persona-file>` or a `# `/`## ` line. A prompt snapshot pinning contract, persona, session. The M10 tests, tester cases through a bound tester session, the `models` mutant. Adapter tests: argv holds the prompt path and no marker from the prompt, and pi's adapter refuses a missing and an unreadable prompt file. M10's actor case: the human's `limits.set` change to Bean's `launch.models` key works next to the chief's refusal. Business file, `.history` and `changes.jsonl` modes after a write |
| C2-09a | S | `#threaded` and launcher read `THREAD_DEFINITIONS`. `thread.create` takes `instance`. The Console new-thread control picks it | C2-08 | Sol (Rocko) | the C2-09a tests in section 6 |
| C2-10a | S | M7 in the conversation reader. M8 in `discord check` and `discord run` | C2-08 | Haiku high | the M7 reader test, the M8 tests |
| C2-10b | S | Console chat: cited-item renderer through `renderItems`, the status line | C2-06, C2-09a, C2-03a | Haiku high | webui tests for items, scope badges, notices, "changed since" and the status line |
| C2-10b | S | Console chat: the reply renderer through `renderReply`, which replaces `renderItems`, the agent-written label, the notice line with Revert, the status line | C2-06, C2-09a, C2-03a, C2-02 | Haiku high | webui tests for text and citations, scope badges, the agent-written label, the notice line and its Revert call, "changed since" and the status line |
| C2-12 | S | The setup sheet for Melanie (7.1, including N2 and N4, and what changes for Bean and Chet in 4) and the survey run sheet for `<host>` | C2-01 | Luna | doc only, reviewed against the survey's real flags |
| C2-12b | S | Setup sheet rewrite for Q223 and Q226: free-text chat, supporting folders and how to mark one, agent writes as commits with notices and Revert, her Console saves, the `editor` name and email, the settings that replace the caps, and the residual | C2-12 | Luna | doc only, reviewed against the ruling after r11 |
| C2-20 | S | Persona import (9.1): a script that reads only the persona definitions of the two Hermes profiles, confirms profile to name, and writes each persona file (4) with its `persona.name` and `persona.sha256` for the business file. Prints names, byte counts and sha256 only. A `--dry-run` mode, run before Jason's go, prints only counts of files, lines and lines starting `# ` or `## ` | C2-08 | Opus (Gosalyn), run under Jason's go | fixture Hermes profile trees: the right fields are taken, memory and session files are never opened (a read trace on the fixture), a profile with no persona field refuses, a persona that would fail C2-08's fence check refuses, output holds no persona text, each written file is 0600 and its digest matches, `--dry-run` writes nothing and prints the right counts for a fixture with two heading lines and no marker string from it |
| C2-13a | S | `scripts/case2-unit.sh`, which renders the bus and Console system units from their existing templates and which root runs from a root-owned checkout in 10 step 2c, and `scripts/case2-precheck.sh` with the host checks. No library dependency, so row 195 chains on it (sage-1480) | none | Opus (Gosalyn) | the C2-13a brief's render and precheck cases: token rules, `@PATH@` from `--node`, the four hardening lines, the Console's capability lines and privileged port, one `WantedBy=multi-user.target` on each unit (Q189), copy-id pairing, the serve flag set, the key file and the unprivileged port start. Scratch unit directory and scratch data root |
| C2-13b | S | Install script for `<host>` as `<user>`: bootstrap, the stack copy, business file with the mapping, `bean` and `chet` with their persona vars and files, any specialists she picks, her human id, token slot path. The install writes no unit. Adds the library checks to the precheck. Jason or his delegate runs it (10) | C2-13a, C2-04, C2-03a, C2-20 | Opus (Gosalyn), Jason's go | precheck library checks: the business file, `.history`, `changes.jsonl` and persona files have the modes in 4 and each persona digest matches, no Discord binding exists, and after step 4 `--library-status` reads the bus's recorded sync state and lists R-problems (10 step 4a). Tested against a scratch data root and a local bare repo |
| C2-14 | S | End-to-end acceptance on a scratch data root and a bare repo in her layout: sync, ask a creative agent in its thread, a reply whose cites verify, a playground cite as canon refused, a force-push held as diverged, the Console shows the reply as items | slice rows | Opus | one script, which the reviewer also runs |
| C2-02 | | Bus writes: `writeLibraryFile`, `commitFile` refactor, r1's S0 rule on the working tree | C2-04 | Sol (Rocko) | writer tests, `packages/project/tests` unchanged and green |
| C2-03b | | Push path, race retry, `unpushed`, `library-remote-invalid` on writes | C2-02, C2-03a | Opus | push tests against a bare repo |
| C2-08b | S | Role changes, authorized in mos-1309 item 5: `library.write` in the creative authority and in `checkCreative`, the gitea credential on the six creative definitions and the chief of staff, instance `identity` and `publish`, `CREATIVE_KINDS` removed | C2-08 | Opus | role tests: a creative role with `library.write` validates, one with any other added action refuses, the chief holds the credential and not `library.write`. `scripts/test-config.sh` |
| C2-21 | S | Grant executor for `kind: "library"` (ruling after r11, F40): the creative raise check narrowed to library projects (mos-1163), collaborator in step 4, the level check in step 5, the rendered block, revoke. The install enrolls the chief of staff | C2-08b, C2-13b | Opus | first, the two Gitea facts proven on a scratch Gitea 1.27.1. Then executor tests: `write` for a creative definition, `read` for the chief and any other, a wider permission or `admin` refuses `gitea-reach-wider`, revoke deletes the token, a non-library project still forks, a creative raise naming a non-library project refuses `gitea-not-declared` |
| C2-03b | S | Push and credentials (7.2): the push form of `syncCommand`, the race retry once, `library-push-failed`, `library-remote-invalid` on writes, the helper answering with a seat's grant token, the chief's for the fetch, and slot `library-human` | C2-03a | Opus | push tests against a bare repo. The helper refuses another seat's user, and no token reaches argv, config or a log |
| C2-02 | S | Writes (ruling after r11): `library.doc.write`, `library.doc.delete`, their typed tools, the plumbing commit and trailer, the notice, `library.revert`, the trailer label in the check result, and `library.file.save` | C2-01d, C2-03b, C2-04, C2-05c, C2-08b | Opus | the M12 broker tests, write, delete and revert against a bare repo, the grant refusals, the notice body, her save's author, the M3 snapshot with the write tools |
| C2-14 | S | End-to-end acceptance on a scratch data root and a bare repo in her layout: sync, ask a creative agent in its thread, a reply whose cites verify, a playground cite as canon refused, a force-push held as diverged, an agent's supporting doc write and its notice, a story path write refused, a revert, her Console save as one commit, the Console shows the reply | slice rows | Opus | one script, which the reviewer also runs |
| C2-19 | | `library.promote`, multi-path commit | C2-02, C2-03b | Sol (Rocko) | promote in one commit, human only |
| C2-09b | | `room.open`, `room.send` with @tags, `room.read` | C2-09a | Sol (Rocko) | the C2-09b tests in section 6 |
| C2-18 | | Discord relay (7.6), optional, after its own Jason ruling | C2-09a, C2-06, C2-03a | Opus | the C2-18 tests in 7.6 |
| C2-10c | | Console room view: merged timeline, @name completion | C2-09b, C2-10b | Haiku high | webui tests, @tag parse parity with the broker |
| C2-11a | | Editor wikilink node. First measure whether `[[` round-trips today | none | Sol (Rocko) | `roundTrip` tests: plain, labelled, heading, in a list, next to front matter |
| C2-11b | | Console library pages: tree, open, save (with C2-02), promote and crossover buttons | C2-04, C2-11a | Haiku high | webui tests. Save refuses `stale-base` in the UI |
| C2-11a | S | Editor wikilink node. First measure whether `[[` round-trips today | none | Sol (Rocko) | `roundTrip` tests: plain, labelled, heading, in a list, next to front matter |
| C2-11b | S | Console library pages: tree with playground and supporting sections, open, one commit per save through `library.file.save`, `stale-base` keeps her text | C2-02, C2-11a | Haiku high | webui tests: a save makes one commit with her `editor` as author, a moved file shows `stale-base` and keeps her text, an unmapped path offers no save |
| C2-15 | | Hybrid universes in the business file, `exclude` and `pin` | C2-05a | later | |
| C2-16 | | Agents see each other's replies in a room | C2-09b | later | |
| C2-17 | | Automatic replay of unpushed commits on divergence | C2-03b | later | |
| C2-17 | | Dropped: there is no unpushed state (ruling after r11) | | | |
Parallel waves:
- Start now: C2-01, C2-08, C2-11a, C2-13a.
- On C2-01's candidate: C2-04, C2-05a, C2-06, C2-12. On C2-08's: C2-09a, C2-10a and C2-20.
- Next: C2-03a, C2-07. Then C2-05b, C2-10b and C2-13b.
- Last: C2-14. The write rows and rooms follow when a seat is free.
- Landed: C2-01, C2-01c, C2-03a, C2-04, C2-08, C2-12, C2-13a, C2-20. C2-13b is in progress.
- Next, side by side: C2-01d, C2-03b, C2-05a, C2-08b, C2-09a, C2-10a, C2-11a, C2-12b.
- Then: C2-05b (row 224, built), C2-05c, C2-06, C2-21 and C2-02. Then C2-10b and C2-11b.
- Last: C2-14. Rooms, the relay and promotion follow when a seat is free.
Dependencies on other tracks: row 190 doesn't apply, since nothing else runs as `<user>`. SSO (Q175,
#412) gates the Console from her machine, and the Console is now her channel, so the slice needs SSO
@@ -1335,14 +1600,15 @@ The in-VM tightening is Vikunja #423. Only its step 1, the `localadmin` account,
### 9.2 The slice and the date
The smallest slice that lets her start is the S rows: C2-01, C2-03a, C2-04, C2-05a, C2-05b, C2-06, C2-07,
C2-08, C2-09a, C2-10a, C2-10b, C2-12, C2-13, C2-14 and C2-20. With it she keeps writing in Obsidian exactly
as now. In the Console she opens a thread with Bean, Chet or a specialist and reads each answer as items
whose citations the broker checked. A status line tells her when the hub falls behind her repo. Hermes
keeps running beside it.
The smallest slice that lets her start is the S rows: C2-01, C2-01c, C2-01d, C2-02, C2-03a, C2-03b, C2-04,
C2-05a, C2-05b, C2-05c, C2-06, C2-08, C2-08b, C2-09a, C2-10a, C2-10b, C2-12, C2-12b, C2-13a, C2-13b, C2-14,
C2-20 and C2-21. C2-11a and C2-11b are slice rows too, and they don't gate 10-27 (Q195). With it she keeps
writing in Obsidian exactly as now. In the Console she opens a thread with Bean, Chet or a specialist and
reads each answer as free text whose citations the broker checked. Each agent write shows as a notice she can
revert. A status line tells her when the hub falls behind her repo. Hermes keeps running beside it.
Not ready by 10-27: bus writes and the Console editor (C2-02, C2-03b, C2-11a, C2-11b, C2-19), rooms with
@tags (C2-09b, C2-10c), the Discord relay (C2-18), and C2-15 to C2-17. The slice makes 10-27 only if
Not ready by 10-27: promotion (C2-19), rooms with @tags (C2-09b, C2-10c), the Discord relay (C2-18), C2-15
and C2-16. The editor may land after 10-27 without holding it. The slice makes 10-27 only if
section 10 steps 1-7 are done by about 10-24. The Console from her machine needs SSO (#412) or an interim
sign-in ruling, and that is the step most likely to be late.
@@ -1428,7 +1694,9 @@ What closes the rest.
The Gitea host. Her books repo is already on git.mosaicstack.dev (mos-1000). On this host that name
resolves to 10.1.1.222, and STATE.md lists its certificate among the edge certificates, so I treat it as
reachable from the internet. Her writing is already there by her own choice. The hub adds no new remote.
What it adds is one bot account with read access to that one repo.
It adds no shared bot account. Each writing seat gets its own Gitea account through the grant path, a
collaborator on that one repo with `write`, and the chief of staff one with `read` (ruling after r11).
Her own token signs her Console saves.
1. The host is set (Q181): 10.1.1.54 as `mosaic` under `/srv/mosaic`, 16 GiB and 6 cores. Jason
confirmed Q4 (Q183), so #421 is closed and the install doesn't wait on it.
@@ -1476,17 +1744,25 @@ What it adds is one bot account with read access to that one repo.
the one file decision 119 and `packages/cli/README.md` document, so I kept it. He places it, or
says who may, per the Q173 secrets rule. Agents never print it. Until #423 step 5, `melanie`'s
sudo can read it, and so can her Hermes processes (9).
3. A new Gitea bot account for the hub, with read access to the books repo only. Not bean-brain, and not
her other three repos. Her repo's access list is hers, so adding the bot means asking her or acting as
admin, which is his call.
4. The bot's token in slot `library-gitea` under `<configDir>/secrets/<business>/`, which is
`/srv/mosaic/.config/mosaic-dev/secrets/<business>/` for `mosaic`, mode 0600. He puts it there, or
says who may, per the Q173 secrets rule. Agents never print it.
3. No bot account. The install enrolls the chief of staff, which raises the gitea grants for `books`
through the existing step, and the executor makes each seat a collaborator on her repo (C2-21). The
seats reach the books repo only, not bean-brain and not her other three repos. Whether each grant also
needs his Approve is Q31, asked of him, and this design adds no step for it. The collaborators change
her repo's access list, which is hers, so step 7 tells her.
4. Her own Gitea token for her Console saves, in slot `library-human` under
`<configDir>/secrets/<business>/`, which is `/srv/mosaic/.config/mosaic-dev/secrets/<business>/` for
`mosaic`, mode 0600. He puts it there, or says who may, per the Q173 secrets rule. Agents never print
it. If the install runs before C2-03b lands, the C2-03a fetch still reads slot `library-gitea`, which
then needs a read token as r11 said. After C2-03b the fetch reads that slot only while no chief grant
record has ever existed (7.2). Step 4b removes it.
4a. `scripts/case2-precheck.sh --business <business> --library-status` as `<user>`, once the bus is up
and step 4's token is in place. It reads the bus's recorded sync state only and syncs nothing. It
and the fetch token is in place. It reads the bus's recorded sync state only and syncs nothing. It
refuses when the mirror is missing, when `lastError` is set, or when `lastFetch` is null. It prints
each R-problem as a `problem` line and then a count, never text (sage-2189). The bus syncs every 60
seconds, so the first check after step 4 may have to wait for one sync.
4b. Once C2-21 has run and `library.status` shows the fetch on the chief's grant, Jason deletes slot
`library-gitea` and revokes its token in Gitea. The fetch never reads the slot again, since a chief
grant record then exists (webby-0151 R3).
5. Her Console sign-in from her machine: her Authentik user and `sub` once SSO lands (#412 step 3 is the
model), or his ruling on an interim. The proxy forwards to the Console's port below 1024 and sets the
proof header. The proof file goes under `<configDir>/secrets/`, mode 0600, owned by `mosaic`, and
@@ -1495,13 +1771,14 @@ What it adds is one bot account with read access to that one repo.
6. The C2-20 persona import: who runs it on `<host>`, his check of the printed profile-to-name pairing,
and whether the per-channel prompts count as personality.
7. Running `library survey` on `<host>` after step 4, which prints names and counts, never text. Then
contacting Melanie: who tells her what changes for Bean and Chet (4), sends the setup sheet (C2-12),
and asks her to confirm the mapping, the universes, the playground folders and which specialists to
start with, and when. A specialist she wants later is added by the human through `instances.set`
contacting Melanie: who tells her what changes for Bean and Chet (4), sends the setup sheet (C2-12b),
tells her the agents become collaborators on her repo, and asks her to confirm the mapping, the
universes, the playground and supporting folders, her `editor` name and email, and which specialists
to start with, and when. A specialist she wants later is added by the human through `instances.set`
(4), with no hand edit and no new row.
8. The residual in 5.1. Closed: Jason ordered the lower caps, 3 idea items of 120 characters per reply,
360 characters of free text within 1,200 total (Q219, mos-1299). These are the defaults of settings
only the human changes (Q222, M11).
8. The residual in 5.1. Closed. Q219 set lower caps, and Q223 then removed the caps with the item
format. What remains is the residual the ruling after r11 states, a supporting doc that holds any
text, and C2-12b tells her about it.
9. The instance start. Stopping Hermes comes later, and only when she chooses it.
- The bus and the Console are already running from step 2c, and both come back after a boot on
their own (Q189, which reverses mos-1097 item 1). Nobody starts the Console by hand here. With a