Files
stack/agents/filbert/work/queue-60/build.md
T

13 KiB
Raw Blame History

Row 60 (#1549): bus read, credential status on the broker, round 2

Filbert, 2026-10-12. Brief: docs/plans/2026-10-10_design-implementation.md, "Bus read: credential status on the broker" (blob a360554c). Darkwing and Dewey review. Sage commits after the gate; nothing is committed, staged or pushed yet.

Base bbd22f1b. build.patch applies there with git apply --index; candidate-manifest.sha256 pins all 9 files after the patch. Round 1 was manifest 99d67e44…eedc on 4100fa68, approved by Dewey (27431) and Darkwing (27531). HEAD is now bd4dff33; none of the 9 paths changed since bbd22f1b. I applied the patch to a clean worktree of bbd22f1b and all 9 manifest lines check OK.

Round 2 changes

Sage scoped the round at rev 569: the digest check, the serve.mjs merge with row 74's preview checks, the missing decisions row (question 2), and Dewey's N1.

Rebase and digest check

Since 4100fa68, row 74 (351464ce) changed packages/webui/src/serve.mjs and packages/webui/README.md, and row 58 (aaf87213) changed packages/bus/src/broker.mjs, packages/bus/README.md, packages/webui/README.md and packages/webui/tests/bus.test.mjs. Round 1's patch applies to bbd22f1b with git apply --3way and no conflict, and no round 1 hunk shares lines with a row 58 or row 74 hunk:

  • serve.mjs: the only change is still the BUS_VERBS line and its comment (line 51). Row 74's preview checks are in the request handler (lines 114–135) and are untouched. The bus route lookup at line 160 runs after them, so in preview mode /api/bus/credentials gets the same Host, Origin, sign-in and read-only checks as the other bus reads.
  • broker.mjs: the credentials case sits before default:, after row 58's tasks change; the constructor option and the reader allowlist are in places row 58 doesn't touch.
  • webui/tests/bus.test.mjs: the round 1 tests follow the decisions route test; row 58's assertion is in the first route test.
  • webui/README.md: the bus-read paragraph is in "Data and boundaries", below row 74's preview section and row 58's S5 bullet.

Compared by added and removed lines, round 2's patch equals round 1's except for the two packages/bus/README.md edits below. Against round 1's manifest, 4 digests are unchanged (runtime.mjs, views.mjs, credential-status.test.mjs, webui/src/bus.mjs; no row changed them). 5 are new: broker.mjs, webui/tests/bus.test.mjs and webui/README.md because row 58 changed their base, serve.mjs and webui/README.md because row 74 did, and packages/bus/README.md because of row 58 and the two edits.

Question 2: the decisions row

packages/bus/README.md verb table gains a decisions row after trail: {list}, roles or recent, human and reader sessions only. It's taken from row 57's paragraph and the broker's decisions case (keys(a, ['list'], ['list']), RECENT 50).

Dewey's N1

Round 1 said the two views differ "since a token file rotated after startup isn't reread until restart". Dewey is right that this is wrong: the dates come from the business file, which the host reads at startup (the broker keeps a structuredClone of it, and Credentials keeps the reference dates). The sentence now says the host takes the dates and accounts from the business file at startup, only the state moves with the clock, the two differ only when the business file changes after startup (a date, an account, or a credential added or removed), and rotating a token file without editing its date changes neither.

Not changed

  • Question 1: the Business view stays as it is, as both reviewers advised.
  • Dewey's N2 (the preview serves this read from the edge, states and dates only): row 74's preview section already says the GET routes, the bus views included, are unchanged. I didn't edit that section. Sage has N2.
  • Darkwing's nit on the round 1 gate table (release 4 should be 14): 4 is what my run printed. Without a Docker daemon, test-release passes 4 and skips its state-machine cases; Dewey's run with Docker passed 14. The table below says which cases were skipped.

Gate (my run on the round 2 candidate)

Worktree at bbd22f1b with the patch, run in sequence with TMPDIR in my scratch and DOCKER_HOST pointed at a missing socket, 2026-10-12T00:06:32Z to 00:10:01Z:

Suite Result
packages/bus 88/0 (84 on bbd22f1b; 4 new)
packages/control-board 124/0
packages/webui 62/0 (61 on bbd22f1b; 1 new), the browser and preview tests included
packages/cli 113/0, since it starts the runtime
scripts/test-*.sh all nine exit 0: auth 15, conductor 17, config 24, discord 66, extension-package 18, foundation 44, queue 27, release 4, task 26

test-release and test-task skipped their Docker cases without a daemon (test-release passes 14 with one). Sage's gate rerun covers those. No test calls a model. test-discord passed on the first run this time. Logs are in my scratch (r60/gate2/), not kept in the repository.

Mutants (round 2)

All 16 round 1 mutants (run.mjs, kept in my scratch) were rerun on the rebased tree, 2026-10-12T00:10:13Z to 00:11:18Z. The runner is unchanged: each mutant edits one file, runs the tests that own it, and restores the file. All 16 are killed, and the worktree diff is unchanged afterwards. The list is round 1's (below). The README edits are docs only and have no mutants.


The round 1 packet follows unchanged, for the description, files, round 1 gate and questions. Its base, line numbers and overlap notes are round 1's.

Round 1 packet

Filbert, 2026-10-11. Brief: docs/plans/2026-10-10_design-implementation.md, "Bus read: credential status on the broker" (blob a360554c). Darkwing and Dewey review. Sage commits after the gate; nothing is committed, staged or pushed yet.

Base 4100fa68. build.patch applies there with git apply --index; candidate-manifest.sha256 pins all 9 files after the patch. HEAD is now c949f9ef, with docs and queue commits only; no file under packages/ changed. I applied the patch there and all 9 manifest lines check OK.

What the read is

A new socket verb, credentials. No args. Human and reader sessions only; a role session gets human-required, as with decisions. It returns one row per credential the running host loaded for the session's business:

{ service, account, role, dateKind, date, state }
  • service: gitea or vikunja.
  • account: the role's tracker.bot for Vikunja, the sync identity's bot for the business/@sync row, null for Gitea (the business file names no Gitea account).
  • role: the instance, or tracker sync for @sync.
  • dateKind, date: rotateBy for Gitea, expires for Vikunja.
  • state: Credentials.status() against the host clock at the request: valid, expiring (under 7 days), expired, or rotation-due for Gitea.

The order is status() load order: role instances, then the sync identity.

The row shape is the one the WebUI's Business view already uses (credential() in packages/webui/src/reads.mjs), so a page can use either. The difference is the source. The Business view reads the business file: what the host should load. This read asks the host: what it did load, checked against its own clock. A token file rotated after startup isn't reread until restart, and this read is how to see what the host is running with.

How the broker gets it

The broker gets one new constructor option, credentialStatus, a function. The runtime passes () => credentials.status(). The broker never gets the Credentials object, so it can't reach use(). The default is () => [], so a broker built without it (every existing test) lists none.

status() already returns metadata only (instance, service, date, state). The broker filters it to business + '/', drops instance, and adds account, role and dateKind from the business config. The result also goes through the known-token check every read goes through.

The existing trusted launch op credentialStatus(business, role) in the runtime is unchanged.

WebUI

GET /api/bus/credentials is a new bus route: credentials joins BUS_VERBS in serve.mjs, and views.mjs gains credentials(). It runs through the same human transport as the other bus reads, so the same 403, 503 and 502 mapping applies. GET only.

No page uses it yet. The brief names the endpoint and its tests, not a view change. The Business view still shows the file's metadata. See question 1.

Files

  • packages/bus/src/broker.mjs: the credentialStatus option, the credentials case, and credentials in the reader allowlist.
  • packages/bus/src/runtime.mjs: passes credentialStatus.
  • packages/bus/src/views.mjs: credentials().
  • packages/bus/tests/credential-status.test.mjs (new, 4 tests):
    • Human and reader sessions get the exact rows and keys for demo, with every state (valid, expiring, expired, rotation-due), a file ref and an env ref, and the sync row. A second business, demox, shares the demo prefix without the slash and gets only its own row.
    • A role session gets human-required; any argument is invalid-request.
    • The status is read on each request (a changed state shows on the next one); a broker without the option lists none.
    • The brief's acceptance test: the real broker process (process.mjs, forked) boots on a seeded config with five token files and one env token. A reader calls credentials, a role session is refused, and the process closes. The rows, the host log (stdout and stderr) and the store files (bus.sqlite*) contain none of the six tokens, the token file paths, their directory, or the env variable name. The store check also confirms the refusal was written, so it read the right files.
  • packages/bus/README.md: a verb table row and a credentials paragraph under "Human and reader paths".
  • packages/webui/src/serve.mjs, packages/webui/src/bus.mjs: the verb and comments.
  • packages/webui/tests/bus.test.mjs: the route returns the reader's rows only (one business of two), { rows, at }, no-store; the route is GET-only; busReader sends {verb:'credentials', args:{}}.
  • packages/webui/README.md: the route, and the exposure note now names the credential status.

Mutants

16 mutants (run.mjs, kept in my scratch). Each edits one file, runs the tests that own it, and restores the file:

  • broker, runtime: the bus suite;
  • views, serve: packages/webui/tests/bus.test.mjs.

All 16 are killed, and the diff is unchanged afterwards:

  • broker, 12: role session allowed, reader refused, prefix without the slash, no business filter, arguments accepted, a Gitea account from tracker.bot, the sync account null, @sync as the role, a fixed dateKind, instance left in the row, a fixed state, the status read once at construction.
  • runtime, 1: no credentialStatus passed (the process test).
  • views, 2: the wrong verb, an extra argument.
  • serve, 1: credentials missing from BUS_VERBS.

Gate (my run on the candidate)

Worktree at 4100fa68 with the patch, run in sequence with TMPDIR in my scratch and DOCKER_HOST pointed at a missing socket:

Suite Result
packages/bus 84/0 (80 before; 4 new)
packages/control-board 124/0
packages/webui 41/0 (40 before; 1 new), the browser tests included
packages/cli 112/0, since it starts the runtime
scripts/test-*.sh all nine exit 0: auth 15, conductor 17, config 24, discord 66 (see below), extension-package 18, foundation 44, queue 27, release 4, task 26

test-discord failed once in the sequential run, 65/1: the engine test "when pi has not started a timed-out turn by the end of the abort grace…" (packages/discord, a timing assertion). Load average was about 20 at the time. This patch touches nothing under packages/discord. The rerun of the whole script passed 66/0. Sage's gate rerun will show whether it recurs.

test-release and test-task skipped their Docker cases without a daemon; Sage's gate rerun covers those. No test calls a model. Logs are in my scratch (r60/gate/), not kept in the repository.

The mutants ran before one comment-only change: I rewrapped the header comment of packages/webui/src/bus.mjs. The gate ran after it.

Overlap

  • Row 59 (#1548, in review) edits the agents branch of broker.mjs, adds #agentState after #dispatch, and replaces the inbox, tasks, agents row of the bus README verb table. With row 59's packet applied on 4100fa68 first, plain git apply of this patch refuses on packages/bus/README.md (both add a line after the trail row); git apply --3way merges every file cleanly, with the credentials row after trail and the new case before default:. On that combined tree, the bus suite and the WebUI bus and browser tests pass, 100/0.
  • Row 58 (Darkwing) edits the tasks branch, which this patch doesn't touch.

Questions for reviewers

  1. Should the Business view use this read, or show it next to the file's metadata (for example, flag a difference)? I left the view alone because the brief lists only the endpoint, and the Business view works without a bus host.
  2. The bus README verb table has no decisions row (row 57 added the verb and a paragraph). I added a credentials row only. Should a follow-up add the missing row?