diff --git a/8-Roadmap.md b/8-Roadmap.md index 6006dd8..959d195 100644 --- a/8-Roadmap.md +++ b/8-Roadmap.md @@ -232,8 +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-16 night: nothing that a session can do.** Every code item in 1.1 is -merged. What is left is CB-596, which needs the operator, and the redeploy-and-tag steps below. +**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. 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 @@ -287,11 +288,35 @@ the command classifier refuses. Everything on the left is single-host work; the ### Open -One, as of 2026-08-16 night — and it is not code. +One — and it grew when it was measured. | Theme | Ticket | The point | |---|---|---| -| **Needs the operator** | CB-596 (#82) | CB-592 blocks one credential name; the pane's login shell re-sources about thirty, and a second forge token is among the untouched ones. The probe that would enumerate them is refused by the command classifier, so this one cannot be closed from inside a session. `scripts/probe-member-credentials.sh` is committed and the ask is posted; it prints a name, a state, a length and a hash, never a value. | +| **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. | + +**What the measurement actually said.** Two results, and they point in opposite directions. + +The reassuring one: **CB-592 works.** The member holds the sentinel +`blocked-by-bridged-cb592-see-gitea-issue-77`, not the admin token — confirmed by hashing the +sentinel, which is a hardcoded non-secret string, and matching it against what the member reported. +The guarded-`export` mechanism does beat the login shell, which is the whole reason it was built that +way. + +The other one: **`GITLAB_PERSONAL_ACCESS_TOKEN` is a full personal access token for a second forge, +and nothing blocks it.** Alongside it sit Cloudflare, Tailscale, Grafana, Home Assistant, Telegram and +Confluence credentials — 24 live secrets with no bridge purpose. The rule that produced CB-592 was +*a member must not hold the operator's admin forge credential*. That argument never stopped at Gitea; +only the implementation did. + +This is the CB-604 → CB-606 shape for the third time in this milestone: **the instance that got fixed +was not the worst instance.** The operator chose deny-by-default — an allow-list of four names, with +every other known name replaced by the sentinel — because a deny-list has exactly the defect this +ticket is about: it is silently wrong the moment a new secret is added, and nothing reports it. + +**A note on the probe's own output.** It prints `NAME | STATE | LEN | SHA256-12`. That digest is a +safe way to compare two readings on one machine and an unsafe thing to publish: `GRAFANA_ADMIN_USER` +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. ### Deferred to 2.0 — the cut decision @@ -306,13 +331,16 @@ Made 2026-08-16, by reading the admission rule strictly. ### Before the tag -1. ~~Land or defer CB-606 and CB-586.~~ Both merged. CB-596 needs the operator and is the - only 1.1 item still open. -2. **Redeploy the daemon.** Every merge in 1.1 is undeployed — the running `bridged` holds the jar it - started with. A merge is not a deployment. Run `scripts/redeploy-bridged.sh` once the fleet has - drained; a restart drops in-flight tickets and rendezvous. -3. Confirm `bridge_whoami` still answers `primary` after the restart. -4. Then tag `v1.1.0` and mark this section closed. +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. **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