CB-596 is an implementation unit, not one operator command

Running the probe is what showed it. Measured inside a live member: 31
credential names, all set. CB-592 works (the sentinel matched), but
GITLAB_PERSONAL_ACCESS_TOKEN — a second forge — has nothing blocking it,
alongside 23 other live secrets with no bridge purpose.

Also records why the probe's own hash column must not be published: a
truncated SHA-256 of a 5-character value is not an anonymiser.
Dai Ha
2026-08-17 08:47:58 +02:00
parent 51fec33cd2
commit 46c50110c8
+39 -11
@@ -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