row 234 (#1740): BUILD.md final broker function list, build decisions and status

This commit is contained in:
2026-10-20 22:34:50 -05:00
parent 9f90d5f4dc
commit f4de24d399
+89 -12
View File
@@ -137,16 +137,93 @@ step order, claim before consume, the resolved unspent grant decision, an async
through `credential-verbs.mjs`, the revoke marker reconcile, the upsert, the undo past `grant.revoked`, the
abort after step 5 and a missing `bus.tester` as dev.
## Broker functions this row touches (sage-1677, planned, final list with the candidate)
## Broker functions this row touches (sage-1677)
For row 236 (Megavolt), which rebuilds on this landing. Cites are `packages/bus/src/broker.mjs` at 8fede5fc6.
- `recordEvent` (:1243-1251): one more refusal, `reserved-event` for a `grant.*` action or operation.
- New private `#grantEvent`, the session-less writer for guarded events. `#event` (:585-606) is unchanged.
- `decision.raise` (:1880-2029): the `grant` frame check and the rendered block, before the raise
transaction. The keyscan runs before it, as an async step in `request()` (:1362-1450) the way
`requestHost` does.
- `decision.resolve` (:2030-2107): when the human's cli `yes` resolves a `grant` mint, it starts the executor
after the transaction commits and returns `{grant: 'started'}`.
- New private `#consumeGrant` next to `#consumeAuthority` (:855-865). `#spent` (:846-852) is unchanged.
- `requestHost` (:1712-1795): reads the store records and the Approve record check in place of `b.hosts`.
- New `hosts.status|check|remove` handling in `request()`, async like `HOST_VERBS` (:95).
For row 236 (Megavolt), which rebuilds on this landing. Old lines are `packages/bus/src/broker.mjs` at
8fede5fc6, new lines are at 9f90d5f4d. The planned list is kept in this file's history (827b89ea6).
Changed:
- `constructor` (:279): one more option, `hostGrant`, the object `host-grant.mjs` builds.
- `recordEvent` (old :1243-1251, new :1596): one more refusal. An `action.allowed` whose `body.action` or
`body.operation` is a `grant.*` name refuses `reserved-event`.
- `request()` (old :1362, new :1716): two routes ahead of the sync path, a `decision.raise` with `grant` to
`#raiseGrant` and the `hosts.*` verbs to `#requestHosts`. Both are async.
- `requestHost` (old :1712-1795, new :2069): the business-file lookups (old :1730-1736) become one
`#hostAccess` call, in the same transaction as `#agent`. `commands` and `entry` come from it.
- `#dispatch` (old :1796, new :2150) gets a fifth parameter, `enroll`, which only `#raiseGrant` passes.
- `decision.raise` (new :2234): `grant` joins the allowed keys, and `grant` without `enroll` (or the
reverse) refuses `invalid-request`. The `host.sudo` block reads `#hostAccess` in place of `b.hosts`
and the role's `hosts`, and an undefined `target` still refuses `host-unknown`. `validArgv` and
`validStdin` now run after the access check, so a malformed argv for an ungranted host refuses
`host-not-granted` first. The enrollment block is appended to the question before `string(question)`,
so the 4096 cap counts it (M1). The decision's raise body carries `grant: enroll`.
- `decision.resolve` (new :2399): when `op` is `resolved`, `#approvedGrant` reads the decision, and a
`host-enroll` grant starts `#runEnrollment` from `setImmediate`, after the transaction commits. The
result adds `grant: 'started'`.
New, all private but one:
- `#grantEvent` (:657), the writer of guarded events. It runs the secret check, then `writeGrantEvent`.
- `#approvedGrant` (:667), `#consumeGrant` (:678). `#consumeGrant` sits after `#approvedGrant`, not next
to `#consumeAuthority` as planned.
- `#hostRecord` (:685), `#hostAccess` (:692), `#grantNotice` (:708), `#enrollFailed` (:736),
`#runEnrollment` (:742), `#openGrantDecisions` (:813).
- `reconcileGrants` (:823), public, which `runtime.mjs` calls once at start.
- `#raiseGrant` (:856), `#hostsCaller` (:876), `#requestHosts` (:890), `#hostLinesLeft` (:954).
Module level, new (:184-230): `GRANT_RUN`, `grantName`, `GRANT_EVIDENCE` (the N1 filter as SQL),
`registrySha256`, `writeGrantEvent` (exported, the one writer of guarded events, which test fixtures use
for F1), `HOSTS_VERBS`, `fingerprintOf`, `INSTALLER_COMMENT`.
Unchanged: `#event`, `#spent`, `#consumeAuthority`.
Other `src` files:
- `bus/src/host-grant.mjs`, new: the keyscan, the installer key pair, and `hosts.check`'s `list`.
- `bus/src/runtime.mjs`: builds `hostGrant`, calls `reconcileGrants` before the server starts, and adds
the `host_grants` references before `new Credentials` (X4 third bullet).
- `bus/src/host-run.mjs`: `runHost` takes `command`, sent as is in place of the sudo argv, for the
installer's `list`.
- `bus/src/server.mjs`: the `hosts.*` verbs and a raise with `grant` go down the async path.
- `bus/src/store.mjs`: the 3i-3j step, `SCHEMA_VERSION` 3j. `bus/src/credentials.mjs` exports
`privateDir`.
- `business/src/business.mjs` and `business/src/hosts.mjs`: the loader refuses `hosts` (S1).
`checkHosts` and `checkHostGrants` are gone.
- `cli/src/hosts.mjs`, new, routed from `cli.mjs` and `scripts/mosaic`.
## Decisions taken in the build
- **`#hostAccess` binds the agent and the host.** X4 says the Approve record check passes when the
decision is an approved `host` grant with a `grant.host` consume. The check also requires the grant's
`agent` to equal the session's role and its `host` to equal the requested host. Without that, an
approved grant for one agent and host would vouch for any `host_grants` row that names its decision.
Cost: none for row b, whose executor writes the row from the same grant.
- **Store fixtures are written the real way.** `bus/tests/host-fixtures.mjs` inserts the decision rows by
SQL, but every guarded event goes through `writeGrantEvent`, so the trigger sees it (F1). An enrollment
fixture writes `grant.host.enrolled`, or the start reconcile reads it as interrupted.
- **The resolver test for host kinds is gone.** `business/tests/hosts.test.mjs` lost its D5 case, because
the loader now refuses `host.<id>` credential refs. The `isHostKind` filter in `business/src/resolve.mjs`
can't be reached now. I left it in, since row b may want it back, and it's one line.
- **Store before Credentials.** The runtime already opened the store before `Credentials`. The
`host_grants` references depend on that order, and the comment there says so.
- **CLI.** `mosaic hosts` prints tab lines, and `--json` prints the bus rows. Usage errors exit 4 and bus
refusals exit 2, as `mosaic credentials` does. There is no enroll command: enrollment is the chief's raise.
- **Host tools tests live in the bus suite.** `bus/tests/host-tools.test.mjs` loads the writer by path
through a Python harness under `python3 -B -I`. The harness passes its own key dir, config dir, owner
uid and user, group and sudo lookups, and runs `mosaic-keys` from a copy with stub `sudo` and `logger`.
The gate runs the bus suite, so G16 and G25 run there. The real `pwd`, `grp` and `sudo -l` paths are
checked on the #433 VM, not here. The test also runs `visudo -c` on the sudoers file and `sshd -T` on
the drop-in.
- **Writer details the design left open.** The `from` file must hold exactly one value matching
`[0-9A-Za-z.:/*?!_-]+(,[0-9A-Za-z.:/*?!_-]+)*`, so it can't close the quote. `users`, `privileged` and `from` must be
regular, owned by the owner uid and not group or world writable, and are opened without following a
symlink. Lines starting with `#` are comments. A refusal exits 1 with its code on stderr and in syslog.
`add` of a tag that is already there exits 0 and writes nothing.
## Status at 9f90d5f4d
Done: every Split item, 1 to 8. Single files green: grant-enroll 20, host-run 23, hard-waits 13,
host-tools 6, grant-migration 5, project-runtime 3, credentials 5, credential-verbs 2, decision-route 5,
account-migration 3, tester-writes 8, seat sandbox 10, business hosts 4, business 16, cli hosts 5, cli
app 30, cli host 42, cli names 2, cli credentials 5.
Left, in Sage's slot: mutants A1 to A23, then the whole bus, business and cli suites and every
`scripts/test-*.sh`. Then the manifest and the in-review move.