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.
595 lines
35 KiB
Markdown
595 lines
35 KiB
Markdown
# Row 165 (#1662): the human adds and changes role instances
|
|
|
|
Owner Darkwing. Reviewers Dewey (security) and Filbert. Gate owner Sage.
|
|
Brief `docs/plans/2026-10-18_role-instance-verb.md`, first at blob 0aeb5f5adb66, corrected in
|
|
b8036b608 to blob b3cf580a (rev 1873, mos-0823). Built on b8036b608, rebased onto 70c9131f0
|
|
before the slot run (sage-0939, sage-0941).
|
|
Claimed 2026-10-18T21:25Z (rev 1871).
|
|
|
|
## Rebase onto 70c9131f0 (sage-0939)
|
|
|
|
Row 154 landed as 9353675d1 and moved origin/refactor to 70c9131f0. Four of my manifest paths
|
|
changed in b8036b608..70c9131f0: `docs/TOOLS.md`, `packages/webui/README.md`,
|
|
`packages/webui/src/public/bus.js` and `packages/webui/src/serve.mjs`. I took the whole diff
|
|
against b8036b608 and applied it with `git apply --3way` in a fresh worktree at 70c9131f0. All 16
|
|
changed files applied cleanly, and I copied the 7 new files over unchanged.
|
|
|
|
The `public/` diff against the new base is `public-70c9131f0.diff`. Its +/- lines, hunk headers
|
|
and context match `public.diff`. Only the `index` line differs (40c6b6758..b2bb52d0a, where it
|
|
was 1e0be849d..67abd41f6). Sage ruled (sage-0941) that mos-0826 covers the +/- lines, so no new
|
|
delta goes to Mos. To make the match checkable, each digest below is
|
|
`grep '^[+-]' FILE | grep -vE '^(\+\+\+|---) ' | sha256sum`:
|
|
|
|
| Diff | Whole-file sha256 | +/- lines | +/- digest |
|
|
|---|---|---|---|
|
|
| `public.diff` (on b8036b608) | `e5e3435b542fbbe2f69c64ac05ea200c02adc606a1c41020341620ddc19d13e0` | 55 | `04856a57c70cd6d96d33fd43aa285a17c0bb52a596c643145a52bf52c93db4e4` |
|
|
| `public-70c9131f0.diff` (on 70c9131f0) | `ee57b150ca92664bb6a62bc9874d26340710a6ac2e5fc703eceba84505f48f96` | 55 | `04856a57c70cd6d96d33fd43aa285a17c0bb52a596c643145a52bf52c93db4e4` |
|
|
|
|
On 70c9131f0, `human.mjs`, `human-cli.mjs`, `cli/src/transport.mjs`, `harness/src/tools.mjs`,
|
|
`stack-release.sh` and `stack-copy.sh` are byte-equal to b8036b608 and to the candidate. The
|
|
channel-proof pin `dfbc2289...9a4c` matches the rebased `webui/src/bus.mjs`. The 64-hex grep also
|
|
hit `webui/tests/service.test.mjs` and `bus/tests/thread-migration.test.mjs`, but those strings
|
|
are a fixture UID and a DDL hash, not pins on manifest files. Both tests run in singles. The
|
|
28-mutant dry run anchors on the new base.
|
|
|
|
Stack scripts: this row doesn't touch `scripts/stack-release.sh`, `scripts/stack-copy.sh` or the
|
|
8e7ea89a1 re-pin target. If that changes, Sage hears before the review request (sage-0918).
|
|
|
|
## Paths beyond the brief's list (sage-0918)
|
|
|
|
Sage approved the expansion at 21:30:34Z. The brief lists the writer, its test, the CLI command and
|
|
the Console section. Everything below is beyond that list:
|
|
|
|
- `packages/bus/src/broker.mjs`: the `instances` and `instances.set` verbs, human-only, with
|
|
`INSTANCE_KEYS`. Neither is in `ORGANIZATION_KEYS`;
|
|
- `packages/bus/src/runtime.mjs`: routes the two verbs to `instanceSettings`;
|
|
- `packages/bus/tests/instances-verb.test.mjs` (new);
|
|
- `packages/bus/tests/channel-proof.test.mjs`: the `webui/src/bus.mjs` pin, below;
|
|
- `packages/harness/tests/tools.test.mjs`: proves no tool reaches the two verbs;
|
|
- `packages/cli/src/cli.mjs`: routes `business instances` and `business instance` to the new
|
|
`packages/cli/src/instances.mjs`;
|
|
- `packages/webui/src/cli.mjs`: the wiring, under `--send` only;
|
|
- `packages/webui/src/bus.mjs`: seven STATUS codes, below;
|
|
- `packages/webui/src/serve.mjs`: `GET /api/bus/instances` and `POST /api/instances`;
|
|
- `packages/webui/src/public/bus.js`: the Settings section (decision 123, below);
|
|
- `packages/webui/tests/instances.test.mjs` and `instances-browser.test.mjs` (new);
|
|
- `packages/webui/tests/shell.test.mjs`: its pinned list of bus.js POST fetches gets the
|
|
`/api/instances` write, and the `method:` count goes to 6. I missed it in the reader grep. It
|
|
reads bus.js's text, not a 64-hex pin. Slot run 1 caught it (see below);
|
|
- `scripts/mosaic`: sends `business instances` and `business instance` to packages/cli;
|
|
- `packages/bus/README.md`, `packages/business/README.md`, `packages/cli/README.md`,
|
|
`packages/webui/README.md`;
|
|
- `docs/TOOLS.md` and `docs/acceptance/314.md`, which the brief assigns.
|
|
|
|
Untouched: `scripts/stack-release.sh`, `scripts/stack-copy.sh` and the 8e7ea89a1 re-pin target
|
|
(`git diff --stat b8036b608` on the two scripts is empty).
|
|
|
|
## The seven STATUS codes and the channel-proof pin
|
|
|
|
`packages/webui/src/bus.mjs` maps the writer's new codes to HTTP:
|
|
|
|
| Code | Status |
|
|
|---|---|
|
|
| `organization-instance-exists` | 409 |
|
|
| `organization-unknown-instance` | 400 |
|
|
| `organization-credentials-out-of-scope` | 400 |
|
|
| `organization-launch-not-configured` | 400 |
|
|
| `organization-no-change` | 400 |
|
|
| `organization-project-mismatch` | 400 |
|
|
| `organization-unresolvable-instance` | 400 |
|
|
|
|
`human-required` (403), `revision-conflict` (409), `organization-definition-refused` (400),
|
|
`unknown-model` (400) and `invalid-request` were already there. The pin moves in its own commented
|
|
hunk:
|
|
|
|
```diff
|
|
- "packages/webui/src/bus.mjs": "d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241", // Row 155 (#1649): eight STATUS entries for the assistant routes, no proof or transport change
|
|
+ // Row 165 (#1662): seven STATUS entries for the role instance routes (organization-instance-exists,
|
|
+ // organization-unknown-instance, organization-credentials-out-of-scope, organization-launch-not-configured,
|
|
+ // organization-no-change, organization-project-mismatch, organization-unresolvable-instance), no proof or
|
|
+ // transport change. Row 155 (#1649) added eight for the assistant routes.
|
|
+ "packages/webui/src/bus.mjs": "dfbc2289337376b94a399a61599fd4af402b802deb2267dcffc5cf23502c9a4c",
|
|
```
|
|
|
|
The other three frozen files are byte-equal to b8036b608 (`git diff --quiet` exits 0 on each):
|
|
`packages/bus/src/human.mjs`, `packages/bus/src/human-cli.mjs` and
|
|
`packages/cli/src/transport.mjs`. `packages/harness/src/tools.mjs` is unchanged too.
|
|
|
|
## The public/ diff (decision 123)
|
|
|
|
Nothing in `public/` is generated, so there is no separate handwritten-only diff.
|
|
|
|
| File | sha256 | Lines |
|
|
|---|---|---|
|
|
| `public.diff` (`git diff b8036b608 -- packages/webui/src/public/`, bus.js +54/-1) | `e5e3435b542fbbe2f69c64ac05ea200c02adc606a1c41020341620ddc19d13e0` | 76 |
|
|
| `public-delta-cb79be2e.diff` (the change from the first diff) | `ff2cbf6bc80a60c24fee374f2e14e9caa5c9619fd4d0a26ae722f54bb324adfe` | 42 |
|
|
|
|
The first diff was `cb79be2eaa010e0974d64a38beafb5b8bc55a933e83dad7945cb7c0c4aaa5c8d` (74 lines).
|
|
Mos gave a conditional go on it (mos-0825) with one finding to fix and one note:
|
|
|
|
- F1: the page took a 200 answer as its next view after checking only the revision and
|
|
`restart`, so an answer without `definitions` would throw in the next render. The page now checks
|
|
the whole shape (`instanceAnswer`: a 64-hex revision, a boolean `restart`, and `instances` and
|
|
`definitions` as non-array objects). A 200 that fails it keeps the view and the draft and says
|
|
"Outcome unknown. Read the role instances again before trying. Draft kept." `serve.mjs` makes
|
|
the same check on the bus answer and refuses with 503 `outcome-unknown`, `sent: "unknown"`. Sage
|
|
asked for both (sage-0931): the page doesn't trust the server's shape.
|
|
- F2: the restart text named a seat. Sage folded it in (sage-0931). The page now says "Restart
|
|
needed: the running bus keeps the roles it loaded at start. The Console does not restart it."
|
|
and the CLI says "restart needed: the running bus keeps the roles it loaded at start; this
|
|
command does not restart it".
|
|
|
|
Mos's go on e5e3435b is mos-0826. It replaces the conditional go. It holds while the slot run is
|
|
green (28 mutants included), the reviewers cover the writer's checks and the new 503 on a bad
|
|
answer shape, and the +/- lines are unchanged at landing. Any later `public/` change goes back to
|
|
Mos as a delta against e5e3435b.
|
|
|
|
### The warn line and mos-0832 (current go)
|
|
|
|
Slot run 1 on 70c9131f0 found that `views.test.mjs` waits for Settings' notifier empty state as
|
|
`#bus-view .empty b`. When the instances read failed, my section drew `empty()` above
|
|
Notifications, so the wait matched my box. On this page `empty()` is for a view with nothing in
|
|
it. A section whose read failed shows a `<p class="small warn">` line (bus.js 268 and 284). Line
|
|
683 now follows that convention:
|
|
|
|
```
|
|
- if(!view)return `...${title}${empty('The role instances could not be read.','They are read from the bus host with your identity.')}</section>`;
|
|
+ if(!view)return `...${title}<p class="small warn">The role instances could not be read, so there is nothing to add or change. Read again to retry; they come from the bus host with your identity.</p></section>`;
|
|
```
|
|
|
|
`instances-browser.test.mjs` reloads Settings with the read failing. It checks the exact warn text,
|
|
and that the section has no `.empty` box and no instance form. Mutant G5 puts the old line back.
|
|
|
|
| File | sha256 | Lines | +/- digest |
|
|
|---|---|---|---|
|
|
| `public-70c9131f0-warn.diff` (the candidate's whole `public/` diff, bus.js +54/-1) | `4181b7f649346aa9bb166cd5597e6474fa7011e5f1f9adb1780c0586c648f7a7` | 76 | `939ecd28f80bc2f4a36a9468be0ccc2b0f08c8a630508060572491357f1f9444` |
|
|
| `public-delta-e5e3435b.diff` (the one-line change) | `fa1012afba5651abaa50b38a304696469a797db485ff0383bf16df5a6f5cd176` | 11 | |
|
|
|
|
Mos gave the decision 123 go on 4181b7f6 in mos-0832. It replaces the go on e5e3435b, and the
|
|
mos-0826 conditions still apply:
|
|
1. a green slot run, with the singles and all 29 mutants recorded here;
|
|
2. reviewer approvals that cover the server-side writer and the 503 `outcome-unknown`;
|
|
3. the +/- digest is still 939ecd28 at landing.
|
|
|
|
Any later `public/` change goes back to Mos as a delta against 4181b7f6.
|
|
|
|
`ORGANIZATION_KEYS` already has an `instances` key. It is row 125's model selection, which sets
|
|
`vars.model` on an existing instance and nothing else. The new verb shares the word and nothing
|
|
more: its args use the singular `instance`.
|
|
|
|
## The credential refusal (mos-0823)
|
|
|
|
The code is `organization-credentials-out-of-scope`. In the business vocabulary a role's
|
|
credentials are what `credentials` binds, and binding them stays a hand edit in this row. The
|
|
`organization-` prefix lets the broker pass the code through unchanged (`broker.mjs`, the
|
|
organization branch's error mapping), so the CLI and the Console show it by name.
|
|
|
|
The writer has three behaviors here, each with its own test and its own mutant:
|
|
|
|
1. An add whose definition declares credentials is refused, and the message names the services:
|
|
`reviewer declares gitea and vikunja credentials; binding them is a hand edit`. The same
|
|
refusal covers a change of definition to such a definition. The file's bytes don't change.
|
|
2. The writer never writes an instance without its credential binding. A change away from a
|
|
definition that declares credentials is refused too, with
|
|
`<instance> holds <definition> credentials; moving them to another definition is a hand edit`.
|
|
`checkCredentialMap` would refuse most of these anyway. The writer's own check names the reason
|
|
instead of a validation message.
|
|
3. A vars or launch change on an instance that holds its binding succeeds, and `credentials` and
|
|
`tracker` come back unchanged.
|
|
|
|
The other codes, all with the `organization-` prefix:
|
|
|
|
| Code | When |
|
|
|---|---|
|
|
| `organization-instance-exists` | `add` names an instance already in `roles` |
|
|
| `organization-unknown-instance` | `change` names an instance not in `roles` |
|
|
| `organization-definition-refused` | the old or new definition is in `END_PROTECTED`, or the instance is protected |
|
|
| `organization-launch-not-configured` | `launch` given and the file has no `launch` block |
|
|
| `organization-no-change` | the result is the same document |
|
|
| `organization-project-mismatch` | a project file's id differs from its key |
|
|
| `organization-unresolvable-instance` | `resolveInstance` refuses an instance for a project |
|
|
|
|
A stale revision is `revision-conflict`. A bad shape and a document `validate` would refuse are
|
|
`invalid-request`, and an instance whose model isn't in `launch.models` is `unknown-model`.
|
|
|
|
## The restart report (sage-0918 point 3)
|
|
|
|
The writer's answer carries `restart: true` after an add or a definition change. The CLI prints
|
|
`restart needed: the running bus keeps the roles it loaded at start; this command does not restart
|
|
it`. The Console receipt says "Restart needed: the running bus keeps the roles it loaded at start.
|
|
The Console does not restart it." Product text names no seat (sage-0931). A vars or launch change
|
|
prints `no restart: the launcher reads the change at its next launch check`, and the page says the
|
|
same. Neither surface offers to run a restart, and the tests assert that: the CLI test checks the
|
|
exact string and that it doesn't match `Mos` or `Jason`, and the page test checks there is no button
|
|
that mentions a restart.
|
|
|
|
The restart itself needs Mos's go or Jason's hand. TOOLS.md, acceptance 314 and the bus and cli
|
|
READMEs say so as policy. No test reads TOOLS.md, acceptance 314 or the bus README.
|
|
|
|
## The REASONS tables
|
|
|
|
The CLI (`packages/cli/src/instances.mjs`, `REASONS`) and the Console (`serve.mjs`,
|
|
`INSTANCE_REASONS`) each keep a reason per code, worded for their own surface: the CLI names
|
|
`mosaic business instances` and the Console names the read. A code without a reason prints
|
|
`refused by the bus`. A refusal is `<code>: <reason>. Nothing changed.` and the page adds
|
|
"Draft kept.". Mutants W6 and L2 drop the lookups.
|
|
|
|
## Tests that read a manifest path (sage-0918 point 1)
|
|
|
|
I grepped every `packages/*/tests/*.mjs`, `scripts/test-*.sh` and `scripts/foundation/*.test.mjs`.
|
|
For each manifest path the grep looked for the repo path, a `../../<pkg>/<path>` import and a
|
|
`../<path>` import. Hits, by path:
|
|
|
|
| Path | Test files |
|
|
|---|---|
|
|
| `packages/bus/src/broker.mjs` | 60 (bus, cli, harness, seat, tasks via `world.mjs`, webui) |
|
|
| `packages/bus/src/runtime.mjs` | 16 |
|
|
| `packages/cli/src/cli.mjs` | 27 |
|
|
| `packages/cli/src/instances.mjs` | 1, `cli/tests/instances.test.mjs` |
|
|
| `packages/business/src/instance-settings.mjs` | 1, `business/tests/instance-settings.test.mjs` |
|
|
| `packages/webui/src/bus.mjs` | 12, among them the `channel-proof.test.mjs` pin, the only 64-hex pin on a manifest path |
|
|
| `packages/webui/src/cli.mjs` | 29, among them `cli/tests/stack-copy.test.mjs` and `stack-release.test.mjs` |
|
|
| `packages/webui/src/serve.mjs` | 33 |
|
|
| `packages/webui/src/public/bus.js` | 0 by repo path. The browser tests load it through the server, and `webui/tests/shell.test.mjs` reads it as `read('bus.js')`, which the path grep missed |
|
|
| `scripts/mosaic` | 16, among them `scripts/test-config.sh`, `test-queue.sh` and `test-task.sh` |
|
|
| `packages/cli/README.md` | `cli/tests/host.test.mjs` (the unit file's name must appear) |
|
|
| `packages/webui/README.md` | `webui/tests/bus-browser.test.mjs` (row 61's refused field table wording) |
|
|
| `packages/bus/README.md`, `packages/business/README.md`, `docs/TOOLS.md`, `docs/acceptance/314.md` | none |
|
|
|
|
The README hits correct my earlier note that no test reads the READMEs: findings on the cli and
|
|
webui READMEs aren't doc-only. A finding on the bus or business README, TOOLS.md or acceptance 314
|
|
is doc-only (decisions 136/137).
|
|
|
|
Some hits came from a package's own `../src/serve.mjs` or `../src/cli.mjs` (control-board,
|
|
discord) or from `.mosaic` in `scripts/foundation/cli.test.mjs`. I read each one, and none reads a
|
|
manifest path. Every real reader is in `singles.sh`: each `*.test.mjs` in bus, business, cli and
|
|
webui (143 files), harness `runner` and `tools`, seat `sandbox` and `session`, the eight tasks tests
|
|
that import `world.mjs`, queue `dispatch`, runs `task-cli`, then `scripts/test-config.sh`,
|
|
`test-queue.sh` and `test-task.sh`. Each file runs singly from its package directory.
|
|
|
|
## Mutants
|
|
|
|
There are 29, each paired with the tests that should kill it. The second column says what the
|
|
mutant changes:
|
|
|
|
| Id | Change | Tests | Result |
|
|
|---|---|---|---|
|
|
| K1 | the broker's `ORGANIZATION_KEYS` gets the instance keys | bus, harness tools | killed |
|
|
| K2 | all three key lists get them | bus, harness tools | killed |
|
|
| K3 | `change_organization` sends `instances.set` | harness tools | killed |
|
|
| H1 | the human-required check skips the `instances` verbs | bus | killed |
|
|
| H2 | the broker's `INSTANCE_KEYS` gets `holder` | bus | killed |
|
|
| C1 | the add credentials check is off (mos-0823 behavior 1) | business, bus, cli, webui | killed |
|
|
| C2 | the old-definition credentials check is off (behavior 2) | business, bus, cli, webui | killed |
|
|
| C3 | a vars change drops the binding (behavior 3) | business, cli | killed |
|
|
| P1 | `definitionFor` skips `END_PROTECTED` | business, bus | killed |
|
|
| P2 | a protected instance can change | business | killed |
|
|
| V1 | no validation of the next document | business | killed |
|
|
| V2 | no `no-change` check | business | killed |
|
|
| R1 | the write takes the current revision, not the caller's | business, bus, cli, webui | killed |
|
|
| A1 | the actor is `system` | business, bus, cli, webui | killed |
|
|
| S1 | `restart` is always false | business, bus, cli, webui | killed |
|
|
| W1 | the route ignores `--send` | webui | killed |
|
|
| W2 | no Origin check | webui | killed |
|
|
| W3 | no Sec-Fetch-Site check | webui | killed |
|
|
| W4 | the Console's `INSTANCE_KEYS` gets `holder` | webui | killed |
|
|
| W5 | the body limit is 64 KiB | webui | killed |
|
|
| W6 | the Console drops the reason | webui | killed |
|
|
| W7 | the Console passes an answer without `definitions` (F1) | webui | killed |
|
|
| G1 | the page never names a restart | page | killed |
|
|
| G2 | a refusal drops the draft | page | killed |
|
|
| G3 | the page offers definitions that declare credentials | page | killed |
|
|
| G4 | the page takes a 200 without `definitions` as its view (F1) | page | killed |
|
|
| G5 | an unread instances view draws the page's empty state, not a section warning | page | killed |
|
|
| L1 | the CLI never names a restart | cli | killed |
|
|
| L2 | the CLI drops the reason | cli | killed |
|
|
|
|
`DRY=1` finds every anchor exactly once (`DRY 29 mutants ok`). The results above are from mutants
|
|
run 2 on wt2, `out/mutants-20261018T224704Z`: 29 killed, none survived.
|
|
|
|
## Slot run (sage-0939)
|
|
|
|
Sage gave the slot at 22:18:52Z after Dewey cleared. Everything ran in
|
|
`~/darkwing-scratch/r165/wt2` at 70c9131f0 plus the candidate, with `COMPOSE_PROJECT_NAME=darkwing-r165`,
|
|
pipefail, and the counts summed with awk over the logs.
|
|
|
|
| Run | Output dir under `~/darkwing-scratch/r165/out/` | Result |
|
|
|---|---|---|
|
|
| Singles run 1 | `singles-20261018T222149Z` | pass 1417, fail 2 |
|
|
| Singles run 2 | `singles-20261018T223459Z` | pass 1419, fail 0 over 163 logs, overall green |
|
|
| Mutants run 1 | `mutants-20261018T224446Z` | 28 of 29 killed, W1 survived |
|
|
| W1 alone | `mutants-20261018T224658Z` | killed |
|
|
| Mutants run 2 | `mutants-20261018T224704Z` | 29 of 29 killed |
|
|
|
|
Singles run 2 includes `scripts/test-config.sh`, `test-queue.sh` and `test-task.sh`, which passed
|
|
24, 28 and 98.
|
|
|
|
Singles run 1 had two real failures, neither a flake or board-held:
|
|
|
|
- `shell.test.mjs` pins bus.js's POST fetches, and the new `/api/instances` write wasn't in its
|
|
list. Fixed in the test (above).
|
|
- `views.test.mjs` matched `#bus-view .empty b` against my instances empty state. The page now
|
|
uses the section-warning convention, which is the `public/` change Mos approved in mos-0832.
|
|
The new last step in `instances-browser.test.mjs` and mutant G5 cover it.
|
|
|
|
W1 survived mutants run 1 because of a test gap. In `webui/tests/instances.test.mjs`, the
|
|
send-off server also had no writer, so a route that skipped `!send` still refused on
|
|
`!instancesWrite`. The test now starts a third server with a writer and send off, and expects 403
|
|
and no write. That file passed singly (5 pass, 0 fail, `out/fix3-webui-instances.log`), W1 alone
|
|
was killed, and mutants run 2 killed all 29. The change is test-only, so the `public/` +/- digest
|
|
is still `939ecd28...9444` (mos-0832 condition 3).
|
|
|
|
After the runs, the /proc sweep found 0 of my processes, and the `darkwing-r165_default` network
|
|
is gone with 0 containers.
|
|
|
|
## Round 2 (Dewey 29037, Filbert 29036)
|
|
|
|
Filbert approved round 1 with three notes. Dewey asked for changes on F1 and F2. Round 2 changes
|
|
7 files against the round 1 candidate and adds none. `webui/src/bus.mjs` is unchanged, so the
|
|
channel-proof pin stays put. The `public/` +/- digest is still `939ecd28...9444`, so mos-0832
|
|
still covers the page.
|
|
|
|
**Dewey F1.** The writer took `human` and `tester` as instance ids. Both match ID_PATTERN, and
|
|
`validateBusinessDocument` only runs `requireId`. The broker refuses a business whose roles use
|
|
either name when it loads (`broker.mjs`, pinned by `bus/tests/tester.test.mjs`), so an add said
|
|
"Restart needed" and the next bus start failed closed. `instance-settings.mjs` now refuses both
|
|
names with `invalid-request`, before it reads the file. The business test covers each name, and
|
|
mutant F1 removes the check. I chose `invalid-request` over a new code because a new code needs
|
|
REASONS, INSTANCE_REASONS and a STATUS entry, and that moves the channel-proof pin. The cost is
|
|
that the human sees `invalid-request` without the writer's message, because the broker drops it
|
|
(Dewey N3, Filbert note 1). I left the rule out of `validateBusinessDocument`, since that would
|
|
change what every business file accepts. That's a follow-up for Sage to place.
|
|
|
|
**Dewey F2.** The route's 503 shape check had a test only for `definitions`. The table in
|
|
`webui/tests/instances.test.mjs` now has three more cases, each wrong in one part:
|
|
`revision: 'x'`, `restart: 'yes'`, and `instances: {}`. Mutants F2a, F2b and F2c each drop one
|
|
check. I also took the two optional GET cases. An answer without `instances` or without
|
|
`definitions` gets 502, and mutants E1 and E2 drop each check. These are test changes only.
|
|
|
|
**Filbert note 2.** The `organization-definition-refused` reason said "an assistant instance
|
|
changes only through ...", which reads wrong for an add or change that names the assistant
|
|
definition. Both tables now say the command or form "neither adds nor changes an assistant, and
|
|
moves no instance to the assistant definition". The CLI test and a real-broker case in the
|
|
webui route test pin the new wording. Mutants N2a and N2b put the old wording back.
|
|
|
|
**Filbert note 3.** The route test already has a revision-conflict case through a real broker,
|
|
at `webui/tests/instances.test.mjs:90`. Nothing changed for it.
|
|
|
|
**Follow-ups, not in this row.** Dewey N1: check a stale revision before anything else. Dewey
|
|
N2: a new instance key can take the name of a held seat. Dewey N3 and Filbert note 1: the broker
|
|
drops the writer's message, so a refusal reaches the human as its code alone.
|
|
|
|
The business README has a new bullet under "Role instances" for the `human`/`tester` refusal.
|
|
|
|
### Round 2 slot run (sage-0950)
|
|
|
|
`~/darkwing-scratch/r165/wt2` at 70c9131f0 plus the round 2 candidate, with
|
|
`COMPOSE_PROJECT_NAME=darkwing-r165`.
|
|
|
|
| Run | Result |
|
|
|---|---|
|
|
| singles, 163 files, 23:12:51 to 23:22:25 | every rc 0; awk sum over the 163 logs pass 1419, fail 0 |
|
|
| `mutants.py`, 37 mutants | 37 killed, none survived |
|
|
|
|
Logs: `~/darkwing-scratch/r165/out/singles-20261018T231251Z` and
|
|
`~/darkwing-scratch/r165/out/mutants-20261018T232237Z`. After the mutants wt2 matched the candidate
|
|
manifest, the sweep found no process and no darkwing network was left.
|
|
|
|
Dewey F2's three 503 checks, all in `serve.mjs:314`, each with its own mutant that
|
|
`webui/tests/instances.test.mjs` kills (rc 1, 1 fail):
|
|
|
|
| Mutant | Check it removes | Case that kills it |
|
|
|---|---|---|
|
|
| F2a | `!/^[a-f0-9]{64}$/.test(result?.revision ?? '')` | `revision: 'x'` |
|
|
| F2b | `typeof result.restart !== 'boolean'` | `restart: 'yes'` |
|
|
| F2c | `!result.instances[instance]` | `instances: {}` |
|
|
| E1 | GET 502, `serve.mjs:91`: `!value.instances \|\| typeof value.instances !== 'object'` | an answer without `instances` |
|
|
| E2 | GET 502, `serve.mjs:91`: `!value.definitions \|\| typeof value.definitions !== 'object'` | an answer without `definitions` |
|
|
|
|
F1 (the human/tester refusal) dies in `instance-settings.test.mjs`. N2a and N2b (the old wording in
|
|
the CLI and the Console) die in `instances.test.mjs`.
|
|
|
|
## Round 3: rebase onto 4f45cadc0 (sage-0957, sage-0958)
|
|
|
|
Row 156 landed as 4f45cadc0, and its paths include 4 of this row's 24: `docs/TOOLS.md`,
|
|
`packages/bus/src/broker.mjs`, `packages/bus/src/runtime.mjs` and `packages/cli/README.md`. The
|
|
round 3 worktree is `~/darkwing-scratch/r165/wt3`, a fresh detached worktree at 4f45cadc0 with
|
|
round 2's `build.patch` (`cc886c47...`) applied. `git apply --check` was clean and `git apply`
|
|
needed no fuzz or 3-way merge. No round 2 line changes.
|
|
|
|
Per path, against round 2:
|
|
|
|
| Path | Result |
|
|
|---|---|
|
|
| the 20 paths row 156 doesn't touch | byte-equal to wt2 |
|
|
| `docs/TOOLS.md` | the file differs by row 156's lines. This row's +/- lines are identical to round 2's (2 hunks, digest prefix `b74e6dd8`) |
|
|
| `packages/bus/src/broker.mjs` | the same, 2 hunks, `446ffd46` |
|
|
| `packages/bus/src/runtime.mjs` | the same, 3 hunks, `3bb624f8` |
|
|
| `packages/cli/README.md` | the same, 1 hunk, `bf46a856` |
|
|
|
|
The digest prefix is the sha256 of the file's `git diff BASE -- FILE` +/- lines, without the
|
|
`+++` and `---` headers, with BASE 70c9131f0 for round 2 and 4f45cadc0 for round 3.
|
|
|
|
The `public/` diff against 4f45cadc0 has +/- lines byte-equal to round 2's, and their digest is
|
|
still `939ecd28f80bc2f4a36a9468be0ccc2b0f08c8a630508060572491357f1f9444`, so mos-0832's go
|
|
covers it unchanged. Row 156 doesn't touch `public/`.
|
|
|
|
What row 156 changes in the shared code files, and why it doesn't meet this row:
|
|
|
|
- `broker.mjs`: 156 adds `#received` (set in `message.receive`), `landHolders`, `landRelease`, the
|
|
`#held` generator behind `landHeld`, and a landing-turn case in `#requestUsage`. This row adds
|
|
`INSTANCE_KEYS` and the `instances` and `instances.set` cases in `Broker.request`'s organization
|
|
branch. No function has lines from both rows.
|
|
- `runtime.mjs`: 156 adds `landHolders` and `landRelease` to the launch API. This row adds the
|
|
`instanceSettings` import and writer, and the two verb lines in the organization callback. Both
|
|
rows' lines are inside `startBroker` (lines 42 to 223 in wt3), in separate statements: this
|
|
row's at 70-71 and 99-100, 156's at 202-204 in the launch API object.
|
|
|
|
Correction (round 3, Filbert 29053): the sentence "No function has lines from both rows" holds
|
|
for `broker.mjs` only. In that file 156 doesn't touch `request()`; its lines are the `#received`
|
|
field, `landHeld`, `landHolders`, `landRelease`, three comment lines between `request()` and
|
|
`#requestUsage`, and `#requestUsage` itself. My review requests to Dewey and Filbert applied the
|
|
sentence to all four shared paths, which was wrong for `runtime.mjs`. The statements don't share a
|
|
line or a variable that either row changes, so the result above stands.
|
|
- 156's other code paths are `bus/src/process.mjs`, `cli/src/launcher.mjs` and
|
|
`harness/src/runner.mjs`. No file of this row imports them, and they don't import this row's
|
|
new modules.
|
|
|
|
One reading I checked: `landHolders` skips a role that `#threaded` reports as a thread instance,
|
|
and an instance this row adds is unknown to the broker until a bus restart, as "How a change
|
|
reaches a running bus" says. So an added instance can't hold a merge decision before the restart.
|
|
The CLI's and Console's "no restart" receipt rests on the launcher reloading the business file at
|
|
each launch check. After row 156 it still does, at `launcher.mjs:227` (198 at 70c9131f0).
|
|
|
|
Round 3 build, from a temp index (`git read-tree 4f45cadc0`, `git add` the 24 paths,
|
|
`git diff --cached --binary 4f45cadc0`):
|
|
|
|
| File | sha256 |
|
|
|---|---|
|
|
| manifest of the 24 files | `e30b54da42d5552b7320a458a5f1c808ac2c5ae6027f554ddc01e3ffc844f580` |
|
|
| `build.patch` against 4f45cadc0 | `cefce7a49c3bbe7863a68449a5eb7f74af0cefbbb5d1d8fb55c3be416d5e08aa` |
|
|
|
|
### Round 3 slot run (sage-0959)
|
|
|
|
On wt3, `TMPDIR=~/darkwing-scratch/r165/tmp`, `COMPOSE_PROJECT_NAME=darkwing-r165`:
|
|
|
|
| Run | Result |
|
|
|---|---|
|
|
| `singles.sh` with `WT=wt3`: 160 test files singly, then `test-config.sh`, `test-queue.sh` and `test-task.sh` | every one of the 163 exits 0. The awk sum over the logs is pass 1436, fail 0 (round 2 was 1419). The 17 more are row 156's: `land` 11 to 17, `launcher` 19 to 24, `thread-launch` 10 to 11 and `runner` 27 to 32. Every other file's count is unchanged |
|
|
| `mutants.py` with `WT=wt3`, all 37 | 37 killed, none survived |
|
|
|
|
Logs: `~/darkwing-scratch/r165/out/singles-20261018T234323Z` and
|
|
`~/darkwing-scratch/r165/out/mutants-20261018T235312Z`. wt3 matched the manifest after both runs,
|
|
the sweep found no process and no `darkwing-r165` network was left. `test-queue.sh` skips its
|
|
`verify` and `render --check` cases outside the canonical checkout, as in round 2.
|
|
|
|
## The candidate
|
|
|
|
The candidate is wt3: 4f45cadc0 plus 24 files, 17 changed and 7 new. Round 3 is round 2 rebased
|
|
onto row 156's landing commit, with no line of this row changed (see "Round 3").
|
|
|
|
| File | sha256 |
|
|
|---|---|
|
|
| `candidate-manifest.sha256` (the 24 files) | `e30b54da42d5552b7320a458a5f1c808ac2c5ae6027f554ddc01e3ffc844f580` |
|
|
| `build.patch` (the whole diff against 4f45cadc0) | `cefce7a49c3bbe7863a68449a5eb7f74af0cefbbb5d1d8fb55c3be416d5e08aa` |
|
|
|
|
Round 2 was manifest `4bcd6a2e...` and build.patch `cc886c47...` against 70c9131f0; the packet
|
|
keeps them as `candidate-manifest-r2.sha256` and `build-r2.patch`. Round 1 was `89ca3fcf...` and
|
|
`d84406b8...`. The `public/` +/- digest is still `939ecd28...9444`, the one mos-0832's go names.
|
|
The commit message is `commit-msg.txt`, unchanged from round 2.
|
|
|
|
## Proposal, before any code (brief item 2)
|
|
|
|
Line numbers are at d88613bac.
|
|
|
|
### The command
|
|
|
|
`scripts/mosaic` sends `business instances` and `business instance` to `packages/cli/src/cli.mjs`.
|
|
`validate` and `resolve` still go to `packages/business/src/cli.mjs`. The human commands live in
|
|
packages/cli because that is where the human proof and the other-tree check
|
|
(`packages/cli/src/cli.mjs:581-586`) already are.
|
|
|
|
```
|
|
mosaic business instances <business> [--json]
|
|
mosaic business instance add <business> <instance> --definition DEF [--vars FILE] [--launch] --revision REV
|
|
mosaic business instance set <business> <instance> [--definition DEF] [--vars FILE] [--launch | --no-launch] --revision REV
|
|
```
|
|
|
|
- `instances` prints the revision and, for each instance, its definition, its vars, whether it is
|
|
in `launch.instances`, and whether it is `launch.by` or protected. It also lists the definitions
|
|
in `roles/` that `add` and `set` accept.
|
|
- `--vars FILE` is a JSON object that replaces the instance's `vars` whole, the way the
|
|
`assistants` key does in row 155.
|
|
- `--revision` is required. A stale one is refused with `revision-conflict` (exit 2).
|
|
- A write prints the business, the new revision, the transaction id, and whether the change waits
|
|
for a bus restart (below).
|
|
- The business is positional, the way it is for `validate` and `resolve`.
|
|
|
|
### The bus verbs
|
|
|
|
There are two new human-only verbs, `instances` and `instances.set`. They go into the
|
|
organization branch of `Broker.request` (`packages/bus/src/broker.mjs:1157`), next to `plugins`
|
|
and `plugins.set`, and `fail("human-required")` covers them the same way. They aren't added to
|
|
`ORGANIZATION_KEYS`, and `organization.set` never maps to them. That keeps them out of the chief
|
|
of staff's tool, because `packages/harness/tests/tools.test.mjs` holds the tool's keys equal to
|
|
`SETTINGS_KEYS`, and those stay as they are.
|
|
|
|
`instances.set` args:
|
|
`{ revision, instance, op: "add" | "change", definition?, vars?, launch?: boolean }`.
|
|
`add` needs `definition`. `change` needs at least one of `definition`, `vars` and `launch`.
|
|
|
|
### The writer
|
|
|
|
There is one new module, `packages/business/src/instance-settings.mjs`, which is
|
|
`instanceSettings({ dir, rolesDir, system })` with `read(id)` and `set(id, args, actor)`, built
|
|
on `organizationWriter().write`. That follows `plugin-settings.mjs`. In order, it:
|
|
|
|
1. checks the shape: the instance id goes through `requireId`. `add` refuses an existing id with
|
|
`organization-instance-exists`, and `change` refuses a missing one with
|
|
`organization-unknown-instance`;
|
|
2. refuses with `organization-definition-refused` when the instance's old or new definition is in
|
|
`END_PROTECTED`. Every change to a protected instance, its vars and launch membership
|
|
included, is refused here, because the assistant's settings go only through row 155's
|
|
`assistants` key. This also covers adding an instance with a protected definition;
|
|
3. builds the next document. `launch: true` adds the instance to `launch.instances`, and `false`
|
|
removes it. With no `launch` block it is refused with `organization-launch-not-configured`;
|
|
4. runs `validateBusinessDocument`, then the unknown-model check from
|
|
`launch-settings.mjs:159-165`, then the project check `mosaic business validate` runs
|
|
(`packages/business/src/cli.mjs:290-301`). For each project whose project file exists, it
|
|
runs `loadProject`, checks the id, and runs `resolveInstance` for every instance;
|
|
5. writes through `writer.write(id, next, { revision, actor })`. That entry records the actor, the
|
|
before and after hashes and the changes in `changes.jsonl`.
|
|
|
|
`validate` also stat-checks credential references. This writer can't add or change one, because
|
|
`credentials`, `holder` and `tracker` are out of scope. A change of definition that needs
|
|
different services fails `checkCredentialMap`, so the writer doesn't re-stat references.
|
|
|
|
### The Console
|
|
|
|
The Settings page gets a "Role instances" section. It has a table from `GET /api/instances` and
|
|
two forms, add and change, that `POST /api/instances`. The routes call the bus through
|
|
`webui/src/cli.mjs` under `--send` only, the way `settingsWrite` does. The form posts the revision
|
|
it read. Mos's decision 123 go covers this `public/` diff.
|
|
|
|
### How a change reaches a running bus (brief item 6)
|
|
|
|
- The launcher reloads the business file at every launch check (`launcher.mjs:198`), so launch
|
|
membership and vars changes count at the next launch.
|
|
- The broker keeps the business it loaded at bus start. `bindLaunch` refuses a role it doesn't
|
|
know with `unknown-role` (`broker.mjs:414`), so an added instance can't launch until the bus
|
|
restarts.
|
|
- A definition change reaches the broker's authority checks only after a restart. Until then the
|
|
launcher would start the new contract while the broker checks the old definition's authority.
|
|
So the writer's answer carries `restart: true` for an add or a definition change. The CLI and
|
|
the Console say so, and the restart is Jason's or Mos's call.
|
|
|
|
## Questions for Sage (answered)
|
|
|
|
Sage answered all three in sage-0918: the expansion is approved, the build keeps the brief's
|
|
scope with a named refusal for credentials, and the restart is reported, not run. The questions
|
|
as sent, in the order they blocked code:
|
|
|
|
1. **Files beyond the brief's list.** The verbs need these:
|
|
- `packages/bus/src/broker.mjs` and `runtime.mjs`, for the two verbs;
|
|
- a new `packages/bus/tests/instances-verb.test.mjs`;
|
|
- `packages/bus/README.md`;
|
|
- `packages/webui/src/cli.mjs`, for the wiring;
|
|
- `packages/webui/src/bus.mjs`, which gets two STATUS codes, so the
|
|
`channel-proof.test.mjs` pin needs a commented bump, as in row 155 r3;
|
|
- `scripts/mosaic`, for the routing.
|
|
|
|
I can't build the verbs without them. Do you approve the expansion?
|
|
2. **A second reviewer can't be added in this row.** `reviewer`, `coder`, `pm` and `cto` declare
|
|
gitea and vikunja credentials, so an instance of any of them needs `credentials` and `tracker`,
|
|
which the brief keeps out of scope. `security-reviewer` and `merge-gate` declare none, so
|
|
acceptance 314's two instances do work. I'd keep the brief's scope and record a follow-up for
|
|
credentials and tracker, but Q171's example was "a second reviewer", and that reading is
|
|
Jason's.
|
|
3. **The definition mismatch until a bus restart** (above). Within this row I'd only report it. A
|
|
launcher refusal on a mismatch would be a separate bus row.
|
|
|
|
## Unrelated finding
|
|
|
|
`mosaic plugins` (row 145, 467a313c7) isn't in the case list at `scripts/mosaic:33`, so it looks
|
|
like it falls through to the seat CLI. I haven't run it. This is a follow-up, not part of this row.
|