REJECT

Reviewed `e77463df..61b4438c` only. F1 remains open; the symlink fix also introduces one new pinning regression.

**F1 — NOT ADDRESSED (P1).**

The writer protects ordinary captured versions after successful publication, but does not establish complete protection:

- **Eviction still ignores generations.** [versionsweep.go:47](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/host/versionsweep.go:47) collects only live-link, `Version` and `PrevVersion` pins before calling `SweepStaged` at line 70. Worker A can remain correctly published while promotions B and C remove its other pins; the hourly sweep can still delete A once its files exceed the grace period. Its next recycle then refuses the missing artifact. This is the original F1 deletion scenario, traced through its consumer—not a separate review of unchanged code.

- **Empty Version does not prove an external artifact.** [generations.go:39](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/generations.go:39) silently omits these identities, while [staged.go:128](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/staged.go:128) derives Version from the *first raw target*. Thus `live/h → operator-current → versions/A/app` captures A’s path and digest but publishes no pin. After live and registry pins move on, root can overwrite A with a re-shipped version. R17 does not exempt handmade links.

- **Pending builds are not published before execution.** [activator.go:1312](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:1312) writes the initial snapshot before capturing the new identity; publication follows `startWorker`. Respawns likewise publish only after installation at [activator.go:2033](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:2033). If execution/publication is delayed while root installs B then C, A can lose its other pins before publication and be restaged. “Milliseconds” is not an enforced bound or synchronization guarantee.

- **Publication failure permits an indefinitely incomplete file.** [generations.go:66](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/generations.go:66) records failure only in agent memory; callers continue spawning. For example, failed temp-file creation leaves an older, readable snapshot omitting the new worker. Root accepts that snapshot and can restage its artifact after other pins disappear. Retrying later cannot undo the overwrite.

**Required fix:** publish the resolved pending artifact before execution, make unsuccessful publication prevent unprotected execution or make destructive consumers reliably fail closed, and feed worker pins into eviction too. Resolve identities from their captured paths, or conservatively mark references unknown when they cannot be represented.

**N1 — ADDRESSED.**

[activator.go:2005](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:2005) retains `workerServing` after digest refusal and reports the failed restart. Subsequent recycling passes that retained identity again; the fresh-identity fallback is removed.

**N2 — ADDRESSED within R17’s scope.**

[workerbuild.go:75](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/workerbuild.go:75) refuses recycle-triggered convergence when `CoalesceRecycle` is set or the budget is exhausted. Otherwise it decrements the same budget reset and consumed by the sweep at [activator.go:2492](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/activator.go:2492). Deferred recycles retain drift age.

**N3 — ADDRESSED for the reported failure.**

[staged.go:76](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/staged.go:76) resolves the holder’s parent before interpreting its relative target. The original `alias/current → ../app` case now reaches the OS-selected executable. However, the new path normalization introduces N4 below.

**Publication concurrency and paths**

The mutex ordering is sound: [generations.go:55](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/activator/generations.go:55) takes `genMu` before snapshotting under `a.mu`, releases `a.mu` before file I/O, and holds `genMu` through replacement. An older writer cannot overwrite a newer writer’s snapshot. This serializes publication; it does not close the spawn-to-publication gap.

Writer and restaging reader use the same configured path: [agent.go:396](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/host/agent.go:396) uses `GenerationsPath(dataDir())`; root passes `BakeDir(dataDir())` at line 4155, consumed by [adopt.go:407](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/adopt.go:407). Default: `/data/zdd/generations.json`. Both service configurations load `/etc/carlos/host.env`.

Stale entries over-pin. An orphaned host makes [pins.go:213](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/pins.go:213) reject the reference set, blocking destructive restages box-wide. That is conservative behavior, not another safety defect. It does **not** protect against the separate eviction consumer above.

**New defects**

**N4 — P2: Multiple relative hops lose the configured-path pin beneath a symlinked opt directory.** [staged.go:83](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/staged.go:83).

Consider:

```text
/opt/carlos → /srv/carlos
/opt/carlos/live/h → ../current
/opt/carlos/current → versions/v1/app
```

Previously, `BoxPins` included `/opt/carlos/versions/v1/app`. Now its hops use `/srv/carlos`; the only lexical alias retained is `/opt/carlos/current`. The final configured-path pin disappears.

Without another registry/generation pin, `pins.Has(VersionPath(...))` at [adopt.go:1802](/home/paulca/amadan.net/carlos/platform/.claude/worktrees/zdd-4-release-side/internal/release/adopt.go:1802) returns false, allowing a re-shipped v1 to overwrite the still-referenced executable.

**Smallest fix:** compare candidate and pinned paths using consistent filesystem resolution, failing closed on resolution errors. Add a regression with two relative hops under a symlinked opt directory; the new single-hop test misses this.

**Out of scope**

- Unchanged ordinary drift convergence with `CoalesceRecycle` set remains plan 5 integration.
- Absolute terminal paths through symlinked directories retain the previously reported pinning limitation.
- Other unchanged deferred findings and later-plan scheduler/generation behavior were not re-reviewed.

No files modified or Go tests run. A read-only in-memory path trace confirmed N4’s before/after difference; `git diff --check e77463df..HEAD` passed.