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.
35 KiB
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 0aeb5f5adb, 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: theinstancesandinstances.setverbs, human-only, withINSTANCE_KEYS. Neither is inORGANIZATION_KEYS;packages/bus/src/runtime.mjs: routes the two verbs toinstanceSettings;packages/bus/tests/instances-verb.test.mjs(new);packages/bus/tests/channel-proof.test.mjs: thewebui/src/bus.mjspin, below;packages/harness/tests/tools.test.mjs: proves no tool reaches the two verbs;packages/cli/src/cli.mjs: routesbusiness instancesandbusiness instanceto the newpackages/cli/src/instances.mjs;packages/webui/src/cli.mjs: the wiring, under--sendonly;packages/webui/src/bus.mjs: seven STATUS codes, below;packages/webui/src/serve.mjs:GET /api/bus/instancesandPOST /api/instances;packages/webui/src/public/bus.js: the Settings section (decision 123, below);packages/webui/tests/instances.test.mjsandinstances-browser.test.mjs(new);packages/webui/tests/shell.test.mjs: its pinned list of bus.js POST fetches gets the/api/instanceswrite, and themethod: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: sendsbusiness instancesandbusiness instanceto packages/cli;packages/bus/README.md,packages/business/README.md,packages/cli/README.md,packages/webui/README.md;docs/TOOLS.mdanddocs/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:
- "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 withoutdefinitionswould throw in the next render. The page now checks the whole shape (instanceAnswer: a 64-hex revision, a booleanrestart, andinstancesanddefinitionsas 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.mjsmakes the same check on the bus answer and refuses with 503outcome-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:
- a green slot run, with the singles and all 29 mutants recorded here;
- reviewer approvals that cover the server-side writer and the 503
outcome-unknown; - 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:
- 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. - 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.checkCredentialMapwould refuse most of these anyway. The writer's own check names the reason instead of a validation message. - A vars or launch change on an instance that holds its binding succeeds, and
credentialsandtrackercome 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.mjspins bus.js's POST fetches, and the new/api/instanceswrite wasn't in its list. Fixed in the test (above).views.test.mjsmatched#bus-view .empty bagainst my instances empty state. The page now uses the section-warning convention, which is thepublic/change Mos approved in mos-0832. The new last step ininstances-browser.test.mjsand 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 inmessage.receive),landHolders,landRelease, the#heldgenerator behindlandHeld, and a landing-turn case in#requestUsage. This row addsINSTANCE_KEYSand theinstancesandinstances.setcases inBroker.request's organization branch. No function has lines from both rows.runtime.mjs: 156 addslandHoldersandlandReleaseto the launch API. This row adds theinstanceSettingsimport and writer, and the two verb lines in the organization callback. Both rows' lines are insidestartBroker(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.mjsandharness/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
instancesprints the revision and, for each instance, its definition, its vars, whether it is inlaunch.instances, and whether it islaunch.byor protected. It also lists the definitions inroles/thataddandsetaccept.--vars FILEis a JSON object that replaces the instance'svarswhole, the way theassistantskey does in row 155.--revisionis required. A stale one is refused withrevision-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
validateandresolve.
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:
- checks the shape: the instance id goes through
requireId.addrefuses an existing id withorganization-instance-exists, andchangerefuses a missing one withorganization-unknown-instance; - refuses with
organization-definition-refusedwhen the instance's old or new definition is inEND_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'sassistantskey. This also covers adding an instance with a protected definition; - builds the next document.
launch: trueadds the instance tolaunch.instances, andfalseremoves it. With nolaunchblock it is refused withorganization-launch-not-configured; - runs
validateBusinessDocument, then the unknown-model check fromlaunch-settings.mjs:159-165, then the project checkmosaic business validateruns (packages/business/src/cli.mjs:290-301). For each project whose project file exists, it runsloadProject, checks the id, and runsresolveInstancefor every instance; - writes through
writer.write(id, next, { revision, actor }). That entry records the actor, the before and after hashes and the changes inchanges.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.
bindLaunchrefuses a role it doesn't know withunknown-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: truefor 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:
-
Files beyond the brief's list. The verbs need these:
packages/bus/src/broker.mjsandruntime.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 thechannel-proof.test.mjspin 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?
-
A second reviewer can't be added in this row.
reviewer,coder,pmandctodeclare gitea and vikunja credentials, so an instance of any of them needscredentialsandtracker, which the brief keeps out of scope.security-reviewerandmerge-gatedeclare 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. -
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.