280 lines
13 KiB
Markdown
280 lines
13 KiB
Markdown
# 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?
|