feat/up!: require an active harness-pool generation, no fallback to the legacy install #108

Merged
pti merged 1 commit from feat/up-require-active-generation into main 2026-10-20 00:48:29 +02:00
Owner

Closes #102, first step of the pre-1.0 Phase 4 cutover tracked in #107. Breaking. wraptool up no longer falls back to the per-image or legacy harness install when the selected harness has no active pool generation for the target platform. All three container runtimes now stop with one shared error that names the platform key and the fix, run wraptool harness install name, or wraptool harness sync name --platform key if it is already locked, and a pointer whose generation is missing on disk or names an invalid generation id gets its own line. The check runs in runUp before the token is provisioned, the bind is changed or the shared server starts, so a failed up leaves no server, no worktree record and no .mcp.json behind, and the bring-ups keep a cheap re-check. The devcontainer runtime takes its platform key from the Docker daemon via #106 and prints that notice once. Two more refusals land with the same rule, a declared runtime mount that is missing or not read-only now stops up with the exact mounts snippet to add instead of a notice, because a writable mount lets the harness rewrite its own locked binary and the per-image binary is no longer a fallback. The legacy fallback plans, their deprecation notices and the half-gone and gc'd legacy error branches are deleted, the mixed-state expose of legacy bin and lib beside runtime stays when a live pointer exists until #107 removes it. Every fallback test was converted to an error assertion, none deleted, the full container entry string is pinned byte for byte again, and new runUp-level tests cover each runtime, the mount refusals and the daemon key. Two adversarial reviewer rounds, eleven mutations killed. Design sections 12.1, 12.2 and 16 and the 1.0 note's section 3.5 are updated with the reversal dated today, four docs pages and CLAUDE.md follow, CHANGELOG Changed entry marked breaking, run wraptool harness install name once after upgrading. 🤖 Generated with Claude Code

Closes #102, first step of the pre-1.0 Phase 4 cutover tracked in #107. Breaking. wraptool up no longer falls back to the per-image or legacy harness install when the selected harness has no active pool generation for the target platform. All three container runtimes now stop with one shared error that names the platform key and the fix, run wraptool harness install name, or wraptool harness sync name --platform key if it is already locked, and a pointer whose generation is missing on disk or names an invalid generation id gets its own line. The check runs in runUp before the token is provisioned, the bind is changed or the shared server starts, so a failed up leaves no server, no worktree record and no .mcp.json behind, and the bring-ups keep a cheap re-check. The devcontainer runtime takes its platform key from the Docker daemon via #106 and prints that notice once. Two more refusals land with the same rule, a declared runtime mount that is missing or not read-only now stops up with the exact mounts snippet to add instead of a notice, because a writable mount lets the harness rewrite its own locked binary and the per-image binary is no longer a fallback. The legacy fallback plans, their deprecation notices and the half-gone and gc'd legacy error branches are deleted, the mixed-state expose of legacy bin and lib beside runtime stays when a live pointer exists until #107 removes it. Every fallback test was converted to an error assertion, none deleted, the full container entry string is pinned byte for byte again, and new runUp-level tests cover each runtime, the mount refusals and the daemon key. Two adversarial reviewer rounds, eleven mutations killed. Design sections 12.1, 12.2 and 16 and the 1.0 note's section 3.5 are updated with the reversal dated today, four docs pages and CLAUDE.md follow, CHANGELOG Changed entry marked breaking, run wraptool harness install name once after upgrading. 🤖 Generated with Claude Code
feat(up)!: require an active harness-pool generation, no fallback to the legacy install
All checks were successful
Lint / lint (pull_request) Successful in 1m4s
Lint / sonarqube (pull_request) Has been skipped
c2ff8c2160
Per-image provisioning does not ship in 1.0, so `wraptool up` must stop
treating a missing pool generation as a soft condition. Until now the guix
runtime fell back to the legacy bin/+lib/ tree with a deprecation notice and
the devcontainer runtime printed a notice and ran whatever the image's
feature had installed; only sbx refused. runUp now requires a live active
generation for every container runtime before it provisions the token,
adjusts the bind or starts the server (after the sbx preflight, next to the
sbx pool plan), and passes the resolved pool-plan input down, so a refused
`up` leaves nothing behind. The guix and devcontainer bring-ups re-check it
before writing the harness config or running any container CLI. All three
runtimes print one shared error naming the platform key and the fix:
`wraptool harness install <name>` when there is no usable pointer,
`wraptool harness sync <name> --platform <key>` when the pointer's
generation is gone from disk.

The liveness rule moves into one requireActiveGeneration helper next to
poolPlanInput. It now validates the pointer's generation id
(pool.ValidGenerationID, a single path segment) before building any path
from it, so a tampered pointer such as "../../etc" cannot steer the
existence checks outside runtime/generations/. newPoolPlanInput builds the
real input for every runtime; a platform key that cannot be resolved fails
`up` with that cause instead of continuing with an empty pointer path.
The devcontainer runtime takes that key from the Docker daemon's
architecture (resolveDevcontainerPlatformKey, #106), so the error names the
daemon's platform; guix and sbx keep the host key.

The devcontainer runtime also stops, in the same pre-server preflight and
again in the bring-up, when devcontainer.json does not declare
the read-only runtime/ mount, with the exact mounts entry to add: proceeding
would start a container without the pool binary, and a `:0` container would
silently run its per-image install. A declared runtime/ mount the devcontainer
CLI would not enforce read-only (object form, no readonly token, non-bind
type) now stops `up` too instead of warning: a writable mount would let the
harness rewrite the locked pool (§5). The error quotes the offending entry
as written and gives the read-only replacement.

The legacy-fallback plan builders, the plans' notice fields and the PoolOnly
input field are gone; legacy state only still decides the mixed-state
expose of bin/+lib/ next to runtime/ when a pointer is live, which #107
removes together with the legacy install step.

Breaking: run `wraptool harness install <name>` once per harness after
upgrading, and declare the runtime mount in devcontainer.json. Design doc
§12.1/§12.2/§16, release-1-0 §3.5, the user docs, CLAUDE.md and the
changelog record the new contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
pti merged commit eb33d2418d into main 2026-10-20 00:48:29 +02:00
pti deleted branch feat/up-require-active-generation 2026-10-20 00:48:29 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
pti/wraptool!108
No description provided.