| 1 | REJECT |
| 2 | |
| 3 | 1. **P1 — Captured worker builds are unprotected before plan 5.** [internal/release/adopt.go:405](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/adopt.go:405), [internal/activator/workerbuild.go:52](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/workerbuild.go:52). |
| 4 | |
| 5 | A worker runs A, whose staged files are over six hours old. Two promotions install B then C before the worker converges. Its live link and registry now pin C and B; nothing exported by this branch pins A. The next hourly sweep can delete A. Alternatively, adoption of a re-shipped A can overwrite it because `BoxPins` reports it unreferenced. |
| 6 | |
| 7 | A subsequent crash or `max_run` recycle refuses A through `VerifyStaged`. The exited-worker branch keeps retrying that identity and never reaches convergence, leaving the worker down indefinitely. This happens without any new flag. |
| 8 | |
| 9 | **Smallest fix:** treat worker references as unknown until they are exported, suppressing both destructive restaging and eviction in that state. Alternatively, bring the worker-pin writer and both consumers into this branch. Add a regression covering two repoints followed by sweep/restage and recycle. |
| 10 | |
| 11 | 2. **P2 — Same-build recycles can prevent convergence indefinitely.** [internal/activator/activator.go:1974](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:1974), [internal/activator/activator.go:2548](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:2548). |
| 12 | |
| 13 | An always-on worker runs A with `max_run=30s`; root installs B. Every recycle correctly respawns A, but also clears `staleSince`. The drift never survives the 90-second `ConvergeGrace`, so B never runs. Frequent crashes produce the same starvation; exited workers skip the drift check entirely. Before this change, their respawn followed the updated live link. |
| 14 | |
| 15 | **Smallest fix:** preserve drift age across recycles of the same build, evaluate drift even when a recycle is due, and give an eligible convergence priority while retaining the convergence budget. Test several recycle intervals after a promotion, not only the first recycle. |
| 16 | |
| 17 | 3. **P2 — Chained live symlinks hide referenced copies from `BoxPins`.** [internal/release/pins.go:59](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/pins.go:59), [internal/release/staged.go:53](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/staged.go:53). |
| 18 | |
| 19 | With `live/h → /opt/operator-current → versions/v1/app`, and no registry version pin for v1, `BoxPins` records only `/opt/operator-current`. Another host adopting a re-shipped v1 therefore overwrites a copy the live link still references. `ResolveLive` likewise retains the intermediate link, rather than the final executable path. A later digest refusal detects the damage but does not prevent the overwrite. |
| 20 | |
| 21 | **Smallest fix:** resolve the captured target’s remaining symlinks for executable identity and pinning; preserve necessary intermediate-path pins and fail closed when resolution is uncertain. Add a chained-link restage-refusal test. The ledger’s Task 3 symlink minor needs promotion to a pre-merge fix. |
| 22 | |
| 23 | ### Ledger rulings and deferred items |
| 24 | |
| 25 | - **R1, R7, R9:** workflow choices; no implementation objection. |
| 26 | - **R2–R5:** consistent with the binding sections and current code. Preserving cgroup cleanup and the config-admission gate is necessary. |
| 27 | - **R6:** canonical comparison and outage fallback are consistent with the stated contract. This does not establish that the reference set is complete. |
| 28 | - **R8:** reasonable against this base; the later staging integration still needs review. |
| 29 | - **R10:** acceptable. The fixtures still reach the binding refusal and retain their assertions. |
| 30 | - **R11:** the conservative refusal is correct. Its claimed recovery is not guaranteed: a held row is not necessarily adopted by a full pass, and that pass may never stage its requested version. Correct that expectation; do not restore the incomplete-pin overwrite. |
| 31 | |
| 32 | Deferred-minor triage: |
| 33 | |
| 34 | | Ledger area | Disposition | |
| 35 | |---|---| |
| 36 | | Task 1 | Test-comment wording and missing uppercase rejection test: P3. | |
| 37 | | Task 2 | Lossy-sibling comment is already corrected. Older-sibling reuse test: P3. | |
| 38 | | Task 3 | Chained symlink: finding 3. Relative-link assertion, wording and error wrapping: P3. | |
| 39 | | Task 4 | Empty SHA is already rejected by `validateManifest`, so `CanonicalDemand` returns hold. EqualFold/digest assertion gaps: P3. | |
| 40 | | Task 5 | Parent-directory fsync, reader consolidation, missing branch tests, parse-error wrapping and empty-host-list strictness: P3. | |
| 41 | | Task 6 | Missing-platform diagnostics/test, repeated hashing/logging and held-stage explanation: P3. | |
| 42 | | Task 7 | Ordering comment, generic failure reason and coverage gaps: P3. Host-claim selection and unit-less proof-policy behavior are not new defects. | |
| 43 | | Task 8 | Regular-file and unreadable-target refusals fail closed. Missing failure-path test: P3. | |
| 44 | | Task 9 | Captured-copy loss: finding 1, **must fix**. Coalescing backoff/logging and remaining coverage gaps: P3; add refused-recycle coverage with finding 1. | |
| 45 | |
| 46 | Static review only; no files modified and no tests run. `git diff --check 4f26f15c..HEAD` passed. |