29 KiB
Row 155 (#1649): the human and the chief of staff configure the assistant
Builder: Darkwing. Brief: docs/plans/2026-10-16_q169-assistant-config.md, section "The human
and the chief of staff configure the assistant", blob b2aeba0364 (rev 1717). Jason's ruling Q169.
Base: refactor 7ce156259, origin/refactor after row 125 landed (rebased 2026-10-18; built on
9098c33f7, then 99d2b49c9). Worktree: ~/darkwing-scratch/r155/wt-125; wt (9098c33f7) and
wt2 (99d2b49c9) are kept unchanged. Reviewers: Dewey (security) and Filbert.
Status: code, tests and docs written, 30 modified files plus packages/webui/tests/assistant.test.mjs.
Rebased twice: onto 99d2b49c9 (rows 149, 124, 129 and 164; see "Rebase onto 99d2b49c9"), then
onto row 125's landing (see "Rebase onto row 125" and "Row 125 landed"). The run on 99d2b49c9
had 27 of 28 files green; the red one, webui shell.test.mjs, was my miss from the first round
and is fixed. The run of all 39 files on 7ce156259 is green (see "Row 125 landed"). The first
round's runs on 9098c33f7 (sage-0720) are under "Single-file runs". The full gate has not run;
it is Sage's. Candidate: r3/build.patch and r3/candidate-manifest.sha256 on 0473574b0
(see "Candidate round 3"); round 2 was r2/ on 449cadd1f, round 1 r1/ on 7ce156259.
Rebase onto 99d2b49c9
Three commits since 9098c33f7 touch this row's files: 70504d23a (row 124, token usage),
03bbd2c98 (row 149, roles and harness) and 850484a7d (row 129, Console chat). I saved the old
worktree's diff against 9098c33f7 (~/darkwing-scratch/r155/out/pre-rebase-9098c33f7.patch),
added a new worktree at 99d2b49c9 and applied the diff with git apply --3way. 26 files
applied cleanly. Three webui files conflicted, and each resolved as a union of both sides:
src/cli.mjs: row 129'schat = createChat({ call })sits beside this row'snameWriteandassistantStop, andstartServergets all three.src/serve.mjs:BUS_VERBSends'messages', 'usage', 'names', andstartServertakeschat,nameWriteandassistantStop.src/public/bus.js: row 124'stokenUsagestays above this row's assistant block.settingsViewreadsusagefirst, keeping row 124's rethrow on 403 or signed-out, thennames, and renders the launch form, the Assistant section, then Token usage.
webui/tests/assistant.test.mjs is untracked in the old worktree, so the diff-based patch
did not carry it. I copied it over unchanged (sha256 4e921ce653cc…) and marked it
intent-to-add. Every changed source passes node --check, public/bus.js parses, and
git diff --check is clean. The rebased diff is out/rebased-99d2b49c9.patch in my scratch:
30 files, +800 -104.
The rebased run (~/darkwing-scratch/r155/run-rebased.sh, each file singly) covers the
15 files of the first round plus the tests rows 124, 129 and 149 added or changed beside the
merged sources: webui usage, usage-browser, chat, chat-browser, browser, shell
and views; bus usage, usage-broker, usage-runtime and workflow-roles; business
workflow-roles; harness raise-domain. 28 files.
Rebased run (sage-0876), 2026-10-18T19:58:43Z to 19:59:52Z, behind busy.sh, TMPDIR in my
scratch: 27 of 28 files green, 236 pass and 1 fail summed over the logs. Per-file totals are in
~/darkwing-scratch/r155/out/20261018T195844Z/summary.txt. The sweep found 0 leftovers.
The failure is webui shell.test.mjs, the write-boundary test. It lists every POST, PUT, PATCH
and DELETE fetch(...) in public/bus.js and counts method:. This row's assistantPost
added fetch(url, {method:'POST', ...}), so the list had a fourth entry and the count was 4,
not 3. The assertion is the same at 9098c33f7, and row 129 did not change it. The first round
ran only the files I touched, so it never ran shell.test.mjs. This was my miss, not a rebase
effect.
The fix is in shell.test.mjs only:
- The expected list gets the
fetch(url, ...)entry second, in source order. - The
method:count goes from 3 to 4. - A new assertion pins every
assistantPost(first argument tourl(the definition),'/api/settings','/api/name'and'/api/assistant-stop'. A fourth caller, or a computed route, fails it.
public/bus.js is unchanged by the fix; its diff against 99d2b49c9 still has sha256
e37389fd…, the one going to Mos. The rerun of shell.test.mjs, with settings-form and
assistant as controls, waits for a slot. The rebased diff with the fix is
out/rebased-99d2b49c9-r2.patch: 31 files, +804 -106.
Rebase onto row 125 (sage-0879)
Row 125 lands before this row and touches 11 of its files, so the run on 99d2b49c9 is not the
candidate. I dry-ran the rebase onto row 125's candidate 5289ca1d2 in
~/darkwing-scratch/r155/wt-dry. git apply --3way left 12 conflict blocks in 8 files.
~/darkwing-scratch/r155/rebase/resolve125.py resolves each block by index and refuses on a
count mismatch. Every resolution is a union:
docs/TOOLS.md: row 125's token-limit paragraph, then this row's Assistant paragraph.- Organization keys. There is one list, row 125's, with
assistantsappended:revision, limits, models, instances, reset, assistants. It is the same inSETTINGS_KEYS(launch-settings.mjs), bothORGANIZATION_KEYS(broker.mjs,harness/src/tools.mjs; row 125'stools.test.mjsholds the three equal) and the Console'swritable(serve.mjs). This row'sUPDATE_KEYSis gone;setrefuses a patch with no key other thanrevisionby readingSETTINGS_KEYS. cli/src/limits.mjs: row 125'sreset PATH...verb andformatTokens, and this row'sPATCH keys:list, now withreset.cli/tests/limits.test.mjspins the new list.broker.mjs#claimEnd: row 125's signature withby = s.run ?? s.humanand both comments. This row's callers pass five arguments, so theirbyis unchanged.harness/src/tools.mjs:change_organizationdescribes both the assistant fields and the token-limit reset, and its schema has bothresetandassistants.public/bus.jssettingsView: reads usage, names and token limits, and renders the launch form, Assistant, Token limits, then Token usage.serve.mjs:BUS_VERBSends'usage', 'limits', 'names'. The refusal text names "a reset or assistant settings".shell.test.mjs: five writes in source order (settings, row 125's reset post,fetch(url, ...), project file, bus message),method:counted 5, and this row's call-site pin.
node --check passes on every changed source and git diff --check is clean. A static
evaluation of the shell test's regexes over the merged bus.js matches the expected list.
assistant.test.mjs came through with sha256 4e921ce653cc…. The dry diff is
out/dry-5289ca1d2.patch (31 files, +801 -104).
When row 125 lands, I redo the rebase on the landed commit, compare it with the dry run, and
run ~/darkwing-scratch/r155/run-125.sh on Sage's slot. That is the 28 files above plus 11 row
125 test files on the merged sources: bus limit-checks, limit-stop, limits-runtime and
process; business token-limit-settings; cli launch-limits, launch-limit-checks,
limits-tokens and thread-launch; webui limits-route and limits-browser.
Sage's ruling sage-0713: no schema step (no 3i; row 124 keeps 3g to 3h). Row 134 stays its own
row, waits for row 155 to be done (rev 1722) and rebases on this landing. Until row 134, the
revision the chief of staff sends comes from the human; the bus, business, harness and cli
READMEs, docs/TOOLS.md, the tool description and roles/chief-of-staff.md say so. Row 156
also touches broker.mjs and the role md files; whichever lands second rebases.
Row 125 landed (sage-0882)
Row 125 landed as aa625565f, and origin/refactor is now 7ce156259. The two commits after
row 125's packet (df4d5702f) touch only docs/SESSIONS.md and
docs/plans/2026-10-14_prd-1.2-build.md, neither in this row, so I based on 7ce156259 rather
than df4d5702f. aa625565f has the same tree as the dry run's base 5289ca1d2 (git diff
empty).
In ~/darkwing-scratch/r155/wt-125, git apply --3way of out/rebased-99d2b49c9-r2.patch
left the same 8 files in conflict, and resolve125.py resolved the same 12 blocks with no
count mismatch. node --check passes on every changed source and git diff --check is clean.
out/rebased-7ce156259.patch is byte-identical to out/dry-5289ca1d2.patch (sha256
4dda2fc2…, 31 files, +801 -104), index lines included. assistant.test.mjs came through with
sha256 4e921ce653cc…, staged this time, not intent-to-add. The shell test's own regex over the
merged bus.js gives 5 writes equal to its expected list, 5 method: and the four
assistantPost( arguments. The bus.js diff for Mos is out/busjs-7ce156259.diff, sha256
9f9ce31b…, +53 -2.
Run (sage-0890), 2026-10-18T20:30:29Z to 20:31:58Z, ~/darkwing-scratch/r155/run-125.sh with
WT=~/darkwing-scratch/r155/wt-125, behind busy.sh, TMPDIR in my scratch, each file singly:
39 of 39 files green. The awk sum over the 39 logs is 300 pass and 0 fail, with 0 skipped and 0
todo. shell.test.mjs passed 5 of 5. Per-file totals are in
~/darkwing-scratch/r155/out/20261018T203030Z/. The sweep found 0 leftovers.
Candidate
r1/build.patch is the staged diff of wt-125 against 7ce156259, byte-identical to
out/rebased-7ce156259.patch: sha256 4dda2fc2b7b2971b6daacaab57a027938bd074463ec25bfeb6314095d8503b52,
31 files, +801 -104. r1/candidate-manifest.sha256 lists the sha256 of each of the 31 files
in wt-125; its own sha256 is 949873cfda99a2e3d418efee0066fa1438e1c12f69b9d2dfa9413ee089a79183.
The worktree had no unstaged change when both were written.
busjs-7ce156259.diff in this packet is the public/bus.js diff against 7ce156259 for Mos's
decision 123 go: sha256 9f9ce31bc14e6c0ed6d1d6c9f6cc282992d0631192be6416e0163866264029cd,
+53 -2. It is a copy of out/busjs-7ce156259.diff in my scratch.
Candidate round 2 (sage-0900)
Row 162 landed as 7e7f8e208 and changed two of this row's 31 paths, docs/TOOLS.md and
packages/cli/README.md, so the r1 manifest no longer described the tree this row would land
on. Round 2 is confirm-only and makes no source change.
I applied r1/build.patch unchanged with git apply --index to a fresh worktree
(~/darkwing-scratch/r155/wt-r2) at 449cadd1f, refactor HEAD. It applied without conflict,
git diff --cached --check was clean, and the staged names are the same 31 files (+801 -104)
with no unstaged change. No run on this base: Sage's landing gate covers it, and
host.test.mjs reads the cli README.
r2/build.patch against BASE 449cadd1f (sha256
2a5a470d4c6fb53ca9c6ee8ffe4f5d8cb4c49b36b95f3f5ea1bc523224d94170).
r2/candidate-manifest.sha256 (sha256
b1989f39012df6cbdd0e676cf19f75ccf88fcbf9d1c0a502eb71ab4247efad25).
What moved from r1:
- In the manifest, only
docs/TOOLS.md(nowe8fa08bf…) andpackages/cli/README.md(now7943058d…). The other 29 sha256s are r1's. - In
r2/build.patch, only lines for those two files: theirindexlines and three hunk headers (TOOLS.md@@ -610is now@@ -636,@@ -627,6 +630is now@@ -653,6 +656; the cli README@@ -789is now@@ -795). Row 162 added text above these hunks, so they start lower. Withindexand@@lines removed, r1 and r2 compare byte-identical. public/bus.js: its diff on449cadd1fhas the same sha256 asbusjs-7ce156259.diff(9f9ce31b…, +53 -2), index line included, since bus.js is unchanged between the two bases. Mos's decision 123 go (mos-0816) was on those lines.
Round 1 verdicts on manifest 949873cf: Filbert approved (comment 29010, rev 1849), Dewey
approved (comment 29012, rev 1852). Each gave non-blocking follow-ups (see
followup-brief.md, which carries Mos's bus.js notes and Dewey's F1 on serve.mjs null
bodies; body() already refuses those, see its item 3).
Candidate round 3 (sage-0906)
Sage's landing gate on r2 (candidate ba32dcad2 on 0473574b0, every suite run through, 20:53:10Z
to 20:59:57Z) had one failure out of 2370: packages/bus/tests/channel-proof.test.mjs:18,
"channel conversion preserves the four proof/transport byte baselines". Row 164 (008746b6e)
pins the sha256 of packages/webui/src/bus.mjs at :14, and this row's eight STATUS entries in
that file change it to d1f3a274…e241. My single-file runs missed it: the test reads
bus.mjs by path, not by import, and it isn't beside any file this row touches. That was my
miss. Two other tests read a manifest path literally (stack-release, stack-copy and webui
service read packages/webui/src/cli.mjs); they passed on Sage's gate.
Sage ruled to bump the pin: the bus.mjs diff is STATUS table entries only, and the freeze
guards the channel conversion, not later status codes. Round 3 changes only
channel-proof.test.mjs:14, the bus.mjs baseline, from dbad731c…1f24 to
d1f3a274f7aa15eb7ecc2267c65bf4fe7e45c898f77c34235b4e9c29cd1be241, with a trailing comment
naming row 155. The other three baselines (human.mjs, human-cli.mjs, transport.mjs) and
the import check are unchanged, and none of those three files is in this row's manifest.
I applied r2/build.patch unchanged with git apply --index to a fresh worktree
(~/darkwing-scratch/r155/wt-r3) at 0473574b0, then made the one edit. Run on Sage's slot
(sage-0906), output in ~/darkwing-scratch/r155/out/r3-20261018T210058Z/, 21:00:58Z to
21:01:13Z:
channel-proof.test.mjssingly: 2 pass, 0 fail.- Mutant, the
'name-taken': 409, …STATUS line removed frombus.mjs: 1 pass, 1 fail, and the failure namespackages/webui/src/bus.mjs. Restored after, sha256 checked againstd1f3a274…. - The whole
packages/bussuite once (node --test tests/*.test.mjs): 399 pass, 0 fail, 0 skipped, 0 todo.
Totals are an awk sum over the logs. The sweep found 0.
r3/build.patch against BASE 0473574b0 (sha256
9bee0e1b6546e70f06c1781beadc67082aa14990228f25571cdaf72e85f8c37e), 32 files.
r3/candidate-manifest.sha256 (sha256
96cae28e095eff1632b7edcba195e1793bcabc3fb5aa93ea86d485c35e1f9016), 32 paths.
What moved from r2: one new manifest line, packages/bus/tests/channel-proof.test.mjs
(3e6d794b…), and its one-line hunk in the patch. The other 31 manifest lines and every r2
patch line, index and @@ lines included, are unchanged. The public/bus.js diff still has
sha256 9f9ce31b…, so Mos's go (mos-0816) applies.
No schema step
The row needs no migration, so there is no 3i.
thread_claimsalready hasop(claim, release, revoke),holder_run,by,reasonanddecision(packages/bus/schema.sql). A thread revoke is one more row there.role.claimon the thread path already refusesrun-revokedfor a run with a revoke row, so a revoked assistant run can't claim its thread again.- Names use row 141's
instance_names. The new config key lives in the organization file, which isn't the bus store.
The comment at broker.mjs:2215 says a thread claim is never revoked and thread_claims has no
reason. Both stop being true here. #claimEnd now writes reason for a thread claim too, and the
comment says so.
1. What "configure" changes
A new organization key, assistants:
{ "revision": "<64 hex>", "assistants": { "<instance>": { "vars": {}, "tracker": {}, "definition": "assistant" } } }
- Each field given replaces that field of the entry.
tracker: nullremoves the tracker. A field left out stays as it is. An empty entry is refused. An instance the file doesn't have isinvalid-request. - Today
tracker: nullchanges nothing in practice: the assistant definition uses no vikunja credential, sovalidateBusinessDocumentrefuses any tracker on it and none exists to remove. The field is there for a future assistant definition that has one. - The target instance's current definition has to be the thread definition, or the write refuses
organization-not-assistant. A givendefinitionhas to be one the broker treats as the assistant, or it refusesorganization-definition-refused. Today that list is one name,assistant, so a definition change is accepted only as a no-op. It's there so the field exists when a second assistant definition does. holderandcredentialsaren't accepted (strict keys,invalid-request).- The whole next document still goes through
validateBusinessDocumentand the unknown-model check. A tracker on a definition with no vikunja credential is refused there, as it is for a hand edit.
No writer this row adds can give an instance a definition in END_PROTECTED. assistants only
targets instances that are already the assistant, and instances still sets only vars.model. On
top of that, set() now compares every instance's definition before and after and refuses
organization-definition-refused if any other one changed. That closes the dewey-0546 case
(an unprotected instance becoming the assistant after an arbiter resolved a revoke of it) for every
key the writer takes, not only the new one. It's defense in depth: no key today can change a
definition except assistants, which only accepts the same one.
For Dewey. vars replaces the whole object, including the limits.tools, limits.network and
limits.authority vars. Those only narrow: resolve.mjs:64-68 filters the role file's tools and
authority by them and takes the narrower network, so a chief that writes wider ones gets nothing
the role file doesn't grant. A chief can remove a narrowing the human set, back up to the role
file. checkVars still checks every var.
One source for the definitions. THREAD_DEFINITION and END_PROTECTED move from broker.mjs into
packages/business/src/vocabulary.mjs, which the broker already imports for ID_PATTERN. The
broker and launch-settings.mjs read the same constants, so no equality test is needed. A test
holds END_PROTECTED including THREAD_DEFINITION, so the assistant can't lose its protection by
an edit to one list.
Files: vocabulary.mjs, launch-settings.mjs (set and read), broker.mjs
(ORGANIZATION_KEYS, the two constants), packages/harness/src/tools.mjs (its
ORGANIZATION_KEYS copy, which a test holds equal).
read adds assistants: each assistant instance's definition, vars and tracker as the file
has them, so the human and the Console see what they're replacing.
2. Who configures
- Human, CLI.
mosaic limits set --file PATCHalready sends the whole patch tolimits.set, so a patch withassistantsworks through it.mosaic limitsprints the revision. The usage line, the cli README anddocs/TOOLS.mdname the new key. I'm not adding a second command for the same write. - Human, Console. The Settings view gets an Assistant section per assistant instance: the
definition, the vars and the tracker as JSON fields, with the same Save, revision and receipt
handling as the limits form.
settingsSend's writable list addsassistants. - Chief of staff.
change_organizationtakesassistantsas well (schema and description). It runsorganization.set, which checksorganization.changebefore the writer and consumes it after. Every other role is refused there, as today. - Revision. Both carry the file's revision and a stale one refuses
revision-conflictinorganizationWriter.write. - Log.
organization.mjsalready writes the actor,before,afterandchangesto.history/<id>/changes.jsonl. The actor is the human id, orchief-of-staff.<instance>.
Gap: the chief can't read the revision. No verb gives an agent the organization revision. Row
134 ("Organization revision read for a role holding organization.change", queued) owns that read.
Row 155 doesn't build it. Until row 134 lands, the chief's change_organization call works only
with a revision it was given (by the human, in a message), and the tests pass the revision in
directly. I've told Sage.
3. The seat name
- Chief of staff. A new typed tool,
set_name(verbname.set, actioninstance.name), withinstanceandname. Row 141 added the grant and the broker check; this adds the tool. With it,untypedActions(chief)and the bundle's untyped list become[], sotools.test.mjs:43andbundle.test.mjs:94are re-pinned. - Human, Console. A POST route,
/api/name, with{instance, name}and the settings boundary (signed-in header user, exact Origin, same-origin Fetch Metadata, JSON, strict keys, 4 KiB). It callsname.setthroughhuman-cli, so the broker's#humancheck applies: a Console started inside an agent run getshuman-required. The Assistant section shows the current name, read through a newnamesbus read.namesis inBUS_VERBS, and the webuicli.mjsadds it beside the shared views (bus = { ...views({ call }), names: () => call('names') });bus/src/views.mjsis unchanged, because the CLI doesn't need it. The route is/api/name; a second one,/api/assistant-stop, is in section 4. - Human, CLI.
mosaic namealready exists (row 141). - Every other role is refused by
name.set's within-role check.
4. Removing the assistant: ending its runs
role.revoke and role.end of a threaded instance. Today the end path reads role_claims through
#holder, and the assistant claims in thread_claims, so the answer is not-held. For a threaded
target the end path now collects every thread of that instance whose latest claim is claim, and
writes a revoke for each. None held is still not-held. A role target keeps its one-claim path. The
result adds threads: N for a threaded target.
- A chief
role.endwith a reason passes#protectedEnd(row 139) and ends them all. - A
role.revokewith a decision the human resolved through the CLI passes#protectedEndand ends them all. An arbiter can't resolve that decision at all:#hardWaitmakes a revoke of anEND_PROTECTEDtarget a hard wait, which goes to the human whoever raised it. - The chief's own run's end stays refused (
a.role === s.role).
The null and undefined holder run. The raise records holderRun as
#holder(...)?.holder_run ?? null, which is null for a threaded target. The check compared it with
#holder(...)?.holder_run, undefined, and answered decision-mismatch. Both sides now use ?? null.
For Dewey. For a role target the decision binds to the holder incarnation at the raise. For a
threaded target there's no single incarnation, so it binds to nothing beyond the target, the
action, the raiser and the class. It's still single-use (#spent) and still human-resolved through
the CLI. If you want it bound tighter, the raise could record the set of held thread runs and the
check could require the same set. I think that refuses a legitimate revoke whenever a thread idles
out between raise and use, which is the common case.
What the run does. The runner polls every 2 s. Its next bus call gets not-holder, which is in
SESSION_GONE, and it exits 22. A turn already running finishes first, the same as role.end of a
role today. The launcher starts a new run only for a thread with pending messages or an open, so the
assistant comes back only when the human writes to it again. Removing the instance from the
organization file is out of scope.
The human, Console. A new human-only verb, assistant.stop {instance}.
- It checks
#human(s)and that the instance is threaded. - It revokes the current claim of every thread of that instance the human owns (REQ-USER-2: a user
reaches only their own threads), with
byset to the human and reasonstopped by the human. None held isnot-held. A known instance that isn't threaded is a new code,not-assistant(webui status 400); an unknown one isunknown-role. - It returns the stopped runs.
The Console route is POST /api/assistant-stop with {instance} and the same boundary as /api/name.
The button sits in the Assistant section. I chose per-instance over per-run because the Console
lists no threads or thread runs (agents reads role_claims only), and stopping the assistant is
what Q169's "remove" means. mosaic stop <run> still stops one run from the CLI.
The human can't use role.revoke itself: #consumeAuthority starts with #agent, which refuses a
human.
5. Text
packages/harness/src/bundle.mjs:112and theend_rundescription (tools.mjs:166) say the chief can't end the assistant's run. That's false after this row. Row 149's brief leaves theend_runtext inbundle.mjsto row 155.roles/chief-of-staff.md:30-35: the chief ends the assistant withend_run, configures it withchange_organization(assistants, with a revision from the human until row 134), and names instances withset_name. Raisingpolicy.changefor those goes.
What else changed
packages/cli/src/limits.mjs: the usage line namesassistants;limits.test.mjspins it.packages/webui/src/bus.mjs: statuses for the codes the new routes andassistantscan return (organization-not-assistant,organization-definition-refused,unknown-model,name-is-instance,name-is-roleandnot-assistant400;name-takenandnot-held409).roles/chief-of-staff.md: the contract text names no person, becausebusiness/tests/chief-of-staff.test.mjsrefusesJasonin it.- Re-pinned tests:
chief-of-staff.test.mjs:174,threads.test.mjs:396, the bus and businesshard-waits.test.mjs,tools.test.mjs,bundle.test.mjs,runner.test.mjs(its fixture's chief getsinstance.name),project-runtime.test.mjs(the broker's import line) and webuishell.test.mjs(the write-boundary list, after the rebase; see "Rebase onto 99d2b49c9").
Tests (brief item 5)
| Brief item | Where |
|---|---|
| Human CLI and Console changes of vars, tracker and definition succeed and log the human | business launch-settings.test.mjs, cli limits.test.mjs, webui settings-write.test.mjs |
| Chief's change succeeds and logs the chief | bus chief-of-staff.test.mjs |
| Another role's change is refused | bus chief-of-staff.test.mjs |
| Definition change without protection refused; another instance given an END_PROTECTED definition refused | business launch-settings.test.mjs |
| Chief-raised revoke of the assistant is a hard wait to the human, no arbiter resolves it | bus hard-waits.test.mjs |
| Stale revision refused | business and bus |
| Human's and chief's name changes succeed, another role's refused | bus chief-of-staff.test.mjs and row 141's names.test.mjs, harness tools.test.mjs (set_name), webui assistant.test.mjs |
| Chief's end stops every assistant thread run; its own end stays refused | bus threads.test.mjs |
| Human-resolved revoke stops them; arbiter-resolved stays refused | bus threads.test.mjs |
| Console stop route ends an assistant run | bus threads.test.mjs (verb), webui assistant.test.mjs (route) |
Plus the re-pins above, a set_name tool test, and a browser test for the Assistant section in
settings-form.test.mjs.
Single-file runs (sage-0720)
~/darkwing-scratch/r155/run-touched.sh: one node --test per file from its package directory,
behind busy.sh, TMPDIR ~/darkwing-scratch/r155/tmp, logs teed. 15 files: the 13 this row
touches (12 modified, assistant.test.mjs new) plus business chief-of-staff.test.mjs and bus
names.test.mjs. No Docker, no full suites, no mutants.
- Run 1, 2026-10-18T15:34:50Z to 15:35:18Z: 13 green, 2 red.
- bus
threads.test.mjs, 14 of 15. My test bound an inside-agent human session to show it can't stop runs.bindHumanrefuses that at the bind with human-required (pinned sincebroker.test.mjs:132), so the session never reaches the verb. The test now asserts the bind refusal and checks the verb with pm, chief and a. No broker change. - webui
settings-form.test.mjs, the Q169 test.renderalways reads/api/settingsagain, so after a save the page took its revision from the fixture server, which only ever returned revision a. The next post carried a where the test expected b. The page is right: a real server returns the saved revision. The test's fetch mock now serves the saved state on reads after a save. Nobus.jschange.
- bus
- Run 2, 15:38:33Z to 15:39:01Z: all 15 green, 167 pass, 0 fail. Logs:
~/darkwing-scratch/r155/out/20261018T153833Z/. Sweep found 0 leftovers.
That run covers the fixtures' project.create and thread.create calls, the chief's
decision.raise of a revoke with no grant, the decision-consumed check order and the browser
test's timing, which were unproven before it.
Overlaps
- Row 149 (Dewey, in review) changes
roles/chief-of-staff.mdAuthority text and theraise_decisiontool intools.mjs. I'll rebase onto its landing before the in-review move and run every touched test file singly. - Row 156 (Sage, queued after 149) touches
broker.mjsand the role md files. Whichever lands second rebases. - Row 124 (Rocko) does the Settings readout and schema 3h. The Settings view is shared; I'll check its diff at rebase.
Gate
Dewey and Filbert approve on #1649 naming the candidate manifest. Mos's decision 123 go on the
public/ diff. Bus, business, harness, cli and webui suites and every scripts/test-*.sh green.