CB-596 code is live; 1.1 is down to two operator actions

Dai Ha
2026-08-17 13:33:37 +02:00
parent ef84c3dd42
commit 01aab0ba10
+44 -17
@@ -232,9 +232,9 @@ commits** have landed on `main` with **no tag**. So the stage tables above are t
the obvious question unanswered: what is actually left before the single-host story can be called
finished?
**The answer, as of 2026-08-17: one implementation unit, CB-596.** Everything else in 1.1 is merged
and deployed. CB-596 looked like a single operator command; running that command is what showed it is
not. See *Open* below — the measurement was step 1 of six.
**The answer, as of 2026-08-17: no code. Two operator actions.** Every ticket in 1.1, CB-596
included, is merged and running on the live daemon. What is left is the half of CB-596 that lives in
the operator's own secret store, plus the probe that proves it. See *Open* below.
Gitea milestone: **`1.1 — single-host close-out`**. The admission rule is one sentence — **if it
would still be broken with exactly one host, it belongs in 1.1.** Read strictly, that rule sent four
@@ -268,9 +268,10 @@ flowchart LR
class op,fed,defer todo
```
*Figure: the 1.1 milestone by theme, as of 2026-08-16 night. Green is merged; amber is open. One
amber box is left, and it is the one no session can close: CB-596 needs the operator to run a probe
the command classifier refuses. Everything on the left is single-host work; the right is release 2.*
*Figure: the 1.1 milestone by theme, as of 2026-08-17. Green is merged and deployed; amber is open.
The one amber box on the left is CB-596, and its code is green — what is amber is the half that lives
in the operator's secret store, which no session may edit. Everything on the left is single-host
work; the right is release 2.*
### Done — merged and verified
@@ -288,11 +289,11 @@ the command classifier refuses. Everything on the left is single-host work; the
### Open
One — and it grew when it was measured.
One — and its code has landed. What is open is a file no session may edit.
| Theme | Ticket | The point |
|---|---|---|
| **Credential scope** | CB-596 (#82) | CB-592 blocks one credential name. A member's pane runs a login shell that re-sources the operator's whole secret store, so the other names come straight back. **Measured 2026-08-17 inside a live member: 31 names, all set.** Criterion 1 is done; criteria 2–6 are an implementation unit, now delegated. |
| **Credential scope** | CB-596 (#82) | CB-592 blocks one credential name. A member's pane runs a login shell that re-sources the operator's whole secret store, so the other names come straight back. **Measured 2026-08-17 inside a live member: 31 names, all set** — later 34 once the gap detector ran. The fix merged (`ac40de1`) and is deployed (jar `e11160695fbe`): `memberCredentials:` in config, deny-by-default, 34 known / 7 allowed / 29 blocked, with a startup summary and a per-spawn gap warning. **The remaining half is the operator's:** the launcher writes the member's environment, and then the pane's login shell overwrites it, so the same block must be re-applied by a `BRIDGED_MEMBER`-guarded export at the end of the secret store. |
**What the measurement actually said.** Two results, and they point in opposite directions.
@@ -318,6 +319,20 @@ safe way to compare two readings on one machine and an unsafe thing to publish:
has length 5 and hashes to `8c6976e5b541`, which is SHA-256 of `admin`. A truncated hash of a
low-entropy value is not an anonymiser. The finding was written up with names and lengths only.
**What the fix found that no list contained.** The gap detector warns, on every spawn, about any
credential-shaped variable on neither `known:` nor `allow:`. On its very first spawn it named two:
`CLAUDE_CODE_MESSAGING_TOKEN` and `SSH_AUTH_SOCK`. Neither is in the secret store, so no list written
by reading that file could ever have held them. `SSH_AUTH_SOCK` is the serious one — a member needs
it to push, because worktree remotes are `ssh://`, but it lets a member sign with every key the
operator's agent holds. That is strictly broader than the repo-scoped forge token CB-302 built to
avoid exactly this. Allowed for now, with the reasoning written into the config; filed as **CB-607
(#110)** on 2.0, fixed by pushing over HTTPS with `WORKER_GITEA_TOKEN`.
The general lesson is worth more than the instance: **the enumeration was of a file, and the exposure
is of an environment.** Anything granted by a handle rather than by a value is invisible to a list
built by reading `secrets.sh` — which is the argument for having the daemon report the gap instead of
trusting the list.
### Deferred to 2.0 — the cut decision
Made 2026-08-16, by reading the admission rule strictly.
@@ -332,15 +347,27 @@ Made 2026-08-16, by reading the admission rule strictly.
### Before the tag
1. ~~Land or defer CB-606 and CB-586.~~ Both merged and deployed.
2. **Land CB-596.** Its measurement is done and it is delegated; criteria 2–6 still have to land and
be verified. This is the last code in 1.1.
3. ~~Redeploy the daemon.~~ Done 2026-08-16: `scripts/redeploy-bridged.sh --yes`, 860 tests, BUILD
SUCCESS, jar `64908de1cb56`. **CB-596's fix will need another redeploy after it merges** — a merge
is not a deployment, and this one changes what a member's environment contains.
4. ~~Confirm `bridge_whoami` still answers `primary`.~~ Done: `primary`, leader `opus`. Note the trap
— the redeploy cuts the lead's own bridge MCP mount and it does not reconnect by itself, so this
check usually needs the operator to run `/mcp` first.
5. Then tag `v1.1.0` and mark this section closed.
2. ~~Land CB-596.~~ Merged `ac40de1`, 870 tests, BUILD SUCCESS.
3. ~~Redeploy the daemon.~~ Done twice. The second, 2026-08-17, put CB-596 live: jar
`e11160695fbe`, and the startup line reads
`memberCredentials: 34 known name(s), 5 allowed — blocking 29 on every spawn`. A merge is not a
deployment, and this change alters what a member's environment contains.
4. ~~Confirm `bridge_whoami` still answers `primary`.~~ Done: `primary`. Note the trap — the redeploy
cuts the lead's own bridge MCP mount. It reconnected by itself the second time and did not the
first, so do not rely on either; if the tools are gone, ask the operator to run `/mcp`.
5. **Two operator actions, in this order.** Neither can be done by any session.
1. **Rotate `GITEA_ACCESS_TOKEN`.** A lead leaked about 31 characters of it into a transcript
while dumping the structure of the secret store: the redaction covered `export NAME=…` lines,
and the token is set on a line that begins with a guard, so it fell through. Reported at once;
the operator chose to rotate later.
2. **Append the CB-596 block to the secret store.** A `BRIDGED_MEMBER`-guarded loop that exports
the same sentinel over the 29 blocked names. It is a drop-in block that moves, copies and
retypes no value; the daemon's blocked set and that block were diffed name-by-name and agree.
6. **Then run the verification probe:** spawn a member and run `scripts/probe-member-credentials.sh`.
The pass condition is that the 29 blocked names come back at the sentinel's length and hash, and
the 7 allowed keep their real lengths. That measurement is what lets #82 close honestly — the
config half alone does not prove anything, because the login shell runs after it.
7. Then tag `v1.1.0` and mark this section closed.
**On "Stage 5 ✅ production shape".** That row was never wrong — CB-504 did build a launchd agent and
a systemd unit. But a unit in `deploy/` that no host has loaded is not supervision. CB-594 and CB-600