1.1 is closed: CB-596 verified in a live member, 29/29
+13
@@ -1658,6 +1658,19 @@ end of the secret store that re-applies the same sentinel. Two lists that must a
|
||||
the gap detector exists, and why the daemon logs its counts at startup. If a member ever reports
|
||||
holding a name you blocked, the secret-store half is what is missing.
|
||||
|
||||
That block must be the **last** thing in the secret store. It overwrites the blocked names, so
|
||||
anything that re-exports them afterwards silently undoes it.
|
||||
|
||||
**Verified, not assumed.** Measured on 2026-08-17 inside a live member pane, once both halves were in
|
||||
place: **29 of 29 blocked names hold the sentinel**, and the allow-listed names present in that pane
|
||||
keep their real values. The operator's own login shell is unchanged, because the whole block sits
|
||||
inside `if [ -n "${BRIDGED_MEMBER:-}" ]`. To repeat the check, spawn a member and run
|
||||
`scripts/probe-member-credentials.sh`; it refuses to run anywhere but in a member.
|
||||
|
||||
**Third gotcha — the probe has the same defect it is checking for.** It carries its own hardcoded
|
||||
name list and does not read the config, so it under-reported by three names and still exited 0. Read
|
||||
its count against the daemon's startup line rather than trusting the table. Tracked as **CB-608**.
|
||||
|
||||
**Second gotcha — `allow` silences the warning.** The detector treats `known ∪ allow` as covered, so
|
||||
adding a name to `allow` makes its warning go away *and* lets the value through. That is correct
|
||||
behaviour, but it means the quiet way to dismiss a gap warning is also the permissive one. Prefer
|
||||
|
||||
+46
-26
@@ -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: 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.
|
||||
**The answer, as of 2026-08-17: nothing. The milestone is closed — 20 of 20.** Every ticket is
|
||||
merged, deployed, and verified on the running daemon. CB-596 was the last one, and it was closed on a
|
||||
measurement taken inside a live member pane, not on a merge. **The tag is the only step left.**
|
||||
|
||||
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
|
||||
@@ -260,18 +260,18 @@ flowchart LR
|
||||
subgraph r2["release 2 — one centre, many hosts"]
|
||||
fed["CB-308 federation"]
|
||||
defer["CB-589 · CB-548 · CB-605"]
|
||||
found["found by CB-596<br/>CB-607 · CB-608"]
|
||||
end
|
||||
r1 --> r2
|
||||
classDef done fill:#2f855a,stroke:#22543d,color:#ffffff;
|
||||
classDef todo fill:#b7791f,stroke:#7b341e,color:#ffffff;
|
||||
class sup,dur,sec,bugs,cfg,flaky,ask,docs,val,wip done
|
||||
class op,fed,defer todo
|
||||
class sup,dur,sec,bugs,cfg,flaky,ask,docs,val,wip,op done
|
||||
class fed,defer,found todo
|
||||
```
|
||||
|
||||
*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.*
|
||||
*Figure: the 1.1 milestone by theme, as of 2026-08-17. Everything on the left is green — 20 of 20
|
||||
closed, merged, deployed and verified on the running daemon. The right is release 2, and it grew by
|
||||
two: CB-596's own gap detector found both of them on its first live spawn.*
|
||||
|
||||
### Done — merged and verified
|
||||
|
||||
@@ -287,13 +287,17 @@ work; the right is release 2.*
|
||||
| **Config validation** | CB-604 (#102) · CB-606 (#106) | Four config fields shared one shape: lower-cased in a compact constructor, then compared against exactly **one** string, so a typo fell through to the other branch in silence. The worst was `auth.mode` — a typo of `token` behaved as `loopback-trust`, and `validateAuthExposure()` only fires on a **non-loopback** bind, so the common loopback bind hid it end to end and the daemon authenticated nobody while the config said otherwise. All four now refuse at load, naming the field, the value, the accepted set, and what would have happened. |
|
||||
| **Snapshot pruning** | CB-586 (#67) | Nothing pruned `refs/wip/*`, so CB-578 stage C's snapshots pinned their whole trees forever. The rule that landed needs **both** conditions: the tree is already reachable from `main`, and the ref is older than 24h. Reachability is the floor — a snapshot exists because the work was committed nowhere else, so a plain TTL would delete the only copy. `/members` now reports `wipRefs{count,costBytes}`. The sweep shipped **dead**: a `Long.MIN_VALUE` "never yet" sentinel overflowed the interval gate, which returned before the assignment that would have fixed it, so it never ran once — and every unit test passed, because they all called the seam directly and walked around the gate. |
|
||||
|
||||
### Open
|
||||
|
||||
One — and its code has landed. What is open is a file no session may edit.
|
||||
### The last one — CB-596, closed on a measurement
|
||||
|
||||
| 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** — 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. |
|
||||
| **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. Merged `ac40de1`, deployed on 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 operator applied the matching guarded block to the secret store on 2026-08-17, and it was then **verified in a live member pane: 29 of 29 blocked names hold the sentinel; the 5 allow-listed names present in that pane keep their real values.** The operator's own shell is unchanged. |
|
||||
|
||||
**Why the measurement had to happen at all.** The launcher writes the member's environment, and *then*
|
||||
the pane's login shell re-sources the secret store and overwrites it. So the config half proves
|
||||
nothing on its own — it has to be paired with a `BRIDGED_MEMBER`-guarded block that re-applies the
|
||||
same sentinel, and only a reading taken inside a real member pane shows which one won. This is the
|
||||
whole reason CB-592 was built the way it was; CB-596 inherits it.
|
||||
|
||||
**What the measurement actually said.** Two results, and they point in opposite directions.
|
||||
|
||||
@@ -333,6 +337,19 @@ is of an environment.** Anything granted by a handle rather than by a value is i
|
||||
built by reading `secrets.sh` — which is the argument for having the daemon report the gap instead of
|
||||
trusting the list.
|
||||
|
||||
**And the verification tool had the same defect.** The probe reported 26 blocked; the policy blocks
|
||||
29. Both numbers were right and counting different sets — `scripts/probe-member-credentials.sh`
|
||||
carries its **own** hardcoded list of 31 names and never reads the config, so it skipped the three
|
||||
`N8N_*` names added to `known:` after it was written. Those three were then measured directly and are
|
||||
blocked, so the result stands at 29 of 29. But note where the failure pointed: the script exited 0 and
|
||||
its table looked complete. **A checker that under-reports fails in the worst direction, because a
|
||||
clean run is taken as evidence.** Filed as **CB-608 (#111)**.
|
||||
|
||||
That makes three copies of one list — Java (CB-592), config (CB-596), and the probe — and only the
|
||||
gap between the first two is guarded. Also recorded from the same run:
|
||||
`CLAUDE_CODE_MESSAGING_TOKEN` is **unset in a member pane**, though it is present in the daemon's own
|
||||
environment. Allow-listing it was harmless, but not for the reason it was allowed.
|
||||
|
||||
### Deferred to 2.0 — the cut decision
|
||||
|
||||
Made 2026-08-16, by reading the admission rule strictly.
|
||||
@@ -343,6 +360,8 @@ Made 2026-08-16, by reading the admission rule strictly.
|
||||
| CB-589 (#74) | Cost-first placement. `weighted` spreads by ratio with no idea which profile costs money, so paid spawns happen while the free box sits idle — but that is working behaviour that costs too much, with a decisive workaround (`local.weight: 100`), not something broken on one host. A missing **explanation** of the workaround *was* broken, because `bridged.yaml` is gitignored and a fresh host starts without it. **That half shipped in 1.1**; the policy did not. |
|
||||
| CB-548 (#16) | Architect slots. A new capability, and genuinely blocked: `MemberRegistry.bind` is never called. |
|
||||
| CB-605 (#103) | The systemd unit carries launchd's login-shell secret gap. Bites only when the first Linux gateway is stood up. |
|
||||
| CB-607 (#110) | A member holds `SSH_AUTH_SOCK`, so it can sign with every key the operator's agent holds — broader than the repo-scoped token CB-302 built. Allowed **on purpose** today, because worktree remotes are `ssh://` and blocking it stops members pushing. A documented, deliberate scope reduction, not a break. |
|
||||
| CB-608 (#111) | The credential probe keeps its own hardcoded name list and under-reported by three. The policy it checks is correct; what drifts is the checker. |
|
||||
|
||||
### Before the tag
|
||||
|
||||
@@ -355,19 +374,20 @@ Made 2026-08-16, by reading the admission rule strictly.
|
||||
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.
|
||||
5. ~~Apply the secret-store half of CB-596.~~ Done by the operator on 2026-08-17: `secrets.sh` backed
|
||||
up, then the `BRIDGED_MEMBER`-guarded block appended. `zsh -n` and `bash -n` both parse it, and the
|
||||
operator's own shell is unchanged.
|
||||
6. ~~Run the verification probe.~~ Done, inside a live member pane. **29 of 29 blocked names hold the
|
||||
sentinel.** The probe itself covered 26 of them — see CB-608 — and the other three were measured
|
||||
directly.
|
||||
7. **Tag `v1.1.0`.** This is the only step left, and it is the operator's: tagging is outward-facing.
|
||||
214 commits since `v1.0.0`.
|
||||
|
||||
**Not part of the tag, but do not lose it:** `GITEA_ACCESS_TOKEN` still needs rotating. 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 that token sits on a line beginning with a guard, so it
|
||||
fell through. Reported at once; the operator chose to rotate later. It was never a CB-596 criterion,
|
||||
which is exactly how it could go missing with that ticket.
|
||||
|
||||
**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
|
||||
|
||||
Reference in New Issue
Block a user