Files
jason.woltje 158205c5cc lead: commit darkwing's untracked evidence (348 files: queue-137 queue-141 queue-150 queue-162 queue-165 queue-166 queue-167 queue-169 queue-179 queue-180 queue-181 queue-182 queue-185 queue-186 queue-187 queue-188 queue-190 queue-93)
Unclaimed files left in the canonical checkout. Inventory: canonical-cleanup-inventory.md (commit-as-evidence groups). detect-secrets scan clean on 2026-11-02: hits are sha256 pins, git object ids, test sentinels already in tracked tests and unit-file lines.
2026-11-02 15:05:14 -06:00

418 lines
24 KiB
Markdown

# Row 141 (#1635): messages addressed by agent name
Builder: Darkwing. Brief: `docs/plans/2026-10-15_q167-rows-r3.md`, section "Messages are
addressed by agent name", blob 6805904555dc. PRD 1.5 REQ-MSG-2. Base: 18aee8fa1. It was rebased from 4ebef2284 onto b0424e9cc, then onto
18aee8fa1 (see "Rebase and first run" and "Second run and second rebase" below).
Status: rebased onto 18aee8fa1 in `~/darkwing-scratch/r141/wt3`, with the fixes from two stopped
runs. The third run stopped on a pin the candidate missed (see "Third run"). The fourth run passed
the round and the full gate (see "Fourth run"). The candidate is `build.patch` (26 files) and
`candidate-manifest.sha256` here.
## Scope and owned files
The brief's owned files, plus two additions Sage made:
- `packages/bus/schema.sql`, `packages/bus/src/migrations/3f-3g.sql` and `packages/bus/src/store.mjs`:
the 3f→3g step.
- `packages/bus/src/broker.mjs`: name resolution in `message.send`, the writer `name.set` and the read
`names`.
- `packages/cli/src/cli.mjs`: `mosaic name` and `mosaic names`.
- `packages/harness/src/tools.mjs`: the `send_message` tool's `to` description.
- Tests and the bus, cli and harness READMEs. The harness tests and README change because the chief's
new grant has no tool until row 155 (see "The harness").
- `roles/chief-of-staff.json`, for the grant alone. Source: sage-0573. 139 landed first without the
grant, so 141 carries it. Brief line 457 says whichever of the two lands second adds it.
- `packages/business/src/vocabulary.mjs` and the business role tests that pin the chief's grants.
Source: sage-0589.
## The action: `instance.name`, routed under REQ-MSG-2 (1.5)
A grant needs an action, and no existing action covers names. sage-0589 ruled out reusing
`organization.change`, which the chief already holds within-role and which would have left the role
file unchanged.
`instance.name` joins ACTIONS and ROUTED in `packages/business/src/vocabulary.mjs` and in broker.mjs's
own ACTIONS and ROUTED. REQ-WAIT-3 makes a new action a hard wait "until a PRD version says it
isn't", and the reviewed commit that adds it has to say which it is. The second half is met by PRD 1.5
REQ-MSG-2: "The chief of staff sets them once its role file grants that within-role." A hard wait
can't be granted within-role (REQ-WAIT-2), so that sentence is the PRD version naming the action
routed. The commit message will say: `instance.name` is routed under REQ-MSG-2 (1.5).
HARD_WAITS and GATED_ONLY don't change, so the hard-waits pins stay. The bus hard-waits test already
checks the broker's ACTIONS against the vocabulary's, and HARD_WAITS against GATED_ONLY. With
ACTIONS equal, that also makes the two ROUTED lists agree, so no new agreement test is needed. A new
assertion pins `instance.name` in ROUTED and out of GATED_ONLY, per sage-0589.
`TASK_VERBS` is the `task.*` actions, so the new action doesn't reach the task socket.
## Schema 3f→3g
`instance_names` is an append-only log, like every other bus table. The latest row for an instance is
its current name.
```sql
CREATE TABLE instance_names (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
at TEXT NOT NULL,
business TEXT NOT NULL,
instance TEXT NOT NULL,
name TEXT NOT NULL,
by TEXT NOT NULL
) STRICT;
```
The brief names `(business, instance, name)`. `seq`, `at` and `by` follow the other tables: `at` puts
the table in `timedTables`, and `by` records the human or the chief's run.
Triggers:
- `instance_names_no_update`, `_no_delete`, `_no_replace`, the same three every table has.
- `instance_names_unique`: an insert refuses when another instance of the business currently holds
the name, where "currently" means that instance's latest row. Renaming an instance frees its old name.
The broker checks the rest, because the store doesn't know the business file: the name matches
ID_PATTERN, and it equals no instance key, no role definition the business knows, and neither
`human` nor `tester`. `_bus` fails ID_PATTERN.
`messages` gains `to_name TEXT`. `to_role` stays the instance key, so delivery, claims and threads read
what they read today. In schema.sql the column goes in the form ALTER TABLE leaves in sqlite_master,
`body TEXT NOT NULL\n, to_name TEXT) STRICT;`, as `decisions` does at line 61.
`migrations/3f-3g.sql` is one CREATE TABLE, its triggers and one ALTER TABLE. store.mjs gets version
`3g`, the new SCHEMA_SHA256, a `{ from: '3f', to: '3g' }` step and `instance_names` in `timedTables`.
Its chain check proves that 3b plus every step gives schema.sql's digest.
## message.send
`to` takes a name or an instance key, as before, or `human` or `tester`. The broker resolves `to`
first, before every check that reads it. That covers the tester's role check, the authority target,
the tester-reply check, unknown-role, the messaging lists, the thread, the insert, the route and the
waiting notice. Each reads the instance key. The message records the instance in `to_role` and the
name in `to_name`. A send by instance key leaves `to_name` null, so sending to a role works as it
does today.
An instance key, `human`, `tester` and a role name win over a name (sage-0590). `name.set` refuses a
name equal to any of them, but the owner can later add an instance whose key, or a definition whose
name, some instance already holds as its name. The key then reaches its instance, and a send to the
definition refuses `unknown-role`, since `to` never addresses a definition. The old name stops
resolving until it is renamed. Name-first would have sent those messages to the named instance.
A name the business doesn't hold refuses `unknown-role`, as an unknown instance does.
## The writer: `name.set` and `names`
`name.set { instance, name }`:
- The human through the CLI (`#human`) always may.
- An agent may only when its file grants `instance.name` within-role, and the write consumes it
(`action.allowed`, target the instance). Any other class refuses `not-authorized` before
`#checkAuthority` runs, so a cross-role grant or a gated decision can't set a name. That follows
REQ-MSG-2 and sage-0573, which give names to the human and the chief alone.
- A tester or reader refuses, as elsewhere.
- Refusals, in the order the broker checks them: `not-authorized` (or `read-only`, `tester-refused`),
`unknown-role` for an instance not in the business, `invalid-request` for a name that fails
ID_PATTERN, `name-is-instance`, `name-is-role`, then `name-taken`. Only then does an agent's write
consume the grant, so a refused write logs no `action.allowed`. The store's trigger backs up
`name-taken`.
- `by` is the human's name or the chief's run id, as `projects.created_by` records it.
`name-is-role` reads the definitions the loaded business knows: every definition one of its instances
uses, which is what `loadBusiness` puts in `definitions`. A shipped role file no instance uses, such as
`cto` in a business without one, is not checked; the broker doesn't read `roles/`. Sage accepted this
(sage-0590). The PRD reading: "role name" in REQ-MSG-2 means a definition in the loaded business.
`#definitions(b)` is the one list both `name.set` and the send-time resolver read.
`names` lists the current name of each named instance in the business. The human and agents may
call it. The tester and reader lists don't change; the Console route is row 155's.
## The harness
The chief's typed tool for names is row 155's. Row 141 changes `send_message`'s `to` description to
"Role instance, its name, or \"human\"", so an agent can send by name now.
Until row 155, the chief holds `instance.name` with no tool. `untypedActions` returns it, the bundle
manifest lists it in `untyped`, and the prompt carries the existing line "Actions you hold that have
no bus tool yet: instance.name. Say so instead of working around it." Two harness pins said the chief
had nothing untyped (`tools.test.mjs:42`, `bundle.test.mjs:92`); they now pin `["instance.name"]`
and the prompt line. The harness README says the same. Row 132's 3d re-pin window checked that
manifest, so the landing window should expect `untyped: ["instance.name"]` for a chief bundle built
from this tree.
## CLI
`mosaic name <instance> <name> [--business <id>]` calls `name.set` and prints `named <instance>
<name>`. `mosaic names [--business <id>] [--json]` prints `instance name` lines. Both are human only and
refuse inside an agent, like every other human verb.
## Tests
New `packages/bus/tests/names.test.mjs`, which reads the shipped `roles/chief-of-staff.json`:
- The chief's `name.set` succeeds, logs exactly one `action.allowed` for `instance.name` (class
within-role, target the instance) and records `by` as the chief's run.
- The human's write succeeds with `by` the human's name.
- A role without the grant refuses `not-authorized`, and so does one holding it cross-role; a decision
argument is `invalid-request`; the reader and tester refuse; nothing is written.
- `name-taken` for the human and the chief; `name-is-instance` for every instance key, `human` and
`tester`; `name-is-role` for a used definition, the chief's and a business-only one;
`invalid-request` for six bad names; `unknown-role` for an unknown instance. A rename frees the old
name. The store refuses a duplicate insert, an update and a delete on its own.
- Send by name delivers to the instance and records `to_role` and `to_name`; a send by key leaves
`to_name` null; the human and the tester send by name; an unknown name and a renamed-away name are
`unknown-role`.
- A name equal to an instance key, written straight into the store to stand in for an instance
added later, doesn't take that key's messages.
- Names equal to a used definition (`coder`) and a business-only one (`reviewer`), written the same
way, don't take sends to those words: each refuses `unknown-role` and neither instance receives
anything (sage-0590).
New `packages/bus/tests/name-migration.test.mjs`, modelled on spec-migration.test: a 3f store built
from `tests/fixtures/schema-3f.sql` and fixture rows, never the live store. It migrates to 3g, keeps
every old row and column, adds an empty `instance_names` and a null `to_name`, and leaves the
untouched copy unchanged.
Updated:
- comment-migration, thread-migration, spec-migration and chief-of-staff tests in the bus package,
where they pin `3f`. comment-migration's fixture-only future step moves from 3f→3g to 3g→3h.
thread-migration also compares whole message rows after its migration, and those now carry a
null `to_name`.
- `packages/bus/tests/schema.test.mjs` counts the tables and triggers in `schema.sql`: 15 and 67.
- `packages/business/tests/chief-of-staff.test.mjs` APPROVED list, and role.test, which pins the
shipped grants.
- `packages/business/tests/hard-waits.test.mjs`: `instance.name` is in ROUTED and not in GATED_ONLY.
- `packages/business/tests/chief-of-staff.test.mjs`'s classification test, which lists what the chief
holds.
- `role.test.mjs` asserts the chief holds `instance.name` and no other shipped role file holds it at
either class.
- New `packages/cli/tests/names.test.mjs`: `mosaic name` and `mosaic names` (text and `--json`), the
verb and arguments sent, `talk` by name reaching the instance, and the usage errors.
- `packages/harness/tests/tools.test.mjs` and `bundle.test.mjs`: the chief's untyped list.
## Kills
Each is a probe in `round.sh`: one sed edit, one test file, the named test must fail, and the file's
sha256 must match after the restore. Every edit was dry-run on copies; each applies once.
1. `kill-1-grant`: remove `instance.name` from `roles/chief-of-staff.json`. The chief's test in
bus `names.test.mjs` must fail (sage-0589).
2. `kill-2-resolution`: the lookup returns null in `message.send`. The send test must fail, alone.
`kill-2b-key-first`: drop the key-first check. The key-beats-name test must fail, alone.
`kill-2c-role-first`: drop the send-time role check. The role-beats-name test must fail, alone.
3. `kill-3a-name-is-instance` and `kill-3b-name-is-role`: each check becomes `if (false)`. The
uniqueness test must fail, alone.
4. `kill-4-not-authorized`: the refusal becomes `if (false)`. The refusal test must fail.
5. `kill-5-routed`: comment out ROUTED's `instance.name` in the vocabulary. The new routed pin in
business `hard-waits.test.mjs` must fail.
6. `kill-6-by`: `by` goes back to `s.human ?? s.run`. The chief's test must fail, alone, on `by`.
Added after the first run (see below).
## The round
`round.sh <worktree> <logdir>` runs, in order:
- singly: bus `names`, `name-migration`, `spec-migration`, `comment-migration`, `thread-migration`,
`schema`, `project-runtime`, `chief-of-staff`, `hard-waits`, `messaging` and `broker`; business `chief-of-staff`, `role` and
`hard-waits`; cli `names`, `app`, `verbs`, `follow` and `tester`; harness `tools` and `bundle`;
- the nine kills;
- the whole bus, business and harness suites, the cli suite without the two real-sandbox files, and
those two singly under the inherited TMPDIR.
It is a builder round (decision 136). Every `scripts/test-*.sh` belongs to the landing gate.
## Rebase and first run (sage-0603, sage-0608)
Row 150 landed as 0f7bca083 (pushed in 63c82f1c4..b0424e9cc). Three of the 24 manifest paths
changed between 4ebef2284 and b0424e9cc:
- `packages/bus/src/broker.mjs`: a comment in the `messages` case. The patch applied cleanly.
- `packages/cli/src/cli.mjs`: `inbox --read`. The patch applied cleanly; the USAGE has both new lines.
- `packages/cli/README.md`: one conflict. I kept row 150's sentence ("Console only reads history, so
a reply to a Console send stays unread in `inbox` until it is marked read.") and my `name` bullet
after it.
I applied `build.patch` with `git apply --3way` to a new worktree at b0424e9cc. The rebased
candidate was patch 2bc50492, manifest bd8d2b30. Only those three hashes differed from the
pre-rebase manifest. Copies: `pre-rebase/` holds the 4ebef2284 pair (f47f0eca, 1089500b), and
`rebase-1/` holds the rebased pair. Between b0424e9cc and 435d1c616 no manifest path changed.
The first run, on Sage's go (sage-0603), went from 2026-10-16T20:50:15Z to 20:50:16Z. It stopped
on the first file, bus `names.test.mjs`: 4 pass, 3 fail (`gate-run.log`, `gate/bus-names.log`).
None of the failures came from the rebase: row 150 changed no logic in these files. The tests had
never run before.
1. The key-first and role-first tests insert `instance_names` rows with `at='x'`. The store's
`#verifyTimes` refuses that (`invalid-timestamp`). The test was wrong. The rows now use
`2026-01-01T00:00:00.000Z`. So does the uniqueness test's direct insert. That test passed
because the trigger fires before the time check, but it shouldn't depend on that order.
2. The chief's test expected `by='chief-run'` and got `'0'`. I checked what `s.run` holds:
`bindLaunch` stores `record.run`, so for the test's chief it is `chief-run`. A launched session
has `human: false`, not null. `s.human ?? s.run` therefore gave `false`, which SQLite stored as
0. The code was wrong and the test was right. `by` is now `s.human || s.run`, as `#session`'s
`s.human || null` does. `kill-6-by` pins it.
While in that code I found that my `name.set` case sat between row 121's two-line comment and
the `project.create` case it describes. I moved the comment back above `project.create`.
The fix changes `packages/bus/src/broker.mjs` and `packages/bus/tests/names.test.mjs` only.
`round.sh` gains `kill-6-by`; I dry-ran its edit on a copy. Fixed candidate: `build.patch`
9c04f224, `candidate-manifest.sha256` e41d74dd, `round.sh` 1ccc4c0e.
## Second run and second rebase (sage-0609, sage-0610, sage-0615)
The second run, on sage-0609's go, used `build.patch` 9c04f224, manifest e41d74dd and `round.sh`
1ccc4c0e. It went from 2026-10-16T20:55:49Z to 20:55:56Z and stopped on bus
`thread-migration.test.mjs`: 2 pass, 1 fail (`gate-run.log`, `gate/`). The four bus files before it
passed. `rebase-1-run2/` keeps that run's patch, manifest and script.
The failing test compares every message row after its 3d→3e migration with the rows before it.
The store now migrates on to 3g, which adds `to_name`, so each migrated row has one more key. My
earlier edit to that test only changed the version strings. The test was wrong. It now compares
the migrated store with the old rows plus `to_name: null`, and the untouched copy with the old
rows as they were.
Before asking for the slot again I looked for other tests the 3g schema breaks without having run:
- Tests that compare whole message rows. The others pick fields, so a new column doesn't change
them. That includes row 137's new `land.test.mjs`.
- `schema.test.mjs` counts the tables and triggers in `schema.sql`. `instance_names` adds one table
and four triggers, so the counts go from 14 and 63 to 15 and 67. The test was wrong, and it joins
the candidate as its 25th file and the round's single bus files.
- Every `INSERT INTO messages` names its columns, row 137's new one included, so the new column
breaks none of them.
Row 137 landed as f1ee9a989, pushed in b0424e9cc..18aee8fa1. It changed seven of the candidate's
paths. I kept the b0424e9cc candidate with both fixes in `pre-rebase-2/` (patch a5493068) and
applied it with `git apply --3way --index` to a new worktree at 18aee8fa1.
- `packages/bus/src/broker.mjs`: one conflict. Both rows add an import after `requirements.mjs`.
I kept row 137's `landing.mjs` import and put mine, `ID_PATTERN` from the business vocabulary,
after it. Every probe's edit text still appears once in the rebased file. `#classification` and
`#consumeAuthority`, which `name.set` calls, keep their signatures; row 137 split
`#checkAuthority` and added `#authorityFor` without changing either.
- `packages/bus/README.md`, `packages/cli/README.md` and `packages/harness/README.md`: applied
without conflict. The verb rows, the CLI lines and the harness paragraph sit where they did.
- `packages/harness/src/tools.mjs`, `tests/tools.test.mjs` and `tests/bundle.test.mjs`: applied
without conflict. Row 137 adds `land_work` under a new `land` option and tests for it. The
untyped pins still mean what row 141 intends. `untypedActions` lists the actions a role holds
that no tool covers. The chief's role file holds no `git.merge.protected`, so `land_work` can
neither add to nor take from the chief's list, and the chief bundle test builds without `land`.
Both pins stay `["instance.name"]`.
Between 18aee8fa1 and 52f35e7a0, the next commit, no candidate path changed. The patch applies to
the canonical checkout at 52f35e7a0 (`git apply --check`). Rebased candidate: `build.patch`
0149754c, `candidate-manifest.sha256` 4d500898, `round.sh` 3f6500c8.
## Third run (sage-0626)
On sage-0626's go, the /proc check and `busy.sh` were clear at 2026-10-16T21:57:39Z. The chain was
`round.sh`, then a full gate (`~/darkwing-scratch/r141/gate.sh`, adapted from row 137's: every
package suite except queue's, cli's two real-sandbox files apart, then every `scripts/test-*.sh`).
Two mistakes of mine:
- `round.sh` had lost its execute bit, so the round never started ("Permission denied").
- I chained the two with `| tee ... &&` without pipefail, so the gate started anyway.
The gate stopped at 21:57:53Z on its first suite, bus: 324 pass, 1 fail
(`gate-r3/pkg-bus.log`; `gate-run3.log` holds the round's one line, `gate-r3/gate-run.log` the gate's). `project-runtime.test.mjs` pins the broker's
exact import list, "no new imports", because the broker runs in the sandbox from `RUNNER_FILES`.
My `ID_PATTERN` import from `packages/business/src/vocabulary.mjs` was the extra line. The round's
whole bus suite would have caught this on either earlier run had they got that far, and my
pre-run audit of tests the candidate breaks missed it, because I looked for row and schema pins,
not source-text pins.
Widened pin: `project-runtime.test.mjs`'s import list gains one line. The reason is that the import
keeps the closure. `vocabulary.mjs` has been in `RUNNER_FILES` since 57948a81f and imports
nothing, so the sandbox loads no file it didn't load before. The update follows the one row 137
made for `landing.mjs`: my line, with a comment, and a new assertion that `vocabulary.mjs`
imports nothing, so a later import there fails this test instead of the sandbox (sage-0630). The test joins the candidate as
its 26th file and the round's single bus files. A narrower audit (tests that `readFileSync` a
touched source, and whole-list pins of ACTIONS, ROUTED or the schema hash) found no other
candidate. Every other hit is in the candidate already or inserts with named columns.
The sweep found nothing at 21:58:16Z, and no darkwing network existed. `rebase-2-run3/` keeps the
stopped candidate (0149754c, 4d500898, 3f6500c8). Fixed candidate: `build.patch` bb036c6d,
`candidate-manifest.sha256` d474b330, `round.sh` 570524c0. It applies to the canonical checkout at
862af3079. Everything after 18aee8fa1 is docs: nothing under `packages/` or `scripts/`.
## Fourth run (sage-0630)
`chain.sh` now runs under `set -uo pipefail` and gates each stage on its exit status: the /proc
check, `busy.sh`, `bash round.sh` (rc 0 and a `ROUND_PASSED` line), then `gate.sh` (`GATE_PASSED`).
The first start refused on `busy.sh` at 22:00:09Z. One `node --test` process, PID 3127134, had its
cwd in Dewey's r148r3 scratch, and it was gone when I looked again. I sampled `busy.sh` every 2 s
for 60 s and got 0 hits in 30 samples, then started again.
Slot start 2026-10-16T22:03:11Z, base 18aee8fa1. The /proc check found no other `node --test`.
The round (`gate-run-r4.log`, `gate-r4/`):
- Bus files singly: names 7, name-migration 1, spec-migration 1, comment-migration 5,
thread-migration 3, schema 1, project-runtime 3, chief-of-staff 13, hard-waits 12, messaging 13,
broker 16. Business: chief-of-staff 10, role 14, hard-waits 4. Cli: names 2, app 30, verbs 9,
follow 6, tester 7. Harness: tools 19, bundle 22. All 0 fail.
- Kills: all nine killed. 1-grant (2 failing), 2-resolution, 2b-key-first, 2c-role-first,
3a-name-is-instance, 3b-name-is-role, 4-not-authorized, 6-by, 5-routed (2 failing).
- Whole suites: bus 325, business 113, harness 108, cli 203, cli launcher 19, cli thread-launch 8,
all 0 fail. `ROUND_PASSED` 22:05:14Z.
Full gate (`gate-r4-full/`; the whole chain is in `chain-r4.log`), 22:05:14Z to 22:11:12Z, every
line 0 fail:
| Suite | Pass |
|---|---|
| pkg-bus | 325 |
| pkg-business | 113 |
| pkg-cli | 203 |
| pkg-cli-launcher | 19 |
| pkg-cli-thread-launch | 8 |
| pkg-control-board | 124 |
| pkg-conversation | 182 |
| pkg-discord | 183 |
| pkg-harness | 108 |
| pkg-ledger | 86 |
| pkg-mosaic | 69 |
| pkg-plugins | 28 |
| pkg-project | 30 |
| pkg-runs | 43 |
| pkg-seat | 150 |
| pkg-tasks | 97 |
| pkg-webui | 113 |
| script-auth | 15 |
| script-conductor | 17 |
| script-config | 24 |
| script-discord | 78 |
| script-extension-package | 18 |
| script-foundation | 44 |
| script-land-work | 86 |
| script-queue | 156 node, 28 suite |
| script-release | 14 |
| script-task | 98 |
`GATE_PASSED` and `CHAIN_PASSED` 22:11:12Z. The gate's trap removed `darkwing-r141_default`. No
flake. The sweep at 22:11:22Z found 0 processes, no darkwing network and no `*.orig`. The candidate
still matches `candidate-manifest.sha256` d474b330 (`sha256sum -c`), and the staged diff is still
`build.patch` bb036c6d. None of the 26 paths changed between 18aee8fa1 and 862af3079
(origin/refactor) or e1e8dda0b (local HEAD).
## For row 155 and follow-ups
- Row 155's Problem list, for Sage to carry: row 155 must give the chief a tool for `instance.name`
and bring `untypedActions(chief)` and the chief bundle's `untyped` back to `[]`, re-pinning
`packages/harness/tests/tools.test.mjs:43` and `bundle.test.mjs:94` (sage-0590). Earlier copies
of this line said 42 and 92, which were the base's lines, not the candidate's.
- Bounded follow-up, for Sage to open: whoever adds instances or definitions to a business should
refuse a new key or definition equal to a current name, or report the name it shadows. Today the
key or role wins and the name stops resolving, which is the safe side. Row 155 excludes adding
instances, so it doesn't carry this (sage-0590).
## Commit message
The commit message will say: "`instance.name` is routed under REQ-MSG-2 (1.5)", as REQ-WAIT-3's second
sentence requires.
## Open
Filbert's review on #1635. The landing gets its own window on Mos's go. The live stack is
52f35e7a0 at schema 3f, so the 3f→3g migration window plan names 52f35e7a0 as OLD (sage-0626).