worktree-controller 1.30.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. worktree_controller/__init__.py +3 -0
  2. worktree_controller/activation_layout.py +483 -0
  3. worktree_controller/branch_retirement.py +1865 -0
  4. worktree_controller/canonical_refresh.py +150 -0
  5. worktree_controller/canonical_transition.py +324 -0
  6. worktree_controller/cleanup_attempts.py +442 -0
  7. worktree_controller/cleanup_evidence.py +75 -0
  8. worktree_controller/cleanup_recovery.py +912 -0
  9. worktree_controller/cli.py +1907 -0
  10. worktree_controller/controller.py +1974 -0
  11. worktree_controller/convergence.py +893 -0
  12. worktree_controller/delivered_closeout.py +928 -0
  13. worktree_controller/disposable.py +677 -0
  14. worktree_controller/environment.py +1038 -0
  15. worktree_controller/frozen_recovery.py +291 -0
  16. worktree_controller/git_backend.py +682 -0
  17. worktree_controller/governance.py +365 -0
  18. worktree_controller/hardlinks.py +294 -0
  19. worktree_controller/integration_cleanup.py +92 -0
  20. worktree_controller/integration_conflict.py +117 -0
  21. worktree_controller/integration_retry.py +526 -0
  22. worktree_controller/keyed_document_scope.py +162 -0
  23. worktree_controller/lifecycle.py +8992 -0
  24. worktree_controller/lifecycle_history.py +358 -0
  25. worktree_controller/lifecycle_observation.py +234 -0
  26. worktree_controller/metadata_codec.py +1694 -0
  27. worktree_controller/metadata_relocation.py +131 -0
  28. worktree_controller/models.py +546 -0
  29. worktree_controller/mount_diagnostics.py +324 -0
  30. worktree_controller/multiple_cleanup_recovery.py +730 -0
  31. worktree_controller/namespace_migration.py +1412 -0
  32. worktree_controller/native_session_recovery.py +171 -0
  33. worktree_controller/owner_actions.py +34 -0
  34. worktree_controller/policy.py +333 -0
  35. worktree_controller/preservation.py +983 -0
  36. worktree_controller/preserved_cleanup.py +849 -0
  37. worktree_controller/publication_execution.py +361 -0
  38. worktree_controller/reconciler.py +430 -0
  39. worktree_controller/recovery_guidance.py +205 -0
  40. worktree_controller/registration_backfill.py +166 -0
  41. worktree_controller/registration_prune.py +391 -0
  42. worktree_controller/rendering.py +179 -0
  43. worktree_controller/resource_admission.py +1736 -0
  44. worktree_controller/retirement_adapters.py +130 -0
  45. worktree_controller/retirement_execution.py +234 -0
  46. worktree_controller/semantics.py +459 -0
  47. worktree_controller/session.py +505 -0
  48. worktree_controller/session_admission.py +265 -0
  49. worktree_controller/source_cleanup.py +515 -0
  50. worktree_controller/source_preservation.py +692 -0
  51. worktree_controller/source_recovery.py +592 -0
  52. worktree_controller/store.py +1228 -0
  53. worktree_controller/tool_cli_contract.json +1791 -0
  54. worktree_controller/transaction_scope.py +876 -0
  55. worktree_controller/writer_lease.py +662 -0
  56. worktree_controller/writer_session.py +887 -0
  57. worktree_controller-1.30.4.dist-info/METADATA +3149 -0
  58. worktree_controller-1.30.4.dist-info/RECORD +61 -0
  59. worktree_controller-1.30.4.dist-info/WHEEL +4 -0
  60. worktree_controller-1.30.4.dist-info/entry_points.txt +5 -0
  61. worktree_controller-1.30.4.dist-info/licenses/LICENSE +202 -0
@@ -0,0 +1,3149 @@
1
+ Metadata-Version: 2.4
2
+ Name: worktree-controller
3
+ Version: 1.30.4
4
+ Summary: Govern Git worktree budgets, authority, ownership, and lifecycle
5
+ License-Expression: Apache-2.0
6
+ License-File: LICENSE
7
+ Classifier: Operating System :: POSIX :: Linux
8
+ Classifier: Programming Language :: Python :: 3.12
9
+ Requires-Dist: websockets>=15,<16
10
+ Requires-Python: >=3.12, <3.13
11
+ Description-Content-Type: text/markdown
12
+
13
+ # Worktree Controller
14
+
15
+ `worktree-controller` is the independent control plane for Git worktree budget, allocation authority,
16
+ ownership, integration-candidate evidence, serialized main-publication authority, recovery, and
17
+ guarded checkout release. Git remains authoritative for registered worktrees, paths, branches,
18
+ HEADs, refs, index state, and cleanliness.
19
+
20
+ The controller stores only non-Git facts under the dynamically resolved Git common directory:
21
+
22
+ ```text
23
+ <git-common-dir>/worktree-controller/v1/
24
+ ```
25
+
26
+ Repository policy is read from tracked `.agents/worktree-policy.toml`. The JSON interface is schema
27
+ version 1 and ordinary commands expose `tool_version`, `schema_version`, `command`, `repository`, `decision`,
28
+ `worktrees`, `warnings`, and `errors`.
29
+
30
+ Licensed under the [Apache License 2.0](LICENSE).
31
+
32
+ Start with the [operations guide](docs/operations.md) for development, handoff, resource waits,
33
+ release and deployment, and the [architecture guide](docs/architecture.md) for module ownership
34
+ and maintenance boundaries. Exact interfaces remain in the [CLI contract](tool_cli_contract.json).
35
+
36
+ Current release: **1.30.4**, CLI contract **60**, storage layout **5**. Software, CLI,
37
+ capability and storage versions are independent compatibility requirements.
38
+
39
+ ## Installation
40
+
41
+ Worktree Controller runs on **Linux** with **Python 3.12** (`>=3.12,<3.13`), **Git**, and a
42
+ readable `/proc` (procfs). Writer identity, runtime evidence and repository locking use Linux
43
+ process interfaces and POSIX `flock`; other operating systems and Python versions are not
44
+ supported by this release.
45
+
46
+ Install the published distribution with pip:
47
+
48
+ ```bash
49
+ python3.12 -m pip install worktree-controller
50
+ ```
51
+
52
+ Or install from a source checkout with [uv](https://docs.astral.sh/uv/):
53
+
54
+ ```bash
55
+ git clone <repository-url> wc && cd wc
56
+ uv sync --locked --python 3.12
57
+ ```
58
+
59
+ Confirm the install. The CLI reports its software version, and `capabilities` reports the
60
+ independent CLI contract and storage layout versions:
61
+
62
+ ```bash
63
+ worktree-controller --version
64
+ worktree-controller capabilities --json
65
+ ```
66
+
67
+ Three console scripts are installed: `worktree-controller` (the control plane),
68
+ `worktree-session` (the managed writer-session handshake) and `worktree-session-end`
69
+ (session closeout).
70
+
71
+ ## Quickstart
72
+
73
+ This walkthrough uses a throwaway repository and a fake identity; it never touches an existing
74
+ project. The Controller only adds authority and lifecycle on top of Git facts — Git stays
75
+ authoritative for worktrees, paths, branches, HEADs, refs and cleanliness. Replace each
76
+ `<...>` placeholder with the value printed by the previous command.
77
+
78
+ 1. Create a synthetic repository and enable policy. The Controller reads repository policy from a
79
+ tracked `.agents/worktree-policy.toml`; copy the example shipped in this repository:
80
+
81
+ ```bash
82
+ mkdir -p /tmp/wc-demo/.agents && cd /tmp/wc-demo
83
+ git init --initial-branch=main
84
+ git config user.email demo@example.invalid
85
+ git config user.name "WC Demo"
86
+ cp /path/to/worktree-controller/.agents/worktree-policy.toml .agents/
87
+ printf '# demo\n' > README.md
88
+ git add -A && git commit -m "initial"
89
+ ```
90
+
91
+ 2. Observe the repository. `status` reports the canonical checkout, the resource budget and any
92
+ allocations; `doctor` checks metadata integrity:
93
+
94
+ ```bash
95
+ worktree-controller status --repo /tmp/wc-demo --json
96
+ ```
97
+
98
+ 3. Preview admission with `plan` (read-only), then `acquire` a governed feature checkout on a new
99
+ branch from a base ref, declaring the repository-relative paths it may touch (`--scope`):
100
+
101
+ ```bash
102
+ worktree-controller plan --repo /tmp/wc-demo --task-key demo-task \
103
+ --authority-key demo-authority --module demo --role feature --json
104
+ worktree-controller acquire --repo /tmp/wc-demo --task-key demo-task \
105
+ --authority-key demo-authority --module demo --role feature --owner demo-owner \
106
+ --branch feat/demo --base-ref main --scope README.md --json
107
+ ```
108
+
109
+ 4. Bind a writer lease to the real, live process that will execute the writes, then require the
110
+ write guard to allow the exact operation before editing:
111
+
112
+ ```bash
113
+ worktree-controller writer-lease-acquire --repo /tmp/wc-demo --path <checkout-path> \
114
+ --owner demo-owner --client-id demo-client --holder-pid <real-pid> --json
115
+ worktree-controller write-guard --repo /tmp/wc-demo --path <checkout-path> \
116
+ --task-key demo-task --authority-key demo-authority --owner demo-owner \
117
+ --holder <real-pid> --operation feature-write --json
118
+ ```
119
+
120
+ 5. Do the work and commit it. A real delivery uses the companion **Git Finalizer** to produce the
121
+ commit/publication evidence, then returns the checkout to the repository control plane with
122
+ `handoff`, recording the exact commit and scope:
123
+
124
+ ```bash
125
+ worktree-controller handoff --repo /tmp/wc-demo --path <checkout-path> \
126
+ --expected-record-version <n> --kind integration-ready --commit <commit-oid> \
127
+ --finalizer-record-id <finalizer-record-id> --scope-changed --json
128
+ ```
129
+
130
+ 6. If a checkout is blocked, do not guess or force it. `recovery-guide` is a read-only explanation
131
+ of the observed state, its blockers and the next formal step:
132
+
133
+ ```bash
134
+ worktree-controller recovery-guide --repo /tmp/wc-demo --path <checkout-path> --json
135
+ ```
136
+
137
+ 7. At session end, reconcile every checkout the session leased. Use `--check` first (read-only),
138
+ then end with `--require-complete`; read each per-allocation decision and
139
+ `summary.terminal_complete`, not the exit code alone:
140
+
141
+ ```bash
142
+ worktree-session-end --path /tmp/wc-demo --client-id demo-client --holder <real-pid> \
143
+ --check --expected-allocation-id <allocation-id>
144
+ ```
145
+
146
+ Integration into main, resource waits, aborted-integration recovery, branch retirement and
147
+ guarded release are covered in the [operations guide](docs/operations.md). Module ownership and
148
+ maintenance boundaries are in the [architecture guide](docs/architecture.md); exact command,
149
+ capability and result schemas live in the [CLI contract](tool_cli_contract.json).
150
+
151
+ ## Optional integrations and dependencies
152
+
153
+ The core control plane has a single runtime dependency (`websockets>=15,<16`) and works standalone
154
+ against any local Git repository on the supported platform. Everything below is optional and is
155
+ exercised only when the corresponding context or companion tool is present:
156
+
157
+ - **Companion delivery tools.** WC composes with, but does not require or embed, the separate
158
+ **Context Loader** (context reading), **Snapshot Runner** (read-only review evidence) and
159
+ **Git Finalizer** (commit and publication). Each owns its own authority; WC never merges,
160
+ publishes or commits on their behalf. The handoff and integration flows in
161
+ [docs/operations.md](docs/operations.md) assume Git Finalizer is installed for delivery evidence.
162
+ - **Client-specific recovery.** The `archived_codex_binding_recovery_version` capability
163
+ (`--recover-archived-codex-session`) recovers a released writer lease whose original session
164
+ belonged to the Codex CLI client, by verifying that client's own archived-session and
165
+ app-server-control state under the user's home directory. It is inert for other clients.
166
+ - **Keyed document scope.** The `integration_keyed_document_scope_version` capability proves
167
+ independent progress-document records from frozen Git objects for one specific known handbook
168
+ profile (`repo_id` `quant-platform`, schema 2). It activates only for that exact document profile
169
+ and otherwise leaves the ordinary file-scope fence unchanged.
170
+ - **Legacy namespace migration.** `namespace_migration` performs the one-time move of a repository's
171
+ historical `codex-worktree/v1` metadata namespace to the current `worktree-controller/v1` layout.
172
+ Repositories created under the current namespace never invoke it.
173
+ - **Maintainer deployment.** `scripts/controller-activate` and `scripts/controller-runtime` are the
174
+ source-owned runtime activation and start entry points used by a maintainer's host; they are not
175
+ required to install or use the published package.
176
+
177
+ From 1.30.1, a completed cleanup at a retained source may survive normal park/resume
178
+ cycles followed by a formal handoff of the same HEAD and owner. Verification requires
179
+ the complete exact-CAS history, original cleanup identity, action proofs and preserved
180
+ generations. It rewrites no receipt and accepts no arbitrary CAS or HEAD drift; normal
181
+ runtime, lease and release guards still apply.
182
+
183
+ From 1.30.0, read-only `recovery-guide` and explicit expected-allocation coverage are available for
184
+ strict session closeout. Both are additive capabilities on CLI 60/storage 5; inspect installed
185
+ capabilities before use. See [recovery and closeout](docs/operations.md#recovery-guidance-and-session-closeout).
186
+
187
+ From 1.30.2, `recovery-guide --intent-id` also explains an already published and released
188
+ integration through complete matching immutable evidence. It points to lifecycle history instead
189
+ of retrying a completed integration. Incomplete or drifted evidence remains blocked.
190
+
191
+ From 1.29.2, scope attribution accepts a retained formal publication reflog suffix
192
+ only when it includes the exact frozen task base and every retained transition is
193
+ continuous and forward, ending at the current formal ref. Git may expire older
194
+ entries without making already-published background changes part of the task's
195
+ scope. A suffix missing the task base, a broken or rewritten chain, unavailable
196
+ history, replacement graphs, symbolic refs and ambiguous merge bases still keep
197
+ the frozen comparison bound. No reflog, scope or allocation metadata is rewritten.
198
+
199
+ Capability `aborted_integration_preserved_cleanup_version` 1 extends the existing exact
200
+ preservation and typed cleanup interfaces to explicitly aborted unpublished integrations.
201
+ Every removed tree needs immutable preservation coverage; quarantine, request identity,
202
+ leases, runtime, ownership and release guards remain mandatory. Only formal checkout release
203
+ settles its admission reservation. See the [operational flow](docs/operations.md#aborted-integration-with-preserved-build-output).
204
+
205
+ The supported runtime is Python 3.12 on Linux with Git and readable procfs. Writer identity,
206
+ runtime evidence and repository locking depend on Linux process interfaces and POSIX flock;
207
+ other operating systems are not supported by this release.
208
+
209
+ From 1.28.2, `reconcile release-plans` preserves every plan referenced by a durable release
210
+ receipt. Closed lifecycle queries and branch retirement continue to use the original plan bytes;
211
+ only unreferenced leftover plans for already released allocations may be retired. Missing
212
+ historical plans still require their original evidence and are never inferred from Git ancestry.
213
+
214
+ From 1.28.1, admission reconciliation observes completed checkout requests in one batch per
215
+ repository under a shared repository lock. Host-ledger accounting and execution observations
216
+ also use shared locks: they do not change repository metadata. This avoids unnecessary writer
217
+ lock contention with status/history readers and repeated full Git/metadata scans. Snapshots
218
+ never survive their held lock or authorize a later queued acquisition. Acquire recovery that
219
+ changes repository evidence still takes an exclusive lock; runtime removal and new acquisitions
220
+ retain their original guarded mutation paths. Missing locks, registration or owner drift,
221
+ uncertain transactions and incomplete release evidence still block reconciliation.
222
+
223
+ From 1.28.0, typed `resource-clean` verifies the hardlink topology before it marks an action
224
+ `STARTED`, so a topology refusal leaves no action evidence and `resource-recover --stage
225
+ reconcile-typed` closes it as a no-op. A journal left by an earlier runtime, whose first action is
226
+ `FAILED_OR_PARTIAL` with `HARDLINK_TOPOLOGY_BLOCKED` and whose only evidence is that action's
227
+ `0000-before.json`, is accepted too: unchanged whole-tree resource evidence and in-scope topology
228
+ prove no deletion began. ctime/nlink drift is tolerated only on inodes that have links outside the
229
+ scope before and after (another environment sharing the files). Capability
230
+ `typed_cleanup_verify_boundary_version` 1.
231
+
232
+ From 1.27.0, `reconcile binding-registrations --dry-run` lists the live linked bindings written
233
+ before 1.26.0 and the evidence for each; applying the same command with `--expected-plan-id`
234
+ writes their registration marker and document. A binding qualifies only when its record exists, its
235
+ Git `gitdir` pointer names the observed checkout, no marker is already present, Git created the
236
+ registration no later than 900 seconds after the allocation's `created_at`, and any recorded base
237
+ commit is in the checkout's history. Refused bindings are listed with typed blockers and keep their
238
+ legacy meaning. Records, bindings and history are unchanged. Capability
239
+ `binding_registration_backfill_version` 1.
240
+
241
+ From 1.26.0, a linked binding belongs to the exact Git registration it was written for.
242
+ `acquire` and `adopt` write a `worktree-registration/v1` token into the worktree's Git
243
+ administrative directory (`worktree-controller-registration`) and record it under
244
+ `binding-registrations/`. If the checkout is removed outside the Controller and Git later
245
+ registers another worktree under the same administrative name, the token is missing: the binding
246
+ is withdrawn from every snapshot, `status` warns `BINDING_IDENTITY_DRIFT`, and the new checkout is
247
+ unmanaged, so write-guard, writer leases and release refuse it instead of granting the old task's
248
+ authority. A binding whose administrative directory is gone keeps the existing `ORPHAN_BINDING`
249
+ meaning, and bindings written before 1.26.0 keep their previous meaning until
250
+ `reconcile binding-registrations` ties them to their registration (1.27.0, below). Git commands run by the
251
+ Controller ignore inherited repository selection (`GIT_DIR`, `GIT_WORK_TREE`, `GIT_INDEX_FILE`,
252
+ `GIT_COMMON_DIR`, `GIT_OBJECT_DIRECTORY` and related variables), so a call from a Git hook observes
253
+ the repository it names. An unreadable metadata directory now fails closed
254
+ (`FILESYSTEM_PERMISSION`/`INVALID_METADATA`) instead of reading as empty. The release runtime scan
255
+ also refuses a checkout whose path or inode changes during the scan; same-UID processes whose procfs
256
+ entries deny access remain host visibility gaps, as in typed cleanup. Storage layout 5 declares the
257
+ existing `cleanup-actions`, `cleanup-recoveries`, `native-session-recoveries` and
258
+ `session-closeouts` directories and adds `binding-registrations`; closeout observations whose holder
259
+ is provably gone are pruned. CLI contract 57.
260
+
261
+ From 1.22.4, direct `writer-session check` and hosted `host-check` do not retain decoded
262
+ metadata or historical mapping caches during their single-snapshot decisions. Each new call
263
+ still reads and validates every metadata file under the same locks, with unchanged byte decoding,
264
+ JSON error details and authority checks. Commands that reload snapshots retain command-local reuse.
265
+
266
+ From 1.22.3, direct `writer-session check` holds one shared repository lock through its guard
267
+ and handoff continuation. It loads one snapshot instead of reloading after a refusal, so concurrent
268
+ Controller lease/lifecycle changes wait for the decision. Each new call still reads and validates
269
+ current metadata; grants, refusals, CLI contract 54 and storage layout 4 are unchanged.
270
+
271
+ From 1.22.2, runtime scans compare path boundaries without repeatedly splitting ordinary procfs
272
+ paths for each checkout root. Lexical path rules, deleted references, scan scope and fresh release
273
+ checks are unchanged. Five alternating pairs of three host-namespace acceptance cases had median
274
+ times of 18.90 / 14.04 seconds before / after (26% lower); this local shared-host sample does not
275
+ establish a full-suite or portable latency improvement. CLI contract 54 and storage layout 4.
276
+
277
+ From 1.22.1, an entitlement check decodes repository metadata once instead of three times: the
278
+ caller's locked snapshot is passed to the write guard, and governance discovery leaves validation to
279
+ that load. Decisions and fail-closed behavior are unchanged; the median hook check dropped from
280
+ about 0.47 s to 0.31 s on a repository with about 20,000 metadata records. CLI contract 54.
281
+
282
+ From 1.22.0, `writer-session check` accepts the same `--access`/`--target` continuation for a
283
+ direct (non-hosted) holder, and a frozen holder may also write into another checkout whose writer
284
+ lease the same live process holds, subject to that checkout's own feature-write guard. Entitlement
285
+ checks skip the full status scan (about a 0.5 s saving per hook call). `handoff --scope-changed`
286
+ records the exact paths changed since the publication base; an integration-ready handoff of
287
+ unscoped changed work now returns warning `MISSING_HANDOFF_SCOPE` instead of failing later at
288
+ `integration-enqueue`. A consumer may report `required_controller_capabilities`; the rollout gate
289
+ refuses activation or rollback to a runtime that lacks any declared minimum. Capability
290
+ `host_handoff_continuation_version` 2; CLI contract 54, storage layout 4.
291
+
292
+ From 1.21.0, a host that names an access class keeps its own frozen handoff's Controller flow.
293
+ `writer-session host-check <path> --access read|exec|write [--target <absolute-path>]` returns
294
+ `CONTINUATION_GRANTED` (`HOST_HANDOFF_CONTINUATION`) only to the exact live holder of the current
295
+ lease on its own `HANDOFF_READY` or `PRESERVED` feature allocation. Reads and commands continue;
296
+ writes continue only outside Controller governance or into an integration candidate built from
297
+ that allocation. The frozen checkout, other checkouts and checks without `--access` keep their
298
+ refusal, and HEAD drift, a missing lease or a foreign holder refuse every class. Capability
299
+ `host_handoff_continuation_version` 1; CLI contract 53, storage layout 4.
300
+
301
+ From 1.20.3, session-end reconciliation pins the selected allocation and original record
302
+ version. Terminal parking checks for a writer lease under the same repository lock as
303
+ the state change, including a new lease whose runtime binding has not yet been written.
304
+ A delayed hook reports its per-allocation refusal and preserves the new writer; path-only
305
+ hooks also refuse a current writer. Explicit owner `park` keeps its existing semantics.
306
+
307
+ From 1.20.2, `release` and `lease-complete` resolve a stable canonical repository context
308
+ before checkout removal. Passing `--repo` from the released checkout or its subdirectory
309
+ completes receipt writes, publication acknowledgement and source release in the same call.
310
+ Release guards, record CAS, retained refs and exact recovery identity remain unchanged.
311
+
312
+ From 1.20.1, command-local history reuse retains a private copy of validated metadata mappings.
313
+ Each snapshot still scans current directory entries and checks every file's identity; added,
314
+ removed or changed files use the existing decode and filename validation path. Snapshot mappings
315
+ remain independent, and no history cache survives the command. Locking, authority rechecks,
316
+ durable writes, CLI contract and storage layout are unchanged.
317
+
318
+ From 1.19.23, nonempty metadata string validation reuses its first whitespace-normalization
319
+ result. Accepted text, empty/NUL rejection and error priority stay unchanged. Snapshot freshness,
320
+ schema checks, command-local decode reuse and durable write boundaries retain their existing gates.
321
+
322
+ From 1.20.0, `integration_keyed_document_scope_version` 1 proves independent QP Handbook
323
+ record changes from frozen Git objects. Distinct records may prepare concurrently; source scope,
324
+ ownership, validation and serialized main publication retain their existing authority.
325
+
326
+ From 1.19.20, uncached historical metadata reads open each fresh directory entry directly,
327
+ avoiding a per-file `Path` construction. UTF-8 text/newline handling, JSON object checks,
328
+ errors and full metadata validation are unchanged. Each snapshot scans current directory
329
+ entries; command-local decode reuse keeps its existing file-identity checks and lifetime.
330
+
331
+ From 1.19.18, optional metadata UUIDs in lowercase hyphenated form are validated by a
332
+ compiled ASCII pattern. All other inputs retain the standard-library UUID validator, including
333
+ legacy accepted formats and their original text after outer whitespace is stripped. There are no
334
+ UUID version or variant restrictions. Schema validation, errors, snapshot freshness and
335
+ command-local cache lifetime are unchanged.
336
+
337
+ Git evidence uses the stored commit graph with replacement objects disabled. Inventory accepts
338
+ Unicode ref names and literal rename/copy paths. Separate Git directories are resolved through Git;
339
+ when invoking from a linked checkout, configure `core.worktree` with the canonical checkout path.
340
+ Metadata timestamps retain the UTC+08:00 offset without a system timezone database requirement.
341
+ Expected filesystem failures on the CLI return the ordinary error envelope (`FILESYSTEM_ERROR`),
342
+ including under `--json`.
343
+
344
+ Storage layout v4 declares the existing `registration-prune-receipts/` directory introduced in
345
+ 1.9.2; upgrading the contract does not move data. Controller writes each
346
+ `registration-prune/v1` receipt once and refuses a different document at the same path. A receipt
347
+ is authoritative proof when classifying an interrupted registration-prune transaction as terminal,
348
+ but it is not repository identity or lock authority. The receipt records the reviewed GC plan id;
349
+ its bytes are not an input to that earlier plan digest. Namespace migration includes these JSON
350
+ documents in its merged-content digest and moves or verifies them byte for byte. The separate
351
+ `metadata-reference-relocation/v1` cleanup inventory does not include this directory because it
352
+ has no historical cleanup reference. Existing v3 readers can continue reading their known paths;
353
+ consumers that pin the exact layout must accept v4 before activation.
354
+
355
+ ## Resource admission and completion
356
+
357
+ From 1.1.0, checkout and writer counts are statistics across every readable policy
358
+ schema. One per-user host ledger under
359
+ `$XDG_STATE_HOME/worktree-controller/resource-admission/v1` atomically reserves
360
+ filesystem bytes/inodes, expected growth and preservation space across repositories.
361
+ Existing occupied space is already reflected by kernel counters; private materialized
362
+ blocks receive credit without charging shared caches or hardlinks twice. Execution
363
+ uses a memory budget and CPU/memory/I/O pressure hysteresis. Initial checkout growth
364
+ is 2 GiB and 100,000 inodes, with 16 GiB/100,000 inode filesystem headroom; execution
365
+ initially reserves 2 GiB with 4 GiB available-memory headroom. New admission tightens
366
+ under pressure; protected running work is not preempted.
367
+
368
+ From 1.19.17, host ledger saves use sorted-key compact UTF-8 JSON with a final newline.
369
+ Existing indented ledgers remain readable, and readers do not rewrite them. Request and holder
370
+ digests, ledger contract v1, save boundaries, locking, private file permissions, file fsync,
371
+ atomic replacement and directory fsync are unchanged. Repository metadata and immutable receipts
372
+ retain their existing encoding.
373
+
374
+ `admission-queue --all-repositories` observes durable requests; `--apply` reconciles
375
+ through ordinary acquisition and recovery. Waiting grants no writer ownership.
376
+ Agent Workspace's existing process reaper periodically forwards this reconciliation
377
+ and approved release-intent reconciliation, including after a restart. Request keys,
378
+ allocation identities and formal receipts prevent duplicate creation or action replay.
379
+ An attempted operation with uncertain effects requires formal reconciliation.
380
+
381
+ From 1.29.1, an explicitly abandoned integration intent settles its matching, unattempted
382
+ checkout request as `CANCELLED`. Reconciliation verifies the original owner and frozen source
383
+ identity, no transaction/allocation, and absent target and branch under the repository lock,
384
+ then compares the exact ledger row before saving the terminal event. Dry-run reports the same
385
+ decision without writing. Attempted or uncertain requests stay blocked; this does not complete
386
+ the Task or change its source handoff.
387
+
388
+ With `integration_lineage_admission_version` 1, a queued integration task may share its frozen
389
+ feature/audit/hotfix/emergency source authority. Controller verifies that exact intent and source
390
+ lineage under the repository lock before reserving another checkout; the source request, frozen
391
+ evidence, scope and queue position remain intact. Unrelated or same-task request drift is still
392
+ refused. Reuse the original intent and authority. If a durable resource request is already waiting
393
+ and main has advanced, replay that request through `admission-queue --repo <canonical-repo> --apply`
394
+ before preparation/refresh; a fresh claim can otherwise report `ADMISSION_REQUEST_DRIFT`.
395
+ See the [resource-wait recovery sequence](docs/operations.md#resource-wait-and-main-advancing).
396
+ A repeated exact claim of an unchanged clean `PREPARING_INTEGRATION` candidate returns its existing
397
+ allocation without another reservation, event or publication lease. A resource wait reports
398
+ `RESOURCE_WAIT` with the original queued intent and no candidate; scope fences remain separate.
399
+
400
+ Failed acquisition retains its journal whenever Git effects are partial or uncertain. Only the
401
+ existing positive no-effect classifier permits archiving the exact journal in the host request's
402
+ history and making its reservation retryable. Proof is saved before journal removal; interrupted
403
+ settlement resumes through `admission-queue --apply`, without replaying the acquire in that pass.
404
+ An exact guarded release remains terminal after its retained branch is retired through a matching
405
+ Controller authorization and observed deletion receipt. The release record, receipt, allocation,
406
+ branch and OID must agree; missing or consumer-only retirement proof retains the acquire fence.
407
+ During settlement the exact open authorization can supply that proof, while its independent
408
+ outstanding-authorization fence remains until completion. Historical journals stay unchanged.
409
+ `acquire_no_effect_recovery_version=1` declares this recovery support. While archived proof still
410
+ belongs to a reserved attempt, runtime switching requires that capability; settle the attempt before
411
+ rolling back to an older runtime.
412
+ Standalone execution admission reconciles finished execution bindings through the existing
413
+ holder, descendant and runtime checks when that can change its answer: a request that would wait, or
414
+ one refused because an earlier execution of the same task is still recorded, reconciles and is asked
415
+ once more with the same durable request. An admission that is immediately available proceeds without
416
+ that pass and leaves finished executions to their host's own exit handling and the periodic reaper.
417
+ From 1.19.11, standalone execution admission and cancellation do not retain decoded metadata on
418
+ their first, single-snapshot attempt. Admission enables command-local decode reuse only if it
419
+ needs recovery, which can decode unchanged history twice. Composite session admission keeps
420
+ its existing cache across plan, acquisition and execution admission. Validation and guards are
421
+ unchanged; this does not guarantee faster recovery.
422
+ Live or uncertain processes keep their budgets. A final exited session can be settled by `admission-queue --all-repositories --apply`
423
+ or the existing external reaper; each newly launched execution uses a new run identity.
424
+ That pre-admission pass skips a reservation whose exact recorded holder is still running: it has
425
+ nothing to recover yet, and its repository is neither scanned nor locked until the holder exits.
426
+ An execution admitted but never bound to a holder keeps its reservation while its allocation is held,
427
+ because a launch may still be in flight. Formal allocation release settles it with the allocation's
428
+ release receipt; the same passes settle one left behind by an older release, only with that unique
429
+ receipt and the existing Git, path and journal checks.
430
+ A checkout budget whose repository no longer exists settles only in the all-repositories
431
+ `admission-queue --apply` pass, and only on positive absence: the repository, its common directory
432
+ and the checkout path each report ENOENT, and each one's nearest existing ancestor is a real directory
433
+ (not a symlink) on the very filesystem the budget was reserved on. The row records that
434
+ `vanished-repository/v1` evidence. Anything present, any failed observation (EACCES, EIO, ENOTDIR and
435
+ the like), or an ancestor on another filesystem keeps the reservation.
436
+ The same fresh absence proof excludes a vanished checkout and its exact terminal execution
437
+ from repository maintenance enrollment. Historical rows for live repositories still enroll them,
438
+ so approved release intents remain discoverable after the last admission has settled. Unmatched
439
+ or nonterminal executions retain their own observations and budgets.
440
+
441
+ Executor hosts with capability `session_admission_version` 1 may admit a session in two
442
+ commands instead of one command per step. `session-admit` runs `plan`, `acquire` and
443
+ `execution-admit` in one process for a new feature allocation acquired from the canonical
444
+ checkout, and answers every other start (an existing allocation for the task, a non-canonical
445
+ workspace, a plan other than `CREATE_ALLOWED`) with `SESSION_ADMISSION_FALLBACK` without changing
446
+ anything. `session-bind --holder-pid PID` then runs `writer-lease-acquire` for that exact live
447
+ process, the holder-aware `write-guard` and the runtime `binding-add`. Each step is the existing
448
+ command's own function with unchanged guards, journals, receipts and record-version CAS; only
449
+ the repository status every intermediate command would attach is omitted. A bind that fails after
450
+ its lease was granted attempts to release that lease and reports `decision.lease_outcome` for
451
+ Controller and filesystem errors. If release also fails, the outcome retains the exact lease and
452
+ holder identity for recovery; it does not report a successful release. If the execution budget
453
+ was recorded before the runtime binding write failed or was interrupted, that budget stays
454
+ occupied while the holder or its process group is alive, even when lease compensation succeeds.
455
+ After runtime exit, `admission-queue --apply` settles the budget through the existing runtime-clear
456
+ gates without completing the task; a later run resumes the same allocation when needed and
457
+ requests its own execution admission. If the runtime binding was durably written but the
458
+ `SESSION_BOUND` response was lost, retrying with the exact live holder, client/session/run
459
+ identity and binding kind reuses the same lease ID, binding and execution budget; the existing
460
+ lease renewal may update its renewal time and lease record version. A different live holder
461
+ is refused with `WRITER_LEASE_HELD`; a different session/run identity is refused with
462
+ `EXTERNAL_BINDING`, preserving the original binding and budget. Exit recovery still waits for
463
+ the holder and its process group to finish and does not complete the task. If runtime binding
464
+ removal was persisted but the execution-budget release was interrupted or its ledger write
465
+ failed, the budget remains reserved. `admission-queue` observes this pending recovery without
466
+ writing; `admission-queue --apply` rechecks runtime clearance and releases the budget once,
467
+ without restoring a synthetic binding or completing the task. A subsequent session waits while
468
+ the previous holder or its process group is alive, then resumes the retained allocation and
469
+ requests its own budget before binding a new lease. A delayed old `binding-remove` keeps its
470
+ original record-version CAS; stale requests fail with `CAS_MISMATCH`. A queue pass that read the
471
+ old execution before the handover rechecks current runtime occupancy. Neither path can clear
472
+ the new session's binding, lease or budget; replaying an old lease after runtime exit still
473
+ does not release the new lease's budget.
474
+ The detached retention pass that admission starts runs
475
+ after the acquisition and is skipped while one is running or within 300 seconds of the last
476
+ one started.
477
+
478
+ `execution-admit` / `execution-cancel` budget a never-launched managed execution.
479
+ `host-execution` uses the same budget for a process outside a repository; it grants
480
+ no writer lease. `binding-transfer` formally transfers a verified process descendant
481
+ through the existing runtime binding contract. Writer ownership, process occupancy,
482
+ business completion and allocation release remain separate facts.
483
+ Host reservation recovery is scoped to its exact typed holder chain (boot ID, PID,
484
+ start ticks, client/session/run and process group). Another active allocation using
485
+ the same repository or canonical path never keeps a dead reservation alive. Missing
486
+ or malformed identity, any writer lease, and a host target spanning the user home
487
+ or a broader ancestor remain fail-closed. From 1.4.0 a home-or-broader reservation is
488
+ released only by `host-execution --action clear --recover-dead-holder`, which requires the
489
+ caller to present that reservation's own recorded holder digest plus a matching terminal
490
+ record for its session and run, and writes an immutable `host-recoveries/<request-id>.json`
491
+ receipt before the release. An unqualified clear of such a row is still refused.
492
+ The producer refuses it up front: `host-execution --action admit` rejects the home
493
+ root and any of its ancestors, including symlinked aliases, so an unbounded
494
+ reservation cannot be minted again; a concrete working directory is admitted
495
+ unchanged. `host-execution --action inspect` is the read-only recovery
496
+ intelligence surface: for each host-execution reservation owned by the caller's
497
+ uid it publishes the canonical holder bytes (sorted-key compact JSON over
498
+ client_id, pid, boot_id, start_ticks, session_id, run_id) and their SHA-256, the
499
+ live kernel observation proving the recorded holder dead, a target observation
500
+ (`PRESENT`, `ABSENT` or `IDENTITY_CHANGED`), and the terminal-evidence template a
501
+ typed caller must present — no caller needs to read the private ledger layout,
502
+ and inspection never writes ledger bytes. A reservation whose target has
503
+ provably vanished closes through the same typed authority: the orphan's typed
504
+ clear records `target_observation: "ABSENT"` in the single immutable receipt,
505
+ while other actions against a missing target stop with
506
+ `RESOURCE_IDENTITY_CHANGED`. From 1.19.19, normal `cancel` also accepts a missing
507
+ concrete target for an unattempted `WAITING` or `RESERVED` host request owned by
508
+ the caller's uid, with no allocation, holder, runtime binding or writer lease.
509
+ The original request identity remains unchanged; repeat cancellation is idempotent
510
+ and accounts zero memory. A present target must still match its recorded device
511
+ and inode, and an uncertain observation never proves absence. Cancellation grants
512
+ no dead-holder recovery authority or task completion. Crash-replayed identical recovery authority reuses
513
+ the original immutable receipt; differing bytes are always refused.
514
+
515
+ From 1.1.2, acquiring or renewing a writer never creates an execution reservation.
516
+ AW and `worktree-session` admit resources before launch and bind the existing typed
517
+ runtime occupancy before command execution. A waiting session holds no writer.
518
+ The independent terminal wrapper retains its occupancy through parent exit; the
519
+ existing reconciler verifies exit and descendants, clears occupancy, and runs the
520
+ ordinary closeout path. Nested host-managed commands only check their parent's
521
+ full identity. Old writer-only budget records are settled against exact formal
522
+ allocation release receipts, without claiming their coordinator process exited.
523
+
524
+ Status separates `writer_leases_recorded` from `runtime_occupied`. The latter counts
525
+ registered allocations with a `runtime:` binding, including retained and canonical
526
+ allocations; multiple bindings on one allocation count once. `live_executions` is
527
+ retained as an occupancy compatibility alias, with its meaning in
528
+ `live_executions_semantics`. These metadata counts do not observe process liveness
529
+ or imply business completion. A dead historical writer lease is never runtime use.
530
+
531
+ New task resources belong to their recorded owner. With the approved automatic
532
+ feature policy, exact remote-verified, handed-off results carry receipt-bound cleanup
533
+ authority; unknown content and unpublished work remain protected. Persistent data
534
+ must live outside disposable checkouts, or be explicitly preserved before cleanup.
535
+ Only registered reproducible resources and exact verified preservation contracts
536
+ authorize deletion. A normal `worktree-session-end --path <checkout>` resolves its
537
+ allocation and existing evidence itself; it never marks unfinished work successful.
538
+ Stale intents are revalidated into new identities. Integration and publication keep
539
+ their existing serialization; this repository does not enable automatic integration.
540
+
541
+ ## Retention convergence
542
+
543
+ Admission is resource based and does not count checkouts, so the population only stays bounded
544
+ if unused checkouts leave again. From 1.12.0, policy schema 8 adds:
545
+
546
+ ```toml
547
+ [convergence]
548
+ enabled = true
549
+ grace_minutes = 30 # published / no-change / closed checkouts
550
+ idle_hours_max = 72 # unfinished work while the population is within its reference
551
+ idle_hours_min = 6 # floor under pressure
552
+ standing_reserve = 3 # idle checkouts tolerated beside the live ones
553
+ release_unintegrated_handoffs = true # optional, default false (see below)
554
+ ```
555
+
556
+ `reconcile retention --actor <reconciler> [--trigger manual|admission|background] [--apply]`
557
+ reports (default) or applies convergence. Only the `[reconciler]` actor may apply. A checkout is
558
+ live when it holds a writer lease or bindings or a publication lease, or when any process uses
559
+ it. An `ACTIVE` feature/audit allocation with none of that evidence is dormant: clients without
560
+ writer leases end sessions without telling the Controller, so after `idle_hours_max` (pressure
561
+ never shortens it for an `ACTIVE` lane) it is parked exactly as a finished session would park
562
+ itself. Clean, unused feature/audit checkouts, and frozen hotfix handoffs, are routed to an
563
+ existing guarded verb and then to the unchanged `release`:
564
+
565
+ | Facts | Route | Waits |
566
+ | --- | --- | --- |
567
+ | HEAD, base and task commit are in the publication branch | `close-delivered` | grace |
568
+ | HEAD equals base | `complete-no-change` | grace |
569
+ | Frozen feature/audit/hotfix handoff equals HEAD and is published | release | grace |
570
+ | `release_unintegrated_handoffs`: integration-ready feature/audit/hotfix handoff not yet integrated, with task/handoff/Finalizer identities, local branch and `<remote>/<branch>` all equal to HEAD and no open integration intent | release (branch kept) | dynamic idle threshold |
571
+ | Already `RELEASE_READY` / `ARCHIVED` / `NO_CHANGE_COMPLETED` | release | grace |
572
+ | Unfinished commits | `archive-retained` (branch kept) | dynamic idle threshold |
573
+ | `ACTIVE` without lease, process, binding or Git activity | `park`, then the row above that fits | `idle_hours_max` (never shortened by pressure) |
574
+
575
+ The idle threshold is `idle_hours_max` while `registered <= live + standing_reserve`; above that
576
+ it is `max(idle_hours_min, idle_hours_max / pressure^2)`. Idle time is the latest of the
577
+ checkout's Git admin files (HEAD, index, reflog) and its lifecycle events. Dirty, detached,
578
+ locked and missing checkouts are reported as `ATTENTION_REQUIRED` and never touched. An
579
+ unregistered root `.venv` is registered as a reproducible `python-venv`; the release teardown
580
+ preflight still decides whether it may be removed. A hotfix `.venv` is the exception: it needs
581
+ its owner's `handoff-owner-attestation`, so the row stays `BLOCKED` with that recovery command.
582
+ Branches, tags and task evidence remain.
583
+
584
+ Releasing an unintegrated handoff loses only the checkout directory. Its record keeps the frozen
585
+ evidence, and `integration-enqueue` resolves a source without a checkout from its release receipt,
586
+ but only while the retained branch still equals the handoff commit; a moved branch fails with
587
+ `SOURCE_BRANCH_UNAVAILABLE`. Without the switch such handoffs stay `BLOCKED` with a reason that
588
+ names the missing integration step. Release is still not business completion or publication.
589
+
590
+ Every result carries `reason` and `outcome_counts` built from the actual findings, `RELEASED`
591
+ rows carry `receipt_id` / `release_plan_id` of the immutable release evidence, and dirty rows carry
592
+ `changes` plus `owner_action` (review, commit through Git Finalizer or discard explicitly, then
593
+ `park`/`handoff`). Convergence never commits, stashes, resets or discards uncommitted work.
594
+
595
+ Clients whose launch directory is canonical but whose writer leases sit in linked checkouts
596
+ end with `worktree-session-end --path <repo> --client-id <client> --session-id <session> --holder
597
+ ancestor:<program>`: only leases held by exactly that live session are released, and each
598
+ allocation then takes the ordinary terminal route (unfinished work is retained). The hook keeps
599
+ the selected allocation and record CAS: a new writer lease, resumed record or replacement
600
+ allocation blocks terminal parking. Read each allocation's decision; exit status remains advisory
601
+ even when parking is refused. Hosted children never end their parent's allocation, and a repeated
602
+ notification does not repeat lifecycle mutations.
603
+
604
+ With `hosted_session_closeout_check_version` 1, hosted children may run the read-only preparation
605
+ check with their verified host selector. That check never ends, parks or releases an allocation;
606
+ hosted session-ending calls remain refused.
607
+
608
+ With `session_closeout_report_version` 1, run the same selector with `--check` before normal
609
+ exit. It is read-only and returns 2 while files, artifact classification, a Task checkpoint or a
610
+ handoff still need owner work. Include `--run-id` when the lease records one; omitting it never
611
+ inherits a replacement run's lease. `--require-complete` makes the ending call return 2 for an
612
+ incomplete selection or any unsettled allocation. Default hook exit remains 0 for compatibility:
613
+ neither that exit nor `SESSION_ENDED` proves success. Consume `allocations[].terminal_complete`
614
+ and `summary.terminal_complete`. `previous_allocations` re-observes the original selection after
615
+ its lease was released, so retrying an incomplete closeout cannot turn it into an empty success.
616
+ Those persisted observations grant no mutation authority. A retained dirty checkout is preserved,
617
+ not closed.
618
+
619
+ Status, convergence and session results expose `checkout-owner-action/v1` requests carrying the
620
+ allocation, original owner/task/authority, HEAD, expected record CAS and a stable action ID. The
621
+ existing Task consumer matches its binding and re-observes these facts before restoring its owner;
622
+ the request never grants a writer, commit, discard or cleanup. The human `owner_action` convergence
623
+ explanation stays available to older clients.
624
+
625
+ For dependency waiting, persist the checkpoint in the existing Task first. Once its writer and
626
+ bindings end normally, `park --checkpoint-id <content-addressed-artifact> --checkpoint-head <HEAD>
627
+ --waiting-on <dependency>` pins the owner's attestation without claiming content verification or
628
+ completion. A clean retained checkout may immediately use the reviewed `archive-retained` plan
629
+ and ordinary guarded `release`; no idle timeout is required for this owner's explicit closeout.
630
+ Registered resources still use normal teardown; unknown files still block release. The Task keeps
631
+ the dependency, branch and receipt, and obtains a controlled checkout from the exact retained HEAD
632
+ when it resumes. No branch or tag is deleted by waiting or release.
633
+
634
+ Triggers: `acquire` starts a detached pass in its own session (reported as
635
+ `retention_convergence: CONVERGENCE_SCHEDULED`), so it neither slows admission nor can be
636
+ interrupted by a caller's timeout; applying passes are serialized per repository by a
637
+ non-blocking host lock (`CONVERGENCE_BUSY`). Each applying pass writes `RUNNING`, then
638
+ `COMPLETED` (code, reason, counts, released receipt ids) or `FAILED` (error code) beside that
639
+ lock; a dry run returns it as `last_applied_pass`, and a `RUNNING` report whose lock is free reads
640
+ as `INTERRUPTED`. The report is an observation for detached callers, never release evidence.
641
+ The background timer runs `reconcile retention --trigger
642
+ background --apply` beside `reconcile-writers`, which first moves provably dead writers from
643
+ `ACTIVE` to `RETAINED`.
644
+
645
+ ## Commands
646
+
647
+ - `status [--json] [--namespace-pid PID]`: join live Git inventory with controller metadata
648
+ and report advisory lifecycle and environment observations when the read succeeds. If it
649
+ fails, the CLI retains the classified error code; use `doctor` for mount evidence.
650
+ - `plan`: make a read-only reuse/budget/authority decision.
651
+ - `write-guard`: bind the selected allocation to the declared task and operation before writes;
652
+ integration allocations reject ordinary feature implementation.
653
+ - `adopt`: register metadata for an existing real worktree without changing Git.
654
+ - `doctor [--namespace-pid PID]`: distinguish Controller authority or lock failures,
655
+ filesystem permission failures, mount namespace mismatches, and `UNKNOWN`.
656
+ It probes repo, `.git`, Git common dir, lock path and parent, and namespace identity; unavailable
657
+ evidence remains `UNKNOWN`.
658
+ - `gc-plan [--namespace-pid PID] [--path PATH --owner-release OWNER | --intent-id ID]`:
659
+ classify every Git registration with lifecycle, owner, session, lease, filesystem and
660
+ namespace observations; a selected target with release authority also runs the existing
661
+ read-only release preflight.
662
+ - `gc-apply --path PATH --plan-id ID [--owner-release OWNER | --intent-id ID]`: revalidate one
663
+ reviewed plan and execute it through the existing release path and receipt.
664
+ - `acquire`: create one explicitly specified policy-approved linked worktree.
665
+ - `canonical-transition`: move a clean frozen canonical handoff to one exact published commit on
666
+ `main` or `master` selected by repository policy, without releasing the canonical checkout.
667
+ It requires the old record CAS, owner, branch,
668
+ HEAD, local publication branch and remote HEAD, plus explicit new control-plane identity. The
669
+ old handoff branch remains at its commit; the publication branch only fast-forwards. A retry
670
+ accepts the exact partial
671
+ state after the ref or checkout moves, and an invalid target policy restores the old checkout.
672
+ With `canonical_main_refresh_version` 1, a handoff already frozen on the policy publication
673
+ branch uses the same entry with `--expected-branch main` (or `master`), the original frozen
674
+ `--expected-head` and the observed `--expected-main-head`. It validates the immutable published
675
+ policy before advancing the checked-out branch and records `CANONICAL_MAIN_REFRESHED`, preserving
676
+ the old handoff history without an extra baseline ref. Interrupted progress keeps main advanced
677
+ and metadata frozen: retry with exact observed main and remote OIDs after proving their ancestry;
678
+ never rewind main or fabricate a release receipt. Unpublished or divergent main stays blocked.
679
+ - `integration-enqueue` / `integration-queue`: persist and inspect ordered `IntegrationIntentV2`
680
+ records without occupying a checkout; source branch, handoff, Finalizer, Snapshot, and scope
681
+ identities are frozen once. An explicit schema v1 policy can use this manual flow with its
682
+ default `origin/main` publication target; no automatic continuation is enabled.
683
+ - `integration-claim`: claim the oldest selected intent when a candidate slot is available and build
684
+ its exact revision from frozen handoffs plus live main without touching canonical checkout state.
685
+ - `integration-refresh`: rebuild a `NEEDS_REPREPARE` candidate on live main and clear old prepared
686
+ eligibility, or move only that candidate to `ATTENTION_REQUIRED` on a real conflict.
687
+ - `integration-validate`: persist an immutable prepared-candidate receipt binding tests, Snapshot,
688
+ base-main, tree, scope, attempt, and candidate identities.
689
+ - `integration-reopen`: invalidate prepared evidence before allowing more integration writes.
690
+ - `lifecycle-events` / `lifecycle-event-ack`: consume and idempotently acknowledge the durable
691
+ integration lifecycle outbox.
692
+ - `integration-recovery-matrix`: expose evidence, repeat, resume, and fail-closed rules by phase.
693
+ - `integration-equivalent`: bind reviewed equivalent-replay evidence and advance the candidate
694
+ identity before validation.
695
+ - `review`: record owner review or explicit owner transfer under record-version CAS. A clean
696
+ `ACTIVE` source task can repair omitted committed files with `--add-scope <exact-file>`
697
+ (repeatable), `--expected-head <full-oid>` and `--scope-reason <reviewed-reason>`.
698
+ Scope review only adds actual out-of-scope files from the original base-to-HEAD diff and records
699
+ the owner, reason, HEAD, base, CAS and before/after scopes in history. Frozen handoffs require
700
+ `reopen` first; integration scopes and sources bound by active intents cannot be amended.
701
+ The ordinary handoff and publication gates still apply after review.
702
+ - `close-preserved`: close verified archived source without declaring task completion or requiring a fabricated Finalizer record.
703
+ - `close-delivered`: explicitly close a retained or legacy preserved checkout whose HEAD and historical commits
704
+ are already in the live publication branch; retain original task evidence and require
705
+ ordinary guarded release for physical removal.
706
+ - `archive-retained`: preserve a clean unfinished retained task on its exact branch, without
707
+ asserting completion or publication; physical removal still uses guarded release.
708
+ Completed preservation cleanup receipts are frozen by actual receipt ID, path and raw SHA.
709
+ Release rechecks these immutable bytes and the exact archive record transition
710
+ (`after CAS = before CAS + 1`); later unrelated metadata changes require a new reviewed plan.
711
+ Formal abandoned integration references use their existing validated effective terminal identity;
712
+ historical source handoffs must still be ancestors of the retained branch. Other open integration
713
+ obligations remain blockers in archive, delivered closeout and retention convergence.
714
+ - `handoff`: record either `integration-ready` or `preserved` evidence.
715
+ - `quarantine`: isolate a semantically drifted integration candidate.
716
+ - `recovery-adopt`: explicitly move a compatible existing checkout under recovery capacity.
717
+ - `externalize`: bind quarantined lineage to a dedicated recovery allocation and retained branch.
718
+ - `resource-register` / `resource-clean`: classify and explicitly clean a task-local Python
719
+ `.venv` using its reproducibility/creator/teardown contract. In addition to existing active,
720
+ retained and receipt-authorized paths, capability `published_handoff_resource_registration_version`
721
+ 1 allows a published `HANDOFF_READY` hotfix to classify its existing reproducible on-release
722
+ `.venv` with exact owner/CAS and `--creator handoff-owner-attestation:<owner>`. It verifies frozen
723
+ task/handoff/Finalizer identity, live publication and branch/base ancestry, no open integration
724
+ or transaction obligations, and the existing runtime/environment preflight. It appends proof
725
+ to the new resource event without reopening or changing frozen evidence; physical cleanup and
726
+ release still use their existing guards. Explicit `resource-clean --typed` also supports the
727
+ three nested resources described below.
728
+ - Capabilities `retained_typed_cleanup_version` 1 and `preservation_leaf_version` 1 permit explicit
729
+ exact preservation/cleanup on clean `RETAINED` feature/audit allocations and selected ignored
730
+ regular-file or literal symlink leaves. The existing preservation `plan` keeps `--repo`, `--path`,
731
+ `--allocation-id`, `--owner`, `--expected-record-version`, `--plan-file` and exact `--source`.
732
+ Copy and verify use that immutable plan; they do not authorize cleanup. `resource-clean --typed`
733
+ continues to require reviewed plan identity and the existing primary/additional receipt pairs.
734
+ Original ancestor pins and all allocation, runtime, binding, ref and transaction guards remain
735
+ mandatory. A symlink target is not followed or removed; a regular leaf cannot authorize deleting
736
+ its tracked parent. Cleanup, archival and physical release are separate guarded operations.
737
+ - `lease-acquire`: refresh configured main, validate candidate lineage, and acquire the single
738
+ publication lease.
739
+ - `publication-execution-session`: public, provider-neutral JSON-line verification, authorization
740
+ and result observation for an exact leased candidate; the Controller holds the repository lock
741
+ while the caller performs its Git push. See the protocol below.
742
+ - `lease-complete` / `lease-abort`: verify published remote state or release unchanged authority;
743
+ both bind the exact publication run, and completion defaults to receipt-authorized auto-release.
744
+ - `release-intent`: bind one short-lived feature/audit release authority to exact HEAD, record CAS,
745
+ handoff/Finalizer identity, integration identity, and trigger.
746
+ - `reopen`: invalidate handoff and pending release authority before continuing feature work.
747
+ - `binding-add` / `binding-remove`: maintain explicit Agent Workspace/runtime bindings under CAS;
748
+ new bindings require `ACTIVE` lifecycle.
749
+ - `reconcile receipts`: plan or record immutable receipt reconciliation without rewriting history.
750
+ - `reconcile retention`: report or apply delegated retention convergence (see above).
751
+ - `reconcile release-intents`: evaluate only existing intents and, when policy allows, invoke guarded
752
+ release; capacity pressure never discovers arbitrary clean worktrees.
753
+ - `reconcile integration-intents`: plan or converge v1 migration, historical terminal drift, scope
754
+ waits, missing outbox events, and optional dispatch of already queued intents.
755
+ - `release`: build the same guarded `ReleasePlan` for dry-run and execution, remove only the exact
756
+ evidence-cleared linked checkout, and tear down only explicitly registered `on-release` resources;
757
+ branches and tags remain.
758
+ - `acquire-across`: admit one task in every required repository, or in none; `acquire-when-free`:
759
+ acquire, waiting a bounded time for capacity that may free.
760
+ - `adopt-delivered`: adopt an allocation whose deliverable is already in the publication branch.
761
+ - `integration-abort` / `integration-abandon`: terminate a failed or owner-aborted integration
762
+ preparation, or one whose preparation is already gone.
763
+ - `integration-retry`: plan/CAS a retry of the original failed intent after its aborted candidate
764
+ has complete release evidence; optionally bind a verified source-ancestor prerequisite.
765
+ - `park`: retain an unfinished checkout and free its writer slot.
766
+ - `reconcile-session`: terminal reconciliation when a task session stops; `reconcile-writers`:
767
+ report, and optionally converge, provably inactive writer entitlement.
768
+ - `writer-lease-acquire` / `writer-lease-release`: bind write entitlement to a verifiable live
769
+ process, or give it up without asserting the task finished.
770
+ - `admission-queue`: inspect or resume durable resource admission requests across repositories.
771
+ - `execution-admit` / `execution-cancel`: budget a never-launched managed execution.
772
+ - `host-execution`: budget and track a process outside a repository writer lease; `--action
773
+ inspect` is the read-only recovery-intelligence surface, `--action clear --recover-dead-holder`
774
+ is the only authority that closes a provably dead or orphaned historical reservation.
775
+ - `branch-retirement-plan`: after release, classify one exact allocation/branch, recheck checkout,
776
+ allocation, writer/runtime, intent/transaction, protection, upstream, OID, and ancestry state, then
777
+ persist an immutable execution plan only for release-backed `ANCESTRY` integration.
778
+ Released `feature` and `audit` allocations remain eligible. A released `integration` candidate
779
+ additionally needs its exact remote-verified publication receipt, linked by the release plan
780
+ or by a unique typed release acknowledgement after owner release. The acknowledgement must match
781
+ the publication, integration intent and released allocation version. Repository, allocation,
782
+ candidate, Finalizer and policy target identities must match. Missing
783
+ or mismatched evidence is `INTEGRATION_PUBLICATION_NOT_VERIFIED`; all occupancy and CAS guards
784
+ still apply. Release retains the branch; retirement remains a separate authorized operation.
785
+ - `branch-retirement-status` / `branch-retirement-record`: revalidate the plan and file an
786
+ independent local/remote result. Partial completion is expected and safely resumable. A record is
787
+ accepted only when the Controller observes the ref gone and governance unchanged.
788
+ - `branch-retirement-verify`: the published pre-mutation verdict for exactly one operation
789
+ (`EXECUTABLE`, `STALE`, `BLOCKED`, or `COMPLETE`) plus the CAS fields a ref mutation must carry.
790
+ - `namespace-migration-plan` / `namespace-migrate`: report and, on the caller's exact plan id,
791
+ perform the one-time move of a repository's legacy `codex-worktree/v1` metadata namespace to
792
+ `worktree-controller/v1` under the destination repository lock. Idempotent, replayable, and
793
+ fail-closed on conflict or untrusted evidence; see the migration section below.
794
+ - `namespace-migration-receipts` / `namespace-migration-verify`: list a repository's write-once
795
+ migration receipts, and re-derive a completed migration from that evidence instead of trusting a
796
+ flag.
797
+
798
+ The tool never pushes, commits, merges, rebases, resets, performs `git clean`, force-removes, deletes
799
+ a branch/tag, or runs a daemon. `lease-acquire` and `lease-complete` are the only network operations:
800
+ they fetch exactly the configured publication branch into its remote-tracking ref. Actual main
801
+ publication remains Git Finalizer's authority. Legacy `resource-clean --resource-path .venv`
802
+ requires an owner-registered environment. `resource-clean --typed` requires an exact classified
803
+ plan and the separate cleanup contract below.
804
+
805
+ ## Branch retirement
806
+
807
+ Release deliberately retains refs. A formally released feature/audit allocation can then request an
808
+ OID-bound plan without transferring Git mutation authority to Controller.
809
+
810
+ Select the required operations from the authorized task: local retirement and remote retirement
811
+ are separate actions. `branch-retirement-status` counts filed local/remote receipts;
812
+ `pending_operations` lists missing receipts for both actions, including a remote action the task
813
+ did not select. A candidate branch that was never published can therefore remain
814
+ `BRANCH_RETIREMENT_PARTIAL` after its local ref is retired. Preserve that status and its original
815
+ receipts. Record an unselected remote action as outside this task's retirement scope, with a
816
+ separate current remote observation; never manufacture a deletion receipt or execute an extra
817
+ deletion to make the global status complete.
818
+
819
+ After each selected action, query its existing operation-specific entry:
820
+
821
+ ```bash
822
+ worktree-controller branch-retirement-verify \
823
+ --repo /absolute/repository \
824
+ --plan-id <sha256-plan-id> --operation local --json
825
+ ```
826
+
827
+ For current closeout, require all three facts together: `RETIREMENT_OPERATION_COMPLETE` with the
828
+ matching `receipt`, `minimum_revalidation.ref_state = ABSENT`, and empty `blockers`. Check the exact
829
+ plan and operation identities. A completed operation may survive an ordinary integrated-main
830
+ fast-forward when its exact receipt matches, its ref stays absent, all other plan identity and
831
+ governance facts are unchanged, and the original integrated OID is an ancestor of the current OID.
832
+ The original plan and receipt retain their OIDs; `minimum_revalidation` reports the current
833
+ observation. `RETIREMENT_OPERATION_COMPLETE` remains a historical marker even when current
834
+ revalidation has blockers; it never suffices for closeout alone. Recreated refs, lost ancestry,
835
+ mismatched receipts and actual governance drift fail
836
+ closed. Pending operations keep exact integrated-OID CAS, including the other half of a partially
837
+ completed plan. Repeated read-only queries neither delete refs nor file receipts. Close a task
838
+ requiring both actions only after checking both operations independently.
839
+
840
+ The remote operation's `ref_state` observes the local remote-tracking ref. Use Git Finalizer's
841
+ live remote verification or a successful exact `git ls-remote --exit-code <remote> <full-ref>` to
842
+ verify the actual remote as well. Accept only an exact returned full ref name: exit 0 with its OID
843
+ means present; exit 2 with no match means absent. Unexpected output and transport/authentication
844
+ errors are unknown and do not prove absence. A remote ref that remains
845
+ present must be reported explicitly, and selected remote retirement stays open. An absent ref
846
+ without an existing receipt yields `ABSENT_UNATTESTED`; keep that verdict instead of claiming a
847
+ deletion occurred. Release, main publication and task/business completion keep their separate
848
+ evidence requirements.
849
+
850
+ Capability `canonical_transition_retirement_version` 1 also covers the old baseline branch left by
851
+ `canonical-transition`. Select that branch explicitly with `--branch <old-baseline-branch>` rather
852
+ than `--allocation-id`. Exactly one complete transition event in the current canonical allocation
853
+ must prove its old HEAD and the recorded publication ancestry on the policy remote/target. The plan
854
+ reports `CANONICAL_TRANSITION_BACKED`, a null release receipt, and `canonical_transition_evidence`
855
+ bound into its identity. Canonical stays registered on main; no release receipt is synthesized.
856
+ Ordinary unregistered refs remain report-only. Missing, ambiguous or changed history, occupied
857
+ branches, leases, runtime bindings, recovery state and unresolved obligations still block retirement.
858
+
859
+ If an owner released the checkout without naming an existing release intent, its exact completed
860
+ release plan and receipt can resolve that intent for retirement, including an intent later marked
861
+ `STALE`. Repository, allocation, frozen owner/task/authority, HEAD and record-version identities must
862
+ match; the original intent remains unchanged. An unrelated or incomplete receipt still blocks
863
+ retirement, and checkout, ancestry, publication, runtime and transaction guards still apply.
864
+
865
+ For a published integration candidate released through `--owner-release`, run the existing
866
+ `reconcile receipts --receipt-id <publication-receipt-id>` after release. Retirement accepts its
867
+ unique `RELEASE_STATE_ACKNOWLEDGED` receipt only for the exact publication and released record
868
+ version. Original release plans and publication receipts stay unchanged; missing, ambiguous or
869
+ stale acknowledgements fail closed. Retirement plans bind acknowledgement bytes and recheck them
870
+ before execution.
871
+
872
+ ```bash
873
+ worktree-controller branch-retirement-plan \
874
+ --repo /absolute/repository \
875
+ --allocation-id <released-allocation-uuid> \
876
+ --remote origin --integrated-into main --json
877
+ ```
878
+
879
+ An eligible response includes a provider-neutral `execution` recipe plus, for compatibility only,
880
+ the released `finalizer` argument array. The consumer then asks the Controller for a grant:
881
+
882
+ ```bash
883
+ worktree-controller branch-retirement-authorize \
884
+ --repo /absolute/repository \
885
+ --plan-id <sha256-plan-id> --operation local \
886
+ --request-id <uuid> --holder self --json
887
+ ```
888
+
889
+ The grant is single-use and durable. It is minted while the Controller holds `repo.lock`
890
+ exclusively, and it binds the plan identity, the `controller_state_digest` of that section, the
891
+ `minimum_revalidation` eight-tuple (`plan_id`, `operation`, `branch`, `expected_oid`,
892
+ `expected_integrated_oid`, `integrated_ref`, `ref_state`, `controller_state_digest`) and an
893
+ `observed` vector of the literal Git facts that digest cannot see: watched ref to OID, checked-out
894
+ paths, upstream ref and target, classification and blockers. It also returns a 256-bit execution
895
+ `token` — the only capability the grant carries. The consumer performs its own expected-OID
896
+ `git update-ref -d` / `push --force-with-lease` and settles:
897
+
898
+ ```bash
899
+ worktree-controller branch-retirement-complete \
900
+ --repo /absolute/repository \
901
+ --authorization-id <sha256> --token <token> --result-file execution-result.json --json
902
+ ```
903
+
904
+ `complete` re-reads the refs, re-derives the digest and re-evaluates governance inside its own
905
+ exclusive section. It files `RETIREMENT_EXECUTION_COMPLETE` only for exactly one authorized delta —
906
+ the target ref moving from its expected OID to absent — and answers
907
+ `RETIREMENT_EXECUTION_REFUSED` with `OBSERVATION_*` blockers, or `EXECUTION_OBSERVATION_MISMATCH`
908
+ when the ref is still there. A result document is never a witness; `abandon` closes a grant with
909
+ immutable evidence and deliberately never files a receipt, because absence alone does not prove who
910
+ caused it. Outstanding grants are visible in `branch-retirement-executions --action list`, in
911
+ `branch-retirement-verify` (`RETIREMENT_AUTHORIZATION_ACTIVE`, for the granted operation only) and
912
+ in `doctor` (`RETIREMENT_AUTHORIZATION_OUTSTANDING`).
913
+
914
+ A grant expires on its own clock without releasing anything: expiry means the claim needs
915
+ verification, never that the work is done. An expired grant still fences occupancy, still has to be
916
+ settled or abandoned with its token, and a settlement that lands late is recorded as
917
+ `grant_expired_at_settle` rather than refused, so a slow remote compare-and-delete can still be
918
+ attested honestly. `--holder` binds the grant to the caller's own process tree (`self` or
919
+ `ancestor:<program>`); a detached pid is refused, because naming somebody else's process proves
920
+ nothing.
921
+
922
+ `branch-retirement-verify` remains the released pre-mutation verdict, and
923
+ `branch-retirement-record` remains the released filing path. Both are now Controller-observed rather
924
+ than caller-attested: `record` refuses a completion whose ref it can still see, refuses an operation
925
+ holding a grant, and refuses with `EXECUTION_GOVERNANCE_MOVED` when authority or occupancy changed
926
+ after the plan. Receipts carry `provenance` — `AUTHORIZED_EXECUTION`, `CONTROLLER_OBSERVED`, or
927
+ `CONSUMER_CLAIM` for history filed before this contract existed — so an auditable distinction
928
+ replaces the assumption that a receipt implies an authorized execution. Consumer-specific argument
929
+ and summary vocabulary lives only in `retirement_adapters.py`; governance speaks
930
+ `retirement-execution-result/v1` and never names a consumer product.
931
+
932
+ Contract compatibility is checked against a real consumer in
933
+ `tests/test_branch_retirement_git_finalizer_acceptance.py`, which is skipped unless
934
+ `WORKTREE_CONTROLLER_GIT_FINALIZER` and `WORKTREE_CONTROLLER_GIT_FINALIZER_COMPANION` point at an
935
+ installation; the default suite needs only Git, and drives grant, settle, recovery and the installed
936
+ wheel through the mock executor in `tests/test_retirement_standalone.py`.
937
+
938
+ Only direct ancestry is executable in v1. `CHERRY_EQUIVALENT`, `MERGE_ONLY`, `UNIQUE`, `MIXED`, and
939
+ historical unregistered refs are classification/report output only. Protected publication refs and
940
+ rescue/archive/recovery lineage are never eligible. Controller does not invoke `git branch`,
941
+ `update-ref`, or remote deletion.
942
+
943
+ When Controller is available for a repository with enabled policy, linked worktree creation and
944
+ removal must use `acquire`/`release`. If Controller is unavailable or metadata is corrupt, use
945
+ `doctor`/recovery and fail closed instead of raw worktree add/remove. Read-only Git and branch,
946
+ merge, and commit operations remain outside Controller mutation authority.
947
+
948
+ ## Public integration publication session
949
+
950
+ Capability `integration_publication_execution_version` 1 advertises
951
+ `integration-publication-session/v1`. Start it with the exact candidate checkout and the identities
952
+ returned by `lease-acquire`:
953
+
954
+ ```bash
955
+ worktree-controller publication-execution-session --repo /absolute/candidate \
956
+ --candidate-oid <full-oid> --lease-id <uuid> --run-id <run> --json
957
+ ```
958
+
959
+ This command exchanges UTF-8 JSON lines rather than the ordinary status envelope. It emits
960
+ `verified` with an opaque public `identity`, an `action` and `remote_oid`. Send
961
+ `{"schema_version":1,"contract":"integration-publication-session/v1","action":"authorize","identity":<identity>}`.
962
+ The producer rechecks authority and returns `authorized`; only `PUSH_CANDIDATE` authorizes the
963
+ exact non-force branch push. `RECOVER_ALREADY_PUBLISHED` requires no push. Afterwards send
964
+ `complete` with the same identity and `result` containing `record_id`,
965
+ `executor: {name, version}`, boolean `push_executed` and the observed `remote_oid`. The Controller
966
+ independently reobserves Git before returning `completed` with matching execution evidence.
967
+
968
+ One Controller-owned exclusive lock spans all three phases. Frames are limited to 64 KiB and
969
+ input waits to 300 seconds. Wrong identities, duplicate keys, missing or drifted authority,
970
+ disconnects and timeouts fail closed. A disconnected executor may already have pushed: retry the
971
+ same lease/run/candidate after observing the remote, and an already-published candidate is
972
+ recovered without another mutation even after lease expiry. The local caller and OS are trusted;
973
+ this does not protect against arbitrary interference by other processes running as the same user.
974
+
975
+ A completed session does not change allocation lifecycle or release authority. Pass its consumer
976
+ `record_id` to the existing `lease-complete`; that command persists the publication receipt and
977
+ owns lease release and guarded cleanup. No daemon, new state directory or external executor is
978
+ introduced. `tests/test_publication_execution.py` tests the producer and enables real Finalizer
979
+ acceptance when `GIT_FINALIZER_ACCEPTANCE` names the consumer entrypoint.
980
+
981
+ ## Closed lifecycle history
982
+
983
+ `status` observes currently registered Git worktrees and their live allocation bindings. Releasing a
984
+ checkout removes that registration and binding; it retains the allocation record, immutable release
985
+ plan/receipt and any integration, preparation and publication evidence. From 1.13.0, the read-only
986
+ `closed-lifecycle/v1` query exposes those existing records without creating another state source:
987
+
988
+ ```bash
989
+ worktree-controller lifecycle-history --repo /absolute/repository --allocation-id <uuid> --json
990
+ worktree-controller lifecycle-history --repo /absolute/repository --authority-key <authority> --json
991
+ worktree-controller lifecycle-history --repo /absolute/repository --task-key <task> --json
992
+ worktree-controller lifecycle-history --repo /absolute/repository --integration-intent-id <uuid> --json
993
+ worktree-controller lifecycle-history --repo /absolute/repository --release-receipt-id <uuid> --json
994
+ ```
995
+
996
+ Exactly one selector is required. `decision.live_allocations` is a current observation, while
997
+ `decision.closed_releases` is historical evidence keyed by immutable `release_receipt_id`. The latter
998
+ contains allocation/authority/task, the owner from the release plan, original branch/path/HEAD,
999
+ release status/time, and existing integration candidate, publication time, prepared validation receipt
1000
+ and Finalizer references. Publisher identity is separate from the source task owner. An authority/task
1001
+ may have several allocations: never choose a historical owner by recency or reuse the current owner.
1002
+ Source delivery joins use the immutable publication receipt's `source_results`, not the current
1003
+ integration intent. All matching immutable preparations are returned; an ambiguous single preparation
1004
+ is null. Later publication receipts can enrich the query, while existing receipt identities remain
1005
+ stable. A closed receipt remains queryable if its mutable integration intent is no longer available.
1006
+ Closed releases sort by receipt ID; paginate with `--limit` (1..1000, default 100) and the returned
1007
+ `next_after` as `--after`. A missing match returns empty lists, never an inferred completion.
1008
+
1009
+ Receipt joins reuse query-local indexes, and only the returned page's evidence is serialized.
1010
+ Every metadata file and every matching release remains validated, including linked identity
1011
+ conflicts beyond that page. Repository locking and atomic disk storage live in `store.py`;
1012
+ versioned format validation and serialization live in `metadata_codec.py`, with the existing
1013
+ store import interface retained.
1014
+
1015
+ Legacy RELEASED records without a formal release receipt appear in `incomplete_history`. Missing
1016
+ optional publication/validation evidence is null; contradictory linked identities fail closed. The
1017
+ query neither backfills evidence nor changes retention/release gates. Storage layout and metadata
1018
+ schema remain unchanged, and no database, daemon or history tracker is added.
1019
+
1020
+ Handbook consumers should retain only the stable receipt reference in their narration and fetch this
1021
+ query during verification. Verify `proof_status=COMPLETE`, exact allocation/authority/task/owner and
1022
+ the required delivery's existing publication receipt; confirm its candidate exists and is an ancestor
1023
+ of the Handbook baseline. Resource release alone, including archived unfinished work, does not prove
1024
+ publication or business completion. A `release:<integration intent>` citation can continue naming the
1025
+ same intent: use its historical source allocation's release receipt to prove the source owner, rather
1026
+ than the integration publisher. Keep live writer authorization for narrative writes and do not copy
1027
+ Controller history into Handbook state. Old missing evidence stays unverified until its owner uses
1028
+ an existing supported recovery path.
1029
+
1030
+ ## Legacy namespace migration
1031
+
1032
+ `namespace-migration/v1` is the supported per-repository transition in 1.11.0. Plan and verify are
1033
+ read-only. Apply requires an operator, the exact reviewed plan id, and the destination lock. The
1034
+ runtime defects from the 1.8.0 audit are closed in this release. Storage layout v4 only documents
1035
+ the already existing registration-prune receipt directory. This release does not migrate any
1036
+ repository.
1037
+
1038
+ Repositories created before the current layout keep their metadata under
1039
+ `<git-common-dir>/codex-worktree/v1`. Nothing in the runtime discovers a namespace, so such a
1040
+ repository reads as unmanaged and its real state stays invisible. `namespace-migrate` is the only
1041
+ supported way to move it; a caller never copies, edits or deletes namespace JSON:
1042
+
1043
+ ```bash
1044
+ worktree-controller namespace-migration-plan --repo /absolute/repository --json
1045
+ worktree-controller namespace-migrate --repo /absolute/repository \
1046
+ --actor <who-is-responsible> --expected-plan-id <plan-id-from-the-previous-command> --json
1047
+ worktree-controller namespace-migration-receipts --repo /absolute/repository --json
1048
+ worktree-controller namespace-migration-verify --repo /absolute/repository --json
1049
+ ```
1050
+
1051
+ `namespace-migration-plan` is read-only, and its decision is exactly what `namespace-migrate` will
1052
+ do with it:
1053
+
1054
+ | Legacy and current state | Decision | Effect |
1055
+ | --- | --- | --- |
1056
+ | content only under `codex-worktree/v1` | `NAMESPACE_MIGRATION_REQUIRED` | documents move and `repo.json` lands last |
1057
+ | legacy absent and no unsettled prepared receipt | `NAMESPACE_ALREADY_CURRENT` | an exact no-op CAS writes nothing; a settled transition replays its original receipt |
1058
+ | both namespaces hold equivalent content, or the destination holds a document the runtime cannot parse | `NAMESPACE_CONVERGENCE_REQUIRED` | redundant legacy copies clear and the destination document is rewritten into its authoritative form |
1059
+ | both namespaces hold the same path with different content | `NAMESPACE_CONFLICT` | refuses; nothing moves and no receipt is written |
1060
+ | evidence the Controller cannot trust: a non-JSON or unreadable document, a symlink, a missing `repo.lock`, no `repo.json` in either namespace, an unfinished journal | `NAMESPACE_EVIDENCE_INVALID`, `NAMESPACE_LOCK_MISSING`, `NAMESPACE_IDENTITY_MISSING`, `NAMESPACE_UNSAFE` | refuses before any write |
1061
+
1062
+ The plan id digests the reviewed merged content and is the compare-and-set the caller must present.
1063
+ A new transition re-derives it inside the lock. A `PREPARED` receipt stores that original identity,
1064
+ the per-document digests, lease conversions and policy move. If interrupted, the read-only plan
1065
+ reports `recovery_required` and the original plan id; apply accepts only that id and checks source
1066
+ and immutable evidence before finishing. Later Controller updates to mutable destination records
1067
+ do not create a second migration identity.
1068
+
1069
+ The whole transition holds an exclusive `flock` on the **destination** `repo.lock`, creating it
1070
+ before any content lands. That is the same lock `adopt`, `acquire` and `integration-enqueue` take
1071
+ when they bootstrap metadata, so a concurrent verb queues behind the migration instead of minting a
1072
+ second repository identity beside the tree being moved. The legacy lock is taken without waiting: a
1073
+ caller holding it is an activation in flight, and blocking there would invert that flow's lock
1074
+ order, so the migration fails closed with `NAMESPACE_BUSY` and is retried. A read-only plan never
1075
+ queues at all; it defers the unfinished-journal question to the fence and reports
1076
+ `"preflight": "under_fence"`.
1077
+
1078
+ `repo.json` is the only file that makes a namespace authoritative, so it is the last thing to land
1079
+ and the destination copy is re-digested against the plan before and after the switch. After that the
1080
+ legacy namespace is removed only when it is demonstrably empty (`NAMESPACE_RESIDUE` otherwise), and
1081
+ the `metadata-reference-relocation/v1` record is written only once it is gone - the two together are
1082
+ what lets immutable completion receipts keep naming the old path and still resolve. Converted leases
1083
+ are published complete, and interrupted JSON scratch files do not change the plan identity. Ten
1084
+ fault injection points cover the prepared receipt, conversion and rewrite windows, authority switch,
1085
+ relocation record, policy move and settlement; the exact names are in `tool_cli_contract.json`.
1086
+
1087
+ Receipts under `namespace-migrations/` are write-once `0400` documents. The receipts verb reports
1088
+ them, and `namespace-migration-verify` flags an unsettled `PREPARED` receipt or invalid settlement
1089
+ evidence. A settled receipt must still describe the moved tree, the relocation record must validate
1090
+ against its own directory identity, and the legacy namespace must be gone. Migration does not change
1091
+ retirement semantics: plans, grants and receipts move byte for byte, and a released branch's plan
1092
+ keeps resolving after the move.
1093
+
1094
+ ## Unified release plan and disposable resources
1095
+
1096
+ Clean `RETAINED` feature/audit checkouts can use `close-delivered` when their committed
1097
+ content is already published. First run `close-delivered --path PATH
1098
+ --expected-record-version N --owner-release OWNER --actor ACTOR --dry-run --json`.
1099
+ Review its exact record, branch and publication evidence, then repeat with
1100
+ `--plan-id PLAN_ID` in place of `--dry-run`. This writes a closeout receipt and moves
1101
+ the checkout to `RELEASE_READY`. It does not declare the business task complete or
1102
+ change HEAD, files, ownership, original task commits or refs. A live publication query
1103
+ must agree with the local tracking ref; stale tracking is refused rather than fetched.
1104
+
1105
+ From 1.13.4, the same reviewed plan also closes a legacy `PRESERVED` feature/audit checkout
1106
+ whose HEAD, base and task commit are already contained in live publication, but which lacks
1107
+ frozen handoff/Finalizer evidence. It preserves that original evidence rather than inventing a
1108
+ handoff or declaring completion. Bindings, leases, runtime uncertainty, ignored content and any
1109
+ uncleaned resource still block it. Verified source archives keep their existing preservation
1110
+ release flow; this recovery does not enter automatic convergence or enable preserved auto-release.
1111
+
1112
+ Use `gc-plan --path PATH --owner-release OWNER` and the returned plan with `gc-apply`,
1113
+ or the normal `release --dry-run` / `release`, to remove the checkout. Every release
1114
+ rechecks the immutable closeout receipt, publication ancestry, live processes, bindings,
1115
+ resources and ignored content. Dirty, ACTIVE, frozen-handoff, unintegrated or uncertain
1116
+ targets cannot use this closeout. A retry after interruption uses the same plan and
1117
+ receipt. `RELEASE_READY` requires `delivered_closeout_version` 1; older runtimes reject
1118
+ that lifecycle, so rollback must pass the rollout gate with the actual repository state.
1119
+
1120
+ For clean `RETAINED` work that is still unpublished, use `archive-retained --path PATH
1121
+ --expected-record-version N --owner-release OWNER --actor ACTOR --dry-run --json`.
1122
+ Review the branch and original task record, then repeat with `--plan-id PLAN_ID`.
1123
+ The immutable receipt moves the allocation to `ARCHIVED` without changing its commits,
1124
+ owner or task evidence. The exact local branch must contain the recorded task lineage;
1125
+ unresolved integration, active use, unknown ignored content and unsafe resources block
1126
+ archiving. `gc-plan` / `gc-apply` then apply the ordinary release gates. An unreleased
1127
+ archive can return to `RETAINED` through `reopen`. `ARCHIVED` requires
1128
+ `retained_archive_version` 1, so rollback must pass the actual repository-state gate.
1129
+
1130
+ `release --dry-run` is an official read-only preflight for every managed releasable worktree,
1131
+ including worktrees without `.venv`. Dry-run and execution call the same `build_release_plan()`
1132
+ guard path. Execution persists the exact plan, rechecks HEAD and record-version CAS plus all runtime,
1133
+ editable, external, dirty, ignored-content, and retained-ref guards, and writes an immutable release
1134
+ receipt. An interrupted execution resumes only from that persisted identity; an exact repeated call
1135
+ returns `ALREADY_RELEASED`.
1136
+
1137
+ Governance v1.1 adds no lifecycle state or deletion mechanism. `status` and `gc-plan` observe
1138
+ recorded owner, lifecycle, writer lease, host session and runtime occupancy. Host session health
1139
+ is `UNKNOWN` without independent evidence. Lease renewal is reported as its own evidence; it is
1140
+ not an independent heartbeat. Stale and orphan findings are advisory; neither changes stored
1141
+ lifecycle state nor grants release eligibility. `gc-plan`
1142
+ reports allocation, path, branch, lifecycle, owner, dirty state, session, lease, candidate reason,
1143
+ filesystem and namespace health, and blocking facts for every registered checkout. A global
1144
+ plan probes each registered checkout's repo path and `.git` entry independently, alongside its
1145
+ view of the Git common dir and lock path. A path that cannot be inspected has an `UNKNOWN` mount state;
1146
+ the plan does not infer another candidate's mount state from the requested checkout. The global
1147
+ report is advisory: a clean worktree alone is never release authority. For one reviewed target,
1148
+ provide its exact recorded owner via `--owner-release` or an existing `--intent-id`; the resulting
1149
+ `gc_plan_id` is stable across timestamp-only ReleasePlan changes. `gc-apply` checks that identity
1150
+ again inside the release lock before any mutation. Drift or a current lease stops the apply;
1151
+ normal `release` remains the recovery entry for an interrupted release. Release plans, receipts,
1152
+ and storage layout remain unchanged.
1153
+
1154
+ Environment diagnosis distinguishes Controller authority or lock faults from filesystem permission
1155
+ faults and mount differences seen in a selected process namespace. A repo can be writable while
1156
+ its `.git` entry is read-only in another namespace. `doctor` retains detailed mount evidence and
1157
+ reports it before attributing an observed lock failure to Controller authority. A successful
1158
+ `status` response includes the same environment diagnosis; on failure, its CLI response keeps the
1159
+ classified error code and `doctor` supplies the detailed evidence. Controller lock attribution
1160
+ requires the relevant environment probes to confirm writable access. An
1161
+ unavailable namespace comparison remains `UNKNOWN` for that comparison, while a confirmed
1162
+ read-only mount still establishes a filesystem fault. A namespace difference alone does not
1163
+ identify the sandbox provider. No diagnostic changes mounts or permissions.
1164
+ `mount_diagnostics.environment` and top-level `environment_diagnosis` classify the observed
1165
+ environment as `HEALTHY`, `MOUNT_NAMESPACE_MISMATCH`, `FILESYSTEM_PERMISSION`, or `UNKNOWN`;
1166
+ `doctor` also exposes `decision.primary_diagnosis` to distinguish `CONTROLLER_LOCK_AUTHORITY`,
1167
+ other `CONTROLLER_METADATA` findings, and `NONE` after environment checks.
1168
+
1169
+ `status` and `gc-plan` include `governance_capacity` in `observe` mode. `active_limit` and
1170
+ `retained_limit` use the policy feature/audit slot declarations and count those linked roles in
1171
+ their respective lifecycle states. `total_limit` uses `hard_limit` and counts all Git registrations.
1172
+ These observations do not alter resource admission or the historical top-level `retained_limit`
1173
+ pool statistic.
1174
+
1175
+ `resource-clean --typed --dry-run` adds `typed-disposable-resource/v1` for a source allocation in
1176
+ `HANDOFF_READY`, without reopening it or changing its handoff/publication identity. The initial
1177
+ paths are deliberately limited to `backend/.venv` (tracked Python project and uv lock, venv
1178
+ identity and standard structure), `frontend/node_modules` (tracked npm project/lock and matching
1179
+ installed dependency identities), and `frontend/dist` (the declared default Vite build output).
1180
+ Root `.venv`, user/provider state, arbitrary caches, other package managers and ambiguous build
1181
+ configurations are outside this contract. Existing release-cache handling is unchanged and receives
1182
+ no typed cleanup action; unknown ignored content blocks the entire cleanup.
1183
+
1184
+ Every dry-run resource reports `resource_type`, `path`, `classification`, `cleanup_action` and
1185
+ `blocker`. A disposable identity alone does not authorize deletion: paths must be canonical and
1186
+ owned, with no tracked or staged content, mounts, special objects, external bindings, writer lease,
1187
+ or an allocation-scoped managed/observed runtime reference. Runtime scanning pins each proc directory, reads
1188
+ `stat`/`status`, and reopens the numeric PID to compare directory identity, `start_ticks`, UIDs and
1189
+ whether it is multi-threaded (a resizing thread pool is the same process; becoming single- or
1190
+ multi-threaded is not). Identity changes cause at most three immediate attempts, without waiting. A
1191
+ missing field alone is not process-exit evidence: `ENOENT`/`ESRCH` require a PID identity recheck; a
1192
+ missing fd requires confirmation that that descriptor closed. A process whose fields vanish, with
1193
+ or without its fd directory also denying access, is rescanned after a short pause up to the same
1194
+ bound: a finished exit resolves to terminal or absent, a live process stays uncertain. A stable single-thread `Z`/`X` process has
1195
+ already released its mm/files/fs in Linux `do_exit`, so inaccessible fields cannot represent active
1196
+ references. A zombie leader with other threads does not qualify for this exclusion.
1197
+
1198
+ Typed cleanup uses `allocation-runtime-authority/v1`:
1199
+
1200
+ | Classification | Authority/evidence | Effect |
1201
+ | --- | --- | --- |
1202
+ | `MANAGED_REFERENCE` | Allocation external/runtime binding, writer/publication lease, recorded holder, or verified holder descendant | Block |
1203
+ | `OBSERVED_REFERENCE` | Readable `cwd`/`exe`/`fd`/`maps` points into the target | Block |
1204
+ | `MANAGED_VISIBILITY_UNCERTAIN` | Recorded holder or positively associated lineage has inaccessible fields or unverifiable identity | `RUNTIME_SCAN_UNCERTAIN`, block |
1205
+ | `HOST_VISIBILITY_GAP` | `EACCES`/`EPERM` without any formal holder/lineage association to the allocation | Warning/evidence for authenticated typed resources |
1206
+
1207
+ AW Run and Native Session identity enters Controller through the existing external bindings or
1208
+ writer leases; these registrations remain blocking until formally removed. Opaque bindings block
1209
+ without needing a PID. A PID holder uses the existing boot ID/start ticks/session/run identity.
1210
+ Descendant proof follows numeric PPIDs, checks parent start ticks, and revalidates every identity
1211
+ and PPID edge. Reuse or changing lineage cannot become a host permission gap. No names, cgroups,
1212
+ provider names, process ages, command lines or environment variables grant an exclusion.
1213
+
1214
+ `plan.runtime_evidence` records governance identities, observed references, managed uncertainties
1215
+ and per-PID host gaps with exact field/errno. `plan.warnings` exposes the gap; an otherwise eligible
1216
+ nonempty plan reports `CLEANUP_READY_WITH_HOST_VISIBILITY_GAP`. Unknown ignored content, invalid
1217
+ resource identity, malformed proc entries, unresolved races and global scan errors still block.
1218
+ Cleanup receipts preserve the original plan, every resource's runtime preflight and the post-scan
1219
+ evidence immutably. Release after typed cleanup rechecks the same managed-runtime boundary.
1220
+
1221
+ This boundary does not prove that an unassociated host process has no references. The existing
1222
+ trusted-user/OS scope observes same-UID processes, plus any positively associated managed process
1223
+ across UIDs. Foreign UID exclusion alone is not a filesystem-access proof. Unregistered or reparented
1224
+ processes without retained governance lineage remain outside provable managed authority; readable
1225
+ positive references still block. This is a bounded observation, not an atomic host-wide snapshot or
1226
+ process isolation. A compliant launcher must retain its runtime registration until its workload is
1227
+ quiescent. The existing leases, bindings and repeated cleanup preflights remain necessary.
1228
+
1229
+ Internal symlinks may stay within the resource. The only external-link exception is
1230
+ the standard venv `bin/python`, `bin/python3`, and matching `bin/python3.x` interpreter aliases:
1231
+ their shape, common executable target, version/home declaration and executable identity are checked.
1232
+ Plans bind the worktree, parent and resource directory device/inode/type identities. Cleanup opens
1233
+ each directory component with `O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC`, verifies its identity, and
1234
+ matches a fresh FD inventory against the planned tree fingerprint. Recursive deletion uses only
1235
+ relative `unlink`/`rmdir` with verified directory FDs. The directory chain and entry identities are
1236
+ rechecked around deletion; renamed, missing or substituted entries retain the blocking journal.
1237
+ Symlink aliases are pinned without following them and unlinked without modifying their targets.
1238
+
1239
+ Run `resource-clean --typed --expected-plan-id <dry-run-plan-id>` with the same `--repo`, `--path`,
1240
+ `--owner` and `--expected-record-version`. Execution rechecks the plan, journals before deletion,
1241
+ rechecks before each resource, rescans, validates release eligibility, and writes a new cleanup
1242
+ receipt. It never releases the allocation. Then use the ordinary `release --dry-run` and `release`
1243
+ commands. Release following typed cleanup also requires managed-runtime clearance. Cleanup failure
1244
+ or interruption retains a blocking journal for explicit recovery; it is not automatically retried
1245
+ through partial deletion. Historical receipts and handoff identities are never rewritten.
1246
+
1247
+ The existing typed runtime binding covers agent, test, preview, background and command
1248
+ occupancy with allocation/worktree, boot/PID/start ticks and run/session identity.
1249
+ Execution exit does not require closing a human terminal that provably predates all
1250
+ declared holders: kernel creation order excludes that process from their descendants.
1251
+ Unknown or newer observed processes and managed uncertainties still block execution
1252
+ clearance. Allocation cleanup continues to reject every observed reference, including
1253
+ that preexisting terminal. No second use-lease schema or supervisor is introduced.
1254
+
1255
+ `.venv` is not a prerequisite for a plan. It is one supported disposable resource and must be
1256
+ registered before handoff with path, type, reproducibility, creator, and `manual` or `on-release`
1257
+ teardown policy. The controller never guesses that an ignored directory is disposable. Unknown
1258
+ ignored content stays fail-closed. The legacy `--teardown-reproducible-venv` flag remains an explicit
1259
+ compatibility assertion; registered `on-release` resources are already represented in the plan.
1260
+
1261
+ The preflight requires a clean, non-active, releasable checkout; matching task, handoff, Finalizer,
1262
+ and HEAD commits; the recorded owner and authority; tracked `uv.lock` and `pyproject.toml`; regular
1263
+ `pyvenv.cfg` and standard `CACHEDIR.TAG`; an executable Python identity; no external binding; no
1264
+ other managed environment referring to the checkout; and no same-UID `cwd`, executable, file
1265
+ descriptor, or memory mapping into the checkout that is visible through Linux procfs. Procfs may
1266
+ hide non-dumpable same-UID system services; those remain inside the documented trusted-user/OS
1267
+ boundary and must be represented by Controller external bindings when they own repository runtime.
1268
+
1269
+ Receipts under Controller metadata bind the worktree, HEAD, owner/authority, lock and project
1270
+ checksums, Python/environment identity, pre-removal byte/entry counts, and final result. A crash
1271
+ before Git worktree removal leaves a fail-closed journal; rerunning the identical command with the
1272
+ same CAS identity resumes from the receipt. A started removal may resume across partial deletion
1273
+ only while its recorded root inode and authority still match; path or root-identity drift remains
1274
+ blocked for review.
1275
+
1276
+ If teardown already reached `VENV_REMOVED` or `PREPARED`, the identical `release --dry-run` returns
1277
+ `RELEASE_RECOVERY_PLAN_READY` only after revalidating the persisted plan, journal, receipt, exact
1278
+ HEAD and record version, retained branch, recovery lineage, worktree content, and all runtime,
1279
+ editable, ignored-content, and external bindings. Execution then continues from checkout removal;
1280
+ it does not recreate or reclassify the removed `.venv`. Missing or altered transaction evidence and
1281
+ an unjournaled missing resource remain blocked.
1282
+
1283
+ ## Legacy preserved state
1284
+
1285
+ From 1.18.8 (`no_change_cleanup_closeout_version` 1), a genuine `complete-no-change` also freezes
1286
+ its original record and the exact successful cleanup receipt set. Its write-once completion
1287
+ receipt is pinned by raw SHA256 in the normal +1 history transition. Release derives the full
1288
+ original record before rechecking cleanup at its unchanged exact CAS, including every action,
1289
+ preserved generation and absent source. Completion does not repeat cleanup or authorize runtime
1290
+ or resource guard exceptions.
1291
+
1292
+ An older no-change completion created after successful typed cleanup may be blocked because the
1293
+ old completion advanced the record without freezing this relationship. Recover that exact task
1294
+ through the explicit append-only entrance below, using observed trusted receipt IDs and raw
1295
+ digests. A newly observed digest is a new baseline, not proof of an earlier observation. The
1296
+ producer requires one unique legacy completion, the entire successful cleanup set, original
1297
+ allocation/owner/HEAD/branch and exact +1 transition, clean resources, no leases/bindings/runtime
1298
+ references or conflicting transaction. Missing, duplicate, changed or ambiguous evidence blocks.
1299
+
1300
+ ```bash
1301
+ worktree-controller reconcile no-change-completion --repo /absolute/canonical-repo \
1302
+ --path /absolute/source-worktree --owner <recorded-owner> --expected-record-version <version> \
1303
+ --completion-receipt-id <uuid> --completion-receipt-sha256 <trusted-raw-sha256> \
1304
+ --cleanup-receipt-id <uuid> --cleanup-receipt-sha256 <trusted-raw-sha256> --dry-run --json
1305
+ ```
1306
+
1307
+ Repeat the same command without `--dry-run` and with `--expected-plan-id <reviewed-plan-id>`.
1308
+ Additional cleanup IDs/digests use paired repeatable flags. Formal recovery writes a new receipt
1309
+ and advances only the normal record/history CAS; the old receipts, refs and resources remain
1310
+ unchanged. While the checkout remains registered, an identical original request returns the same
1311
+ pinned receipt without another transition. Physical release remains a separate guarded command;
1312
+ after release, use its normal replay or exact `lifecycle-history` receipt query.
1313
+
1314
+ `python -m worktree_controller.preservation` implements `legacy-preserved-state/v1`.
1315
+ The directory example below uses the original root `.venv/agent-workspace` legacy state shape.
1316
+ Capabilities `retained_typed_cleanup_version` 1 and `preservation_leaf_version` 1 also support
1317
+ clean `RETAINED` feature/audit allocations and an exact `--source` selecting a Git-ignored,
1318
+ untracked directory, regular file or literal symlink leaf. This is a separate preservation
1319
+ entry point, not a disposable resource type. Preservation does not mutate allocations, bindings,
1320
+ handoffs, historical receipts or Controller transactions. Cleanup requires the separate exact
1321
+ receipt contract below and does not itself release an allocation.
1322
+
1323
+ The command binds an eligible clean allocation's repository/owner/authority, record
1324
+ version, HEAD, worktree/source node identities, and a fresh complete source manifest.
1325
+ It uses the existing allocation runtime guards: managed and observed references or uncertain
1326
+ managed visibility block; host permission gaps are recorded without claiming host-wide absence
1327
+ of references. Inspection FDs are closed before runtime scans; there is no self-PID exclusion.
1328
+ Per-resource classification blockers are recorded as `cleanup_not_ready` during preservation
1329
+ of the selected source; allocation, runtime, binding and editable-reference guards still block
1330
+ unsafe preservation. Copy and verify do not authorize cleanup, including nested typed resources.
1331
+
1332
+ Directory ancestors and tree directories are traversed with directory FDs and
1333
+ `O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC` at every directory component. Regular files are pinned
1334
+ with `O_PATH | O_NOFOLLOW`, confirmed regular by
1335
+ `fstat`, then read through the kernel's `/proc/self/fd` descriptor link. This prevents a changed
1336
+ source pathname from opening a symlink target or a FIFO endpoint. Provider/private files,
1337
+ including DB/WAL/SHM, and unknown files are copied and hashed only as opaque bytes; their
1338
+ contents are never parsed or printed. Unreadable files and unstable entry identities block.
1339
+
1340
+ Canonical, sorted JSON manifest rows use distinct node types:
1341
+
1342
+ | Node | Tree digest input, in addition to relative path/type/mode |
1343
+ | --- | --- |
1344
+ | Regular file | Size and SHA-256 of every byte |
1345
+ | Directory | No content payload; empty directories are included |
1346
+ | Symlink | Literal link target, including broken or external links; never followed |
1347
+ | FIFO | No content digest or payload; reconstructed with fd-relative `mkfifo` and exact mode |
1348
+
1349
+ UID/GID are recorded for every source node, but never restored with `chown`. Device/inode,
1350
+ mode/size, mtime/ctime, UID/GID and link count form the separate source race identity. Inodes
1351
+ and ownership are excluded from source/destination tree equality; directory sizes and timestamps
1352
+ are not portable tree equality fields. FIFO is never opened for I/O, read, waited on, or converted
1353
+ to a regular file. Its creation temporarily clears and immediately restores this single-threaded
1354
+ command's process umask. Sockets, devices and other node types remain blocked.
1355
+
1356
+ Generations live under the formal private state root
1357
+ `~/.local/state/worktree-controller/preservations/v1/<repository-id>/<allocation-id>/<uuid>/`,
1358
+ outside all repository worktrees. Plans pin existing destination ancestors. Creation is exclusive,
1359
+ all writes use verified directory FDs, and destination identity races or collisions fail closed.
1360
+ Failures retain an incomplete generation without a successful receipt; retry requires a new plan
1361
+ and UUID, never overwriting an earlier generation.
1362
+
1363
+ ```bash
1364
+ python -m worktree_controller.preservation plan \
1365
+ --repo /absolute/canonical-repo --path /absolute/source-worktree \
1366
+ --allocation-id <uuid> --owner <recorded-owner> --expected-record-version <version> \
1367
+ --source .venv --plan-file /absolute/task-owned-directory/preservation-plan.json
1368
+ python -m worktree_controller.preservation copy --plan-file /absolute/task-owned-directory/preservation-plan.json
1369
+ python -m worktree_controller.preservation verify --plan-file /absolute/generation/plan.json
1370
+ ```
1371
+
1372
+ Planning hashes the entire current source and reports counts, bytes, unreadable/special entries,
1373
+ runtime evidence and the proposed generation. It creates only the explicitly requested, exclusive
1374
+ plan file, not the generation or a source modification. Copy rechecks the plan and every source
1375
+ entry, copies the complete tree, verifies destination contents, then rescans source contents and
1376
+ inode identities. Reverification detects missing/changed destination files, changed FIFO modes,
1377
+ wrong receipts and any source change since planning.
1378
+
1379
+ A successful generation contains `tree` (a directory, regular file or literal symlink matching
1380
+ the selected source), `plan.json`, `manifest.json` and `receipt.json`.
1381
+ The receipt binds source/allocation/generation identities, manifest and equal tree hashes, node
1382
+ counts and bytes, source verification time, runtime warnings and implementation digest. Evidence
1383
+ files are write-once `0400`; the generation is finalized `0500`. These permissions and hashes
1384
+ provide the existing trusted-user/OS evidence boundary, not protection from hostile same-UID
1385
+ modification. Later source or destination drift invalidates current coverage. There is no atomic
1386
+ snapshot of unregistered host writers and no claim that host permission gaps prove absence.
1387
+ `LEGACY_PRESERVATION_COMPLETE` means the recorded preservation checks passed.
1388
+
1389
+ ### Exact receipt cleanup
1390
+
1391
+ `legacy-preserved-state-cleanup/v1` requires the exact source already covered by a complete
1392
+ `legacy-preserved-state/v1` generation; the example below uses the root `.venv`. Supply the
1393
+ canonical receipt path and previously recorded SHA-256 explicitly; the Controller does not discover receipts by directory name or trust a newly
1394
+ computed hash as proof of an earlier receipt. The ordinary disposable types stay unchanged.
1395
+
1396
+ ```bash
1397
+ worktree-controller resource-clean --typed --dry-run \
1398
+ --repo /absolute/canonical-repo --path /absolute/source-worktree \
1399
+ --owner <recorded-owner> --expected-record-version <version> \
1400
+ --preservation-receipt /absolute/generation/receipt.json \
1401
+ --preservation-receipt-sha256 <trusted-receipt-sha256> --json
1402
+ ```
1403
+
1404
+ Capability `multiple_preservation_cleanup_version` 1 (CLI contract 40) also accepts paired,
1405
+ repeatable `--additional-preservation-receipt` and `--additional-preservation-receipt-sha256`
1406
+ options after the primary pair. Every receipt must cover the same exact allocation identity and
1407
+ one distinct, non-overlapping classified source path. Missing pairs, duplicate receipts, overlap
1408
+ or any invalid source blocks the entire cleanup. The normalized proof set is bound to the plan
1409
+ ID and immutable journal/receipt under `legacy-preserved-state-multiple-cleanup/v1`; single-input
1410
+ plans retain their existing contract and shape. All resources and global guards remain mandatory.
1411
+ Every action verifies its own source against the full recorded hardlink topology. Only a source
1412
+ with a verified `COMPLETED` action may use destination-only verification, and it must remain absent.
1413
+ Release re-verifies every preserved generation and the complete proof/resource/action relationship.
1414
+
1415
+ Capability `multiple_preservation_recovery_version` 1 adds one bounded recovery path: a nonempty
1416
+ verified `COMPLETED` prefix followed only by never-started `PENDING` actions. Supply the original
1417
+ transaction ID and its previously recorded journal and failed-receipt SHA-256 values:
1418
+
1419
+ ```bash
1420
+ worktree-controller resource-recover --stage prepare-multiple --dry-run \
1421
+ --repo /absolute/canonical-repo --path /absolute/source-worktree \
1422
+ --owner <recorded-owner> --expected-record-version <version> \
1423
+ --transaction-id <original-transaction-id> \
1424
+ --transaction-sha256 <trusted-journal-sha256> \
1425
+ --cleanup-receipt-sha256 <trusted-failed-receipt-sha256> --json
1426
+ ```
1427
+
1428
+ After reviewing the dry-run, repeat it without `--dry-run` and with the exact fresh
1429
+ `--expected-plan-id`. Formal preparation writes an immutable
1430
+ `multiple-preservation-cleanup-recovery/v1` record and returns its path and SHA-256. It retains
1431
+ the full original proof set, verifies every completed generation's official metadata, and freezes
1432
+ those metadata paths and hashes together with the allocation owner, CAS and history. Resume
1433
+ uses that exact new reference:
1434
+
1435
+ ```bash
1436
+ worktree-controller resource-recover --stage resume --dry-run \
1437
+ --repo /absolute/canonical-repo \
1438
+ --recovery-receipt /absolute/recovery/record.json \
1439
+ --recovery-receipt-sha256 <trusted-recovery-record-sha256> --json
1440
+ ```
1441
+
1442
+ Run the same resume command without `--dry-run` only after a passing review. It uses the existing
1443
+ cleanup executor for the untouched suffix; it never replays the completed prefix or reduces the
1444
+ proof set. A started or partial recovery cannot replay deletion. If the successor cleanup already
1445
+ completed, resume may only finish its missing terminal evidence after verifying the unique exact
1446
+ successful receipt. The original journal, failed receipt and all generations remain unchanged.
1447
+ Every existing allocation, root, resource, topology, runtime, reference and transaction guard still
1448
+ applies. Historical terminal verification continues after an exact official release by checking
1449
+ the frozen authority, complete metadata set and release evidence; checkout absence is not proof.
1450
+ Unsupported interrupted shapes still block release and rollout. Legacy single-receipt recovery
1451
+ retains its existing flow, and no interrupted multi plan may be downgraded into it.
1452
+
1453
+ Capability `preservation_supersession_version` 1 recognizes an exact official frozen handoff,
1454
+ reopen and new handoff lineage. The checkout root and source parent/ancestor identities must
1455
+ remain unchanged. A rebuilt source leaf or directory inode is accepted only when the new exact
1456
+ verified proof and actual completed action cover that path under the same allocation and owner,
1457
+ with the recorded CAS/HEAD ancestry. Every older completed generation's official metadata remains
1458
+ verified and retained; neither a newer copy nor a missing source alone grants cleanup authority.
1459
+
1460
+ The plan revalidates receipt bytes, supported contract, allocation/HEAD/record/owner/authority,
1461
+ exact worktree/source and generation identities, manifest digest, PASS status, every preserved
1462
+ node and the complete current source tree and inode identities. Missing, changed or mismatched
1463
+ evidence blocks. Tracked/staged files and unknown ignored resources still block. Managed and
1464
+ observed references and managed visibility uncertainty still block; host visibility gaps remain
1465
+ explicit warnings and evidence, without claiming host-wide absence of references.
1466
+ Editable links inside the same verified legacy tree are preserved and removed with it; links
1467
+ from other managed environments remain release blockers.
1468
+
1469
+ An eligible plan reports `CLEANUP_READY_WITH_PRESERVATION`. Run the same command without
1470
+ `--dry-run`, adding `--expected-plan-id <fresh-plan-id>`, only after explicit cleanup authorization.
1471
+ In this root `.venv` example, the three typed resources are classified separately and removed
1472
+ first; exact-preserved `.venv` is last. Each action repeats the safety checks. Deletion uses the existing FD-bound primitive,
1473
+ validates directory and entry identities, unlinks symlinks themselves, and handles FIFOs only as
1474
+ nodes with `O_PATH` metadata pins. No FIFO endpoint or external symlink target is opened for I/O.
1475
+ The preservation generation is outside the deletion tree and is never deleted by cleanup.
1476
+
1477
+ The immutable cleanup receipt records the original plan, per-action progress, runtime warnings,
1478
+ and post-scan. Any failed/partial action retains its transaction and receipt, blocks allocation
1479
+ release, and requires review; there is no blind retry. Successful cleanup rescans Git, resources,
1480
+ runtime authority and release eligibility before clearing its transaction. A separate ordinary
1481
+ `release` still applies every existing gate and re-verifies the preserved destination from the
1482
+ cleanup receipt. Historical preservation and handoff receipts are never rewritten.
1483
+
1484
+ After cleanup or release, verification can still hash the preserved tree without requiring the
1485
+ removed source:
1486
+
1487
+ ```bash
1488
+ python -m worktree_controller.preservation verify --plan-file /absolute/generation/plan.json \
1489
+ --destination-only --receipt-sha256 <trusted-receipt-sha256>
1490
+ ```
1491
+
1492
+ ## Policy v2, v3, and v4
1493
+
1494
+ Policy v2 separates checkout preparation capacity from publication concurrency and reserves
1495
+ governance recovery independently from ordinary feature work:
1496
+
1497
+ ```toml
1498
+ schema_version = 2
1499
+
1500
+ [limits]
1501
+ normal_limit = 6
1502
+ hard_limit = 8
1503
+
1504
+ [slots]
1505
+ integration_candidate_limit = 2
1506
+ feature_or_audit = 3
1507
+ emergency = 1
1508
+ recovery_reserve = 1
1509
+
1510
+ [publication]
1511
+ main_publish_limit = 1
1512
+ remote = "origin"
1513
+ branch = "main"
1514
+ ```
1515
+
1516
+ The remaining `[canonical]`, `[review]`, and `[authority]` tables retain their v1 fields. Policy v1
1517
+ remains readable, but 1.1.0 uses resource admission in place of aggregate quantity limits.
1518
+
1519
+ Policy v3 retains every v2 capacity/publication field and adds only the automatic-release switch and
1520
+ short intent lifetime:
1521
+
1522
+ ```toml
1523
+ schema_version = 3
1524
+
1525
+ [release]
1526
+ automatic_feature_audit = false
1527
+ intent_ttl_seconds = 3600
1528
+ ```
1529
+
1530
+ When enabled, automatic release applies only to `feature`/`audit` allocations in `HANDOFF_READY`
1531
+ with exact clean committed handoff/Finalizer identity, released and remote-verified integration
1532
+ evidence (ancestry or explicit equivalent replay), no unintegrated result, no runtime/editable/
1533
+ external binding, classified disposable resources, a retained branch, passing official plan, and
1534
+ passing execution-time HEAD/record CAS. `PRESERVED`, canonical, emergency, and recovery allocations
1535
+ are excluded; integration candidates continue to use publication lease completion.
1536
+
1537
+ Policy v4 keeps total-capacity accounting unchanged, names preparation capacity explicitly, and
1538
+ adds the continuation boundary consumed by Agent Workspace:
1539
+
1540
+ ```toml
1541
+ schema_version = 4
1542
+
1543
+ [slots]
1544
+ integration_prepare_limit = 3
1545
+ feature_or_audit = 11
1546
+ emergency = 1
1547
+ recovery_reserve = 1
1548
+
1549
+ [publication]
1550
+ main_publish_limit = 1
1551
+ remote = "origin"
1552
+ branch = "main"
1553
+
1554
+ [release]
1555
+ automatic_feature_audit = true
1556
+ automatic_integration_continuation = true
1557
+ preserved_auto_release = false
1558
+ intent_ttl_seconds = 3600
1559
+ ```
1560
+
1561
+ Count observations no longer require the historical slot sums to equal `normal_limit` or
1562
+ `hard_limit`; their positive values and `normal_limit < hard_limit` remain validated.
1563
+ Integration preparation and the single main-publication lease remain enforced independently.
1564
+ `preserved_auto_release=true` is rejected. Schema v1-v3
1565
+ remain readable and do not enable automatic integration continuation.
1566
+
1567
+ ## Integration and recovery gates
1568
+
1569
+ Repository-writing agents call `write-guard` at the task/write entry boundary after selecting an
1570
+ allocation. `feature-write` is never valid for an integration allocation. Same-task
1571
+ `integration-write`, explicit recovery, and finalization remain available under their lifecycle
1572
+ rules. A `HANDOFF_READY` integration whose HEAD no longer matches its evidence returns
1573
+ `BLOCK_RECOVERY_REQUIRED` before additional work. This is a trusted-agent workflow gate, not
1574
+ same-UID process isolation; `doctor`, handoff, lease, and release continue to revalidate durable
1575
+ state independently.
1576
+
1577
+ Feature handoff evidence binds a base commit, task commit, Finalizer commit, and repository-relative
1578
+ path scope. An integration allocation names one or more integration-ready sources by allocation ID,
1579
+ or resolves a retained handoff commit with `acquire --source-handoff <commit>`; its scope is exactly
1580
+ their union. `handoff`, `doctor`, and `lease-acquire` reject novel non-merge commits,
1581
+ out-of-scope paths, or task/handoff/Finalizer commits that no longer equal HEAD. These are delivery
1582
+ gates, not same-UID process isolation: direct Git writes can still occur, but cannot obtain Controller
1583
+ publication authority after semantic drift.
1584
+
1585
+ `HANDOFF_READY` is a logical write fence. `feature-write` and new active bindings are rejected until
1586
+ `reopen` moves the allocation back to `ACTIVE`; reopen clears old handoff/Finalizer identity and
1587
+ cancels pending intents. Out-of-band dirty, HEAD, record, or binding drift makes old intents/plans
1588
+ stale or blocked rather than granting cleanup authority.
1589
+
1590
+ Two explicit owner/CAS recovery commands cover published historical drift:
1591
+
1592
+ - `recover-handoff --path PATH --owner OWNER --expected-record-version N --expected-head OID`
1593
+ accepts an attached frozen source whose old handoff has an exact ancestry publication receipt
1594
+ and whose advanced HEAD is already on the configured publication ref. It preserves the entire
1595
+ old record and any proven dead writer lease in an immutable recovery preparation receipt,
1596
+ leaves modified/untracked files and the index untouched, and reopens at the observed base.
1597
+ The owner must then test, commit and record a new handoff before normal release.
1598
+ - `recover-published` takes the same identity arguments plus `--branch recovery/NAME`. It requires
1599
+ a clean detached `PUBLISHED` integration, an exact remote-verified receipt with its lease released,
1600
+ and ancestry on the publication ref. It creates a fresh retained branch at the same HEAD and
1601
+ attaches it without checking out files or running checkout hooks. Normal release remains separate.
1602
+
1603
+ Both reject active integration obligations, staged state, runtime references, live writers and
1604
+ uncertain visibility. Neither edits historical publication evidence or grants publication authority.
1605
+ Interrupted preparation can be retried with the same arguments; an existing branch is accepted
1606
+ only when the immutable preparation proves this exact creation attempt and its OID still matches.
1607
+ These receipts use the existing completion-receipts storage, without changing its layout.
1608
+
1609
+ An integration intent freezes task/authority, durable queue order, source allocation/branch,
1610
+ handoff/Finalizer/Snapshot, path-scope digest, base main, candidate attempt/OID, and exact prepared
1611
+ receipt. Queue entries consume no checkout. Non-overlapping intents may use up to the policy
1612
+ preparation limit; a later overlapping intent enters `WAITING_SCOPE`. An intent-bound integration
1613
+ checkout rejects feature writes. `READY_TO_PUBLISH` is write-fenced until explicit reopen;
1614
+ out-of-band HEAD, content, record, or binding drift makes old evidence unusable.
1615
+
1616
+ The bounded `integration-keyed-document-scope/v1` refinement applies only when the entire
1617
+ overlap is the exact `docs/handbook/business-progress.json` path. Every pair of frozen source
1618
+ commits must have one non-ancestor merge base and the same committed `.gitattributes` declaring
1619
+ `merge=qp-handbook-progress`. QP schema 2 records are compared by whole `tasks.authority_key`
1620
+ or `development_lines/modules/decisions.id`, including additions and deletions. All changed key
1621
+ sets must be disjoint. Metadata, schema, ordering, duplicate keys, missing/oversized/non-regular
1622
+ blobs, unknown evidence, broad paths and code overlaps keep the ordinary file fence. Explicit
1623
+ prerequisites and earlier publishing/recovery/attention states still block. The normal claim or
1624
+ reconciler can clear an existing `WAITING_SCOPE` without changing its frozen source or queue order.
1625
+ This proof grants preparation only; publishing still requires a fresh main base and normal validation.
1626
+
1627
+ At lease acquisition Controller compares live `origin/main` to the immutable prepared base. If main
1628
+ advanced, the candidate enters `NEEDS_REPREPARE`; `integration-refresh` then rebuilds from frozen
1629
+ handoffs without rewriting source branches and requires new tests/Snapshot evidence. A true semantic
1630
+ conflict enters `ATTENTION_REQUIRED`; other preparation slots and the publisher remain independent.
1631
+
1632
+ Capability `integration_retry_version` 1 supports an aborted, unpublished intent whose candidate
1633
+ has formally been released. Run `integration-retry --intent-id ID --owner OWNER --reason TEXT
1634
+ --dry-run`, then repeat the exact request with `--expected-plan-id PLAN_ID`. The plan pins frozen
1635
+ source/scope, original queue sequence, failure receipts and queue evidence. Retry increments the
1636
+ attempt and clears old candidate, preparation, tests, Snapshot and validation eligibility;
1637
+ `integration-claim`, validation and publication remain separate mandatory steps. Default retry
1638
+ branches have an attempt suffix, so the original failed branch and receipts remain recoverable.
1639
+
1640
+ Capability `integration_conflict_resolution_version` 1 also permits the same retry plan/CAS for an
1641
+ exact `INTEGRATION_CONFLICT_BLOCKED` failure that never allocated a candidate. It requires no invented
1642
+ abort or release receipt: the original failure event/base, frozen sources and queue evidence are
1643
+ retained. Unknown missing-allocation shapes still fail closed.
1644
+
1645
+ After retry, `integration-claim --intent-id ID --owner OWNER --resolve-conflicts` may claim an actual
1646
+ single-source conflict at the exact fetched main base. Normal scope order and resource gates apply;
1647
+ the decision names `pending_merge_source`. This is an unfinished candidate, never publication proof.
1648
+ Within that governed checkout, merge that exact source, explicitly resolve only the frozen scope,
1649
+ and commit through Git Finalizer's resolved-merge mode. Then run `integration-resolve --path PATH
1650
+ --expected-record-version N --owner OWNER --expected-head FULL_OID --finalizer-record-id RECEIPT`.
1651
+ It checks the unchanged source identity, clean attached HEAD, exact two parents (pinned main, frozen
1652
+ source), and normal commit/path scope before binding the new candidate revision. It does not merge,
1653
+ commit or choose conflict contents. Fresh tests, Snapshot, `integration-validate`, publication lease,
1654
+ remote verification and guarded release remain required. Default claims still refuse conflicts;
1655
+ multiple-source conflicts, arbitrary candidate replacements and scope widening are unsupported.
1656
+
1657
+ When a necessary dependency is already an ancestor of the frozen source but is outside its scope,
1658
+ enqueue its own formally evidenced handoff and use the same plan/apply entry with
1659
+ `--prerequisite-intent CHILD_ID`. The child must be unclaimed, later in the queue, owned by the same
1660
+ integration owner and contain only strict ancestors of the parent source. One append-only parent
1661
+ decision gives it the parent's scheduling position before later overlapping waits; earlier
1662
+ unrelated intents still fence it. Both stored queue sequences, source identities and scopes stay
1663
+ unchanged. The parent stays failed until the prerequisite is formally published, released and
1664
+ absorbed into main, then retry it without `--prerequisite-intent`. This entry never broadens scope,
1665
+ republishes an existing result or resolves a merge conflict by editing another owner's files.
1666
+
1667
+ `lease-acquire --run-id ...` binds repository ID, target ref, holder/run, expected main OID, exact
1668
+ candidate OID, validation evidence, and expiry. Git Finalizer consumes that same lease with
1669
+ `--publish-integration-candidate`; both it and Controller serialize on the existing repository lock.
1670
+ An expired or replaced executor cannot enter the mutation gate. On restart, Finalizer reads live
1671
+ remote first: exact candidate means receipt recovery without another push; unchanged expected main
1672
+ means re-entering the fenced gate.
1673
+
1674
+ `lease-complete --run-id ...` live-verifies the candidate, persists an immutable publication receipt
1675
+ and lease duration, releases the lease, then by default calls the unified guarded release. Once the
1676
+ integration intent is both remote verified and `RELEASED`, Controller creates source `ReleaseIntentV1`
1677
+ records and reconciles them under policy. Repository-control, Finalizer, and Agent Workspace provide
1678
+ events and bindings; only Controller removes worktrees. Historical receipt or stale-intent drift is
1679
+ represented by a new immutable reconciliation receipt: safe historical drift is `DEGRADED`, while
1680
+ identity ambiguity remains a blocking doctor failure.
1681
+
1682
+ With capability `source_release_summary_version` 1, `source_release_trigger.outcomes` reports each
1683
+ source separately while preserving `created_intent_ids`, `skipped`, and `reconciliation`.
1684
+ An already closed source reports `status: RELEASED`, `result: ALREADY_RELEASED`, and its immutable
1685
+ `release_receipt_id` only when unique, complete `lifecycle-history` evidence matches the frozen
1686
+ source branch, handoff, Finalizer identity, and publication association, with no live binding.
1687
+ Retiring its branch afterward does not invalidate that historical closure. Missing or conflicting
1688
+ proof remains blocked with a diagnostic and `recovery_commands`; candidate cleanup still pending
1689
+ reports `WAITING`, and live source reconciliation retains its original manual, retryable, or stale
1690
+ result. These observations do not create release authority or prove business completion. Older
1691
+ runtimes require querying `lifecycle-history` separately; path absence alone never proves closure.
1692
+
1693
+ Recovery is explicit: `quarantine` records the last legitimate commit and observed polluted HEAD;
1694
+ an `acquire --role recovery --source-allocation ... --scope ...` checkout starts at that observed
1695
+ HEAD and explicitly scopes the recovered feature rather than inheriting the integration scope;
1696
+ `externalize` binds its retained branch as lineage evidence. Only then may the polluted checkout be
1697
+ released. Both the original branch and recovery branch remain.
1698
+
1699
+ ## Lifecycle dogfood evidence
1700
+
1701
+ A local lifecycle dogfood covers `plan`, `acquire`, owner review, a Finalizer-linked commit,
1702
+ `integration-ready` handoff, local control-plane integration, explicit owner release, and guarded
1703
+ checkout release. It must retain the feature branch and finish with a clean canonical checkout and
1704
+ passing `doctor` result. Local-only evidence does not prove remote publication or justify mandatory
1705
+ Controller enforcement; those require a separate publication-linked lifecycle run.
1706
+
1707
+ A publication-linked lifecycle acceptance additionally requires non-force control-plane
1708
+ integration, Git Finalizer publication with live remote verification, and release guards evaluated
1709
+ against the published main lineage. A successful acceptance removes only the linked checkout,
1710
+ retains its branch as a recoverable ref, and ends with a clean canonical checkout and passing
1711
+ `doctor` result.
1712
+
1713
+ ## Development
1714
+
1715
+ ### Explicit writer session contract (1.0.0)
1716
+
1717
+ The single `worktree-session` entry point accepts any executable. Its client identity is
1718
+ an opaque identifier, with no provider list, private session reader or adapter family:
1719
+
1720
+ ```bash
1721
+ worktree-session --path /absolute/worktree --client-id terminal \
1722
+ --session-id task-session --run-id task-run -- bash
1723
+ worktree-controller writer-session acquire my-client /absolute/worktree 12345 \
1724
+ --session-id task-session --run-id task-run
1725
+ worktree-controller writer-session check my-client /absolute/worktree 12345 \
1726
+ --session-id task-session --run-id task-run
1727
+ ```
1728
+
1729
+ Admission is `GRANTED`, `NOT_APPLICABLE`, or `REFUSED`. Applicability follows the declared
1730
+ role. A broken policy, corrupt metadata, a conflicting lease or a nested repository cannot
1731
+ turn a governed path into an ungoverned one. Any acquisition/check failure stops execution.
1732
+ The holder retains its exact Linux boot ID, PID and start ticks, plus the explicit client,
1733
+ session and run identities. The resolver also accepts an explicit `ancestor:<program>`;
1734
+ it never infers task identity from command arguments or an executor's session files.
1735
+
1736
+ The launcher acquires and checks once before starting its child. It exports the exact lease
1737
+ through `CONTROLLER_WRITER_MODE=host-managed-v2` and `CONTROLLER_WRITER_CONTEXT`. A host may
1738
+ supply the same context directly. The context contains `holder` (PID, boot ID, start ticks),
1739
+ `client_id`, `session_id`, explicit nullable `run_id`, `repository_path`, `worktree_path`,
1740
+ `allocation_id`, and `lease_id`. `writer-session host-check <actual-caller-path>` is read-only
1741
+ and requires the current allocation, lease, holder, lifecycle and ordinary write-guard to
1742
+ agree. Missing, duplicate, unknown or mismatched fields refuse the operation. Hosted child
1743
+ commands never acquire, release or reconcile their parent's allocation.
1744
+
1745
+ A per-tool host gate passes one `--access` class: `read`, `exec`, or `write` with exactly one
1746
+ absolute `--target`. While the allocation is `ACTIVE` the answer is the unchanged `GRANTED`.
1747
+ After this holder's own handoff (`HANDOFF_READY` or `PRESERVED`), the same holder receives
1748
+ `CONTINUATION_GRANTED` for reads, commands, writes outside governance, and writes into an
1749
+ `ACTIVE` integration candidate whose `source_allocations` include the frozen allocation and whose
1750
+ `integration-write` guard allows them. This is what lets a session that handed off its checkout
1751
+ continue with `integration-enqueue`, `integration-claim`, candidate work, `lease-acquire`,
1752
+ Finalizer publication, `lease-complete` and session end. Writes into the frozen checkout (symlinks
1753
+ resolved), other governed checkouts or repositories, and checks without `--access` keep the
1754
+ original refusal; commands keep their own Controller guards, which still detect drift. A missing
1755
+ or replaced lease, a foreign holder or runtime binding, HEAD drift, an unknown class or a relative
1756
+ target refuse every class. The check never mutates leases, bindings or lifecycle.
1757
+
1758
+ On ordinary child completion, the launcher preserves the command's exit code, releases only
1759
+ its exact lease, and invokes the existing guarded terminal reconciliation. A command that
1760
+ cannot start releases its new lease and leaves the task ACTIVE. Cleanup remains advisory;
1761
+ it never invents task completion, removes another holder or relaxes release evidence.
1762
+
1763
+ Public CLI contract 30, Writer Session 2, writer lease schema 2 and host context version 2
1764
+ replace the former executable contracts; branch retirement publishes contract
1765
+ `branch-retirement/v5` with execution authorization `branch-retirement-execution/v1` on plan schema 1. The package exposes `worktree-controller`,
1766
+ `worktree-session`, and `worktree-session-end` only. Historical releases remain unchanged.
1767
+
1768
+ ### Governed deployment and rollback
1769
+
1770
+ An archived Codex thread can leave a stale agent binding on a shared, still-live
1771
+ app-server after its writer lease was released. Ordinary `binding-remove` still
1772
+ requires the holder to exit. The explicit `--recover-archived-codex-session`
1773
+ option instead verifies the original daemon through its same-user Unix socket,
1774
+ the exact archived rollout/session identity, native `notLoaded` status and loaded
1775
+ thread inventory, and absence of checkout references. It refuses active leases,
1776
+ active/unarchived threads, different peers, identity drift and uncertain scans.
1777
+ Use `--dry-run` first, then the same owner/binding/record CAS without dry-run.
1778
+ The immutable `native-session-recoveries` receipt supports interrupted completion
1779
+ and exact idempotent readback. This action never archives a thread, terminates a
1780
+ daemon, changes source/lifecycle/owner, or weakens the ordinary live-holder gate.
1781
+ Archiving the original thread is a separate authorized native operation; other
1782
+ loaded threads remain running. Native status/transport unavailability fails closed.
1783
+
1784
+ `scripts/controller-activate stage --version <commit>` installs a frozen, commit-pinned
1785
+ runtime beside the active one. Staging leaves live entry points unchanged. Activation and
1786
+ rollback both verify source bytes and the installation receipt, then run the frozen
1787
+ `scripts/check-writer-rollout` gate. Declare every external host using repeatable
1788
+ `--consumer /absolute/capability-command`; every host reports the same
1789
+ `--writer-session-capabilities` contract. There is no built-in application prerequisite.
1790
+
1791
+ For a first pinned installation, initialize the declared repositories with the staged Controller
1792
+ and deploy `scripts/controller-runtime` as the executable `~/bin/controller-runtime` and the three
1793
+ `~/.local/bin/{worktree-controller,worktree-session,worktree-session-end}` entry points. Then run
1794
+ `scripts/controller-activate bootstrap --version <full-commit-oid>` with the same governed repository
1795
+ and consumer declarations. Bootstrap verifies the staged package and runs the same frozen rollout
1796
+ gate, then creates the active pointer only if it is still absent under the activation lock. Existing
1797
+ or dangling pointers require normal activation/recovery. A failed read-only smoke removes only its
1798
+ own new pointer; concurrent pointer drift blocks restoration. Ordinary upgrades keep the verified
1799
+ previous-runtime and ancestry gates.
1800
+
1801
+ Sites using `tool-skill-sync` declare the reviewed owner catalog with
1802
+ `--toolchain-compatibility /absolute/canonical-owner/toolchain_compatibility.json`, or
1803
+ `CONTROLLER_ROLLOUT_TOOLCHAIN_COMPATIBILITY`. Deliver the catalog registration to its owner main
1804
+ before staging or activating a newly advertised CLI, capability or storage version. The deployment
1805
+ entry checks catalog schema 1 / toolchain contract 6 and the target's independent version bindings.
1806
+ The catalog must match its tracked bytes on the owner's canonical `main` or `master`, whose HEAD
1807
+ must equal live `origin`; dirty catalogs, candidate branches, missing/unreachable sources and
1808
+ unreviewed versions refuse acceptance before any runtime switch. Other owner files are not written.
1809
+ The check runs for staged acceptance, before rollout, and again under the switch lock, including
1810
+ bootstrap, rollback and automatic restoration. A changed catalog is rechecked rather than granting
1811
+ authority from a previous observation. Logs identify the owner commit and catalog SHA256; failures
1812
+ are permanent deployment refusals, not temporary rollout retry authority. Sites without a cross-tool
1813
+ workflow retain the independent deployment path when no catalog is declared. After activation,
1814
+ run the default installed `tool-skill-sync doctor --repo <actual-checkout> --summary`.
1815
+
1816
+ Forward activation requires a commit descended from the active runtime and a newer software
1817
+ version. The frozen public
1818
+ contract, installed package and `capabilities --json` must agree on the software, CLI,
1819
+ capability-set and storage-layout versions independently. Every previously advertised
1820
+ versioned capability must remain present without regression; rollback keeps its separate
1821
+ compatibility gate and accepts only an ancestor of the active runtime. The local v1.10.0
1822
+ tag is not an activation target from 1.9.4 because its contract omits the already active
1823
+ `archive-retained` capability.
1824
+
1825
+ The gate requires the declared repositories' governance state, pinned neutral entry points,
1826
+ a runnable staged version, compatible session identities, and quiescent publication and
1827
+ mutation state. Incompatible hosts and live sessions whose transition cannot be proved safe
1828
+ block activation. The exact re-preservation recovery exception below remains supported.
1829
+
1830
+ If the repository mutation lock is busy, the gate defers transaction inspection and reports only
1831
+ the temporary mutation blocker for that repository. A live command's intermediate journal is not
1832
+ classified as interrupted recovery evidence. The next complete gate still verifies the lock,
1833
+ pending transactions and publication lease before granting readiness.
1834
+
1835
+ From 1.24.6, completed feature/audit acquisition evidence also survives ordinary commits in an
1836
+ ACTIVE or RETAINED task. The staged check requires its original acquisition history, unchanged
1837
+ task/base identity, exact Git registration and allocation binding, an attached branch and base
1838
+ ancestry. It leaves the journal and unfinished task untouched; handoff, writer, publication and
1839
+ release continue to require their own authority.
1840
+
1841
+ From 1.24.7, the same acquisition proof survives formal archival and a later guarded release.
1842
+ An ARCHIVED task must match its immutable archive receipt, original acquisition identity and
1843
+ attached descendant branch. RELEASED progress additionally requires the exact release receipt,
1844
+ absence of checkout/registration/bindings/lease, and the matching retained ref or official retirement
1845
+ proof. Archive identity checks read metadata only; preserved payload verification remains in the
1846
+ normal archive-release guards. Journals and task-completion/publication claims remain unchanged.
1847
+
1848
+ When an unfinished transaction blocks activation, the staged read-only check reports the
1849
+ first refused transaction's ID, command, phase, concrete error and recovery entry under
1850
+ `decision.transaction_diagnostics`. The frozen gate retains these fields, adds the repository,
1851
+ and passes them to the activation log. Older staged errors retain their code and message;
1852
+ failed or malformed checks report an explicit diagnostic and remain blocked.
1853
+ An interrupted `release`, including `RELEASED_RECORDED` without its formal release receipt,
1854
+ requires the original guarded `release` request. With capability
1855
+ `release_recovery_inspection_version=1`, inspect that exact transaction through the producer:
1856
+
1857
+ ```bash
1858
+ worktree-controller release-recovery-inspect --repo /absolute/repository \
1859
+ --transaction-id <original-uuid> --json
1860
+ ```
1861
+
1862
+ The query verifies the original journal and ReleasePlan, their repository/allocation/path/branch/
1863
+ HEAD/CAS identity and any referenced publication or release intent. It returns the original
1864
+ owner and `request`, existing evidence paths with raw SHA-256 digests, and `release_argv` as
1865
+ individual arguments. The original CAS comes from the plan, even when the current `RELEASED`
1866
+ record has advanced and its owner is `unassigned`. Missing, malformed or inconsistent evidence
1867
+ is refused. Consumers pass the returned arguments to `worktree-controller` through an argument
1868
+ array, never shell evaluation. Publication recovery preserves `--publication-receipt-id`;
1869
+ intent recovery preserves `--intent-id`.
1870
+ Inspection is read-only and sets `release_authorized=false` and `execution_guards_checked=false`:
1871
+ the original task must still verify its authority and use normal guarded `release` recovery.
1872
+ It does not declare that live resource, binding, retained-ref or other execution guards pass,
1873
+ create a receipt, execute recovery, or clear the rollout gate. On older runtimes, inspect the
1874
+ persisted journal and release plan and verify the exact original identity before normal replay.
1875
+ Once the checkout is removed,
1876
+ `release --dry-run` refuses with `INTERRUPTED_TRANSACTION` and requires formal recovery;
1877
+ this diagnostic adds no dry-run recovery authority. Only supported legacy cleanup requires a new
1878
+ preservation baseline; unknown commands and cleanup contracts remain refused. Recovery entries
1879
+ are hints, require their original identity/evidence arguments, and grant no execution
1880
+ authority. The check never repairs metadata or relaxes activation and retry guards.
1881
+
1882
+ `controller-runtime` supplies the three stable entry points. Each invocation resolves one
1883
+ concrete active runtime and stays on it through completion. A broken active pointer fails
1884
+ closed. Switching uses the activation lock and compares the prior pointer with preflight;
1885
+ a failed post-activation check restores that exact prior runtime only if no later activation
1886
+ has changed the pointer. Rollback has the same gates and no skip-gate escape hatch.
1887
+
1888
+ For temporary concurrent activity, add `--wait-ready-seconds 300` to an ordinary `activate`
1889
+ or `rollback` command. The optional integer budget is 1..3600 seconds; omitting it preserves
1890
+ the immediate-refusal behavior. Only `CONTROLLER_MUTATION_IN_FLIGHT:<repository>`,
1891
+ `CONTROLLER_TRANSACTION_SET_CHANGED:<repository>` and the legacy
1892
+ `LIVE_CONTROLLER_INVOCATION:<pid>` shape are retryable, and every blocker must be one of
1893
+ those types. From 1.30.4 the frozen gate never blocks on a process count: live Controller
1894
+ processes are reported as the `live_controller_invocations` inventory, and
1895
+ `CONTROLLER_MUTATION_IN_FLIGHT` names an exclusive repository-lock holder, so shared-lock
1896
+ admission accounting no longer masquerades as a mutation. Compatibility, publication,
1897
+ unfinished-transaction, unobservable-process, unknown and malformed decisions stop
1898
+ immediately. Each attempt repeats the complete gate from the same frozen source
1899
+ with the same repository, consumer and recovery-proof declarations.
1900
+
1901
+ From 1.24.0, a host may release a writer lease after its runtime reaped the holder: the exact
1902
+ recorded identity plus `--lease-id` releases an exited holder's lease (`holder_exited: true`) when
1903
+ no member of its process group survives and no other process still uses the checkout; anything
1904
+ else still refuses. An intent that was claimed,
1905
+ aborted and retried can now resolve a later pre-allocation conflict: retry judges only the current
1906
+ attempt (`integration_conflict_resolution_version` 2). Renewal of a stale release intent for an
1907
+ unchanged record and HEAD backs off exponentially from the second repeat (TTL doubled per repeat,
1908
+ capped at a day; `deferred: RENEWAL_BACKOFF`). From 1.24.1 retention convergence applies the
1909
+ same backoff before it creates a release intent to classify a frozen handoff's `.venv`
1910
+ (`RENEWAL_BACKOFF` on the row). `scripts/check-shards [N]` runs the test
1911
+ suite in isolated parallel shards plus ruff; `.agents/integration.toml` points Agent Workspace's
1912
+ `just integrate` at it. CLI contract 56, storage layout 4.
1913
+
1914
+ From 1.23.0, `canonical-refresh [--dry-run]` fast-forwards an ACTIVE canonical checkout to the
1915
+ fetched publication branch after integrations publish from candidates. It acts only when the
1916
+ checkout is clean, on the publication branch, holds no writer lease, runtime binding or unfinished
1917
+ transaction, is used by no live process other than the caller's own ancestry, and is an ancestor
1918
+ of published main whose policy remains governable. It reports `CANONICAL_REFRESHED`,
1919
+ `CANONICAL_CURRENT` or `CANONICAL_REFRESH_READY`, and refuses otherwise (a frozen canonical keeps
1920
+ `canonical-transition`). Controller metadata is unchanged. Capability `canonical_refresh_version` 1;
1921
+ CLI contract 55, storage layout 4.
1922
+
1923
+ From 1.24.8, live-process `CANONICAL_IN_USE` refusals additionally expose
1924
+ `decision.process_diagnostics` and the same details in text output. At most five processes show
1925
+ kernel process names (64 characters), parent PID/name and three distinct matched paths (512
1926
+ characters each); omission counts and path-truncation flags mark the evidence limits. The process
1927
+ path scan is reused; identity metadata accepts at most 4096 characters, with one extra character
1928
+ read to detect oversize. Command arguments and
1929
+ environments are not read. Exited, unreadable or malformed identity observations become `unknown`;
1930
+ the original positive occupancy still blocks the attempt. Parent/name details are advisory
1931
+ observations, not verified task ownership or permission to terminate a process.
1932
+
1933
+ Use the owning daemon's native session inventory to identify the exact session and other active
1934
+ work. End only the completed task-owned session through its normal lifecycle; ending the current
1935
+ CLI alone may leave shared-daemon tools running. After the references clear, run the returned
1936
+ `retry_argv` (a guarded `canonical-refresh --dry-run --json`), then repeat without `--dry-run` once
1937
+ ready. These diagnostics do not archive sessions, stop processes or grant cleanup authority.
1938
+ The optional diagnostic field is additive; CLI contract 56 and storage layout 4 are unchanged.
1939
+
1940
+ From 1.22.5, the gate observes transaction membership before and after the staged check, including
1941
+ an initially empty set. Readiness requires the same membership at both observations and in the
1942
+ staged result. Only a successful, well-formed check matching either observed boundary, together
1943
+ with an actual before/after membership change, produces the retryable set-change blocker.
1944
+ Recovery errors, contradictory results, unreadable evidence and a reported set matching neither
1945
+ boundary retain the permanent refusal. These are bounded observations, not an atomic snapshot
1946
+ of the entire gate; the existing mutation, process and publication checks remain mandatory.
1947
+
1948
+ The monotonic budget includes gate execution and the pauses between attempts. Each gate runs in
1949
+ its own process group; expiration terminates that invocation and its children. The prior active
1950
+ pointer is checked before and after attempts; the existing 300-second per-gate command limit also
1951
+ remains fatal. The existing switch CAS remains mandatory.
1952
+ Timeout or pointer drift does not switch or restore a runtime. The option does not apply to
1953
+ bootstrap, layout migration/recovery, or the automatic rollback after a failed post-check.
1954
+
1955
+ For the one-time namespace/schema transition, `activate` also accepts
1956
+ `--migrate-namespace-from <previous-common-directory-name>`,
1957
+ `--migrate-policy-from <previous-repository-relative-policy-path>`, and repeatable
1958
+ `--previous-entrypoint /absolute/installed-entry=SHA256`. All three are required together.
1959
+ Supply the exact governed repository set through `CONTROLLER_ROLLOUT_GOVERNED`, the committed
1960
+ checkout through `CONTROLLER_ROLLOUT_REPO` (defaults to the script's checkout), and the source URL
1961
+ through `CONTROLLER_ROLLOUT_GIT_SOURCE`. Stage and activation refuse an unspecified source. The
1962
+ package index defaults to the normal `uv` configuration; `CONTROLLER_ROLLOUT_INDEX` overrides it.
1963
+ `--temp-helper /absolute/helper` remains available for sites with a shared temporary-directory
1964
+ helper; otherwise the script creates and removes its own temporary directory.
1965
+
1966
+ For a Skill installation that lags the active runtime, use the source-owned maintenance
1967
+ entry point without activating another version:
1968
+
1969
+ ```bash
1970
+ scripts/controller-activate sync-skill --version <full-active-commit-oid> --dry-run
1971
+ scripts/controller-activate sync-skill --version <same-full-active-commit-oid>
1972
+ ```
1973
+
1974
+ Set `CONTROLLER_ROLLOUT_REPO` and `CONTROLLER_ROLLOUT_GIT_SOURCE` to the governed source as
1975
+ above. The entry point verifies the active runtime against that exact committed source,
1976
+ then reads its committed `skills/worktree-lifecycle` payload. Only `SKILL.md` may be replaced;
1977
+ `agents/openai.yaml` must already match, and unknown paths, symlinks, or installed instructions
1978
+ that match no committed ancestor of the active version refuse synchronization. The active
1979
+ pin and installation are checked again under the activation lock. Failed replacement or
1980
+ verification restores the previous Skill bytes; unknown concurrent drift blocks restoration.
1981
+ Runtime pointers, sessions, lifecycle metadata and the shared Claude Skill link are preserved.
1982
+ Dry-run creates no synchronization receipt. Apply writes an identity-only receipt under
1983
+ `~/.local/state/worktree-controller-rollout/`, including when the Skill is already current;
1984
+ that no-change case does not replace the installed file.
1985
+
1986
+ The transition verifies the previous runtime against its own frozen public contract,
1987
+ holds the activation and repository locks, and temporarily refuses old entry points.
1988
+ It moves each unchanged policy to `.agents/worktree-policy.toml` and the existing metadata
1989
+ directory to `worktree-controller/v1`. Only current writer leases change schema:
1990
+ `holder.provider` becomes `holder.client_id`; all identities, ownership, timestamps and
1991
+ expiry remain intact. Directory inodes and historical receipts are preserved. Publication
1992
+ leases, unsupported unfinished transactions, malformed leases, concurrent mutations and
1993
+ conflicting layouts block the transition.
1994
+
1995
+ The layout gate uses the same terminal-evidence classifier as cleanup and doctor. It also
1996
+ admits an exact `tracked-source-no-action-recovery/v1` read-only proof; this neither reconciles
1997
+ the attempt nor authorizes a destructive action. The proof is checked again under the
1998
+ repository lock. Other live completion evidence tied to old paths still blocks migration.
1999
+ Only this explicit migration read view validates schema 1 leases through the existing
2000
+ lease converter in memory; normal runtime parsing still requires schema 2. An unrelated
2001
+ lease survives the transition, while a lease for the cleanup allocation still blocks it.
2002
+ `metadata-reference-relocation/v1` pins the old/new roots, unchanged directory device/inode
2003
+ and each immutable cleanup JSON file's relative path and SHA-256. Its exclusive record
2004
+ travels with the directory rename. Historical references resolve only through this exact
2005
+ inventory and digest, using no-follow reads; there is no second metadata authority, alias
2006
+ symlink, rewritten receipt or guessed legacy namespace. Historical attempt-chain identities
2007
+ remain unchanged. Missing/changed references or an overlapping old namespace fail closed.
2008
+
2009
+ The frozen consumer gate and post-activation checks still run. Failure restores the exact
2010
+ previous policy, state location, entry points and active pointer. The durable layout receipt
2011
+ also supports `rollback --layout-receipt <absolute-receipt> --layout-receipt-sha256 <digest>`;
2012
+ lease renewals are preserved during reverse conversion. Rollback removes only its own
2013
+ additive relocation record after moving original evidence back unchanged; an interrupted
2014
+ inverse move retains its reference map for retry. New reconciliation,
2015
+ action/completion evidence, drift or newly unfinished work blocks rollback without discarding
2016
+ it. After real client acceptance, call `finish-layout`
2017
+ with that same receipt and its latest digest to retire the fenced old entry points and empty
2018
+ old directories. Keep the recovery receipt; it records the retired layout as provenance.
2019
+
2020
+ ### Started resume attempts (0.9.8)
2021
+
2022
+ `cleanup-resume-attempt/v1` records `ATTEMPT_STARTED` separately from immutable action
2023
+ evidence (`ACTION_STARTED`, `ACTION_COMPLETED`). An incomplete call records the underlying
2024
+ status, blocker/error codes, safe result/exception summaries and attempt identity before
2025
+ coordinator diagnostics. Cleanup is an in-process Python call: subprocess exit code is
2026
+ explicitly null, and no stdout/stderr stream is invented. The CLI returns nonzero for
2027
+ `RECOVERY_ATTEMPT_INCOMPLETE`. Missing completion never becomes a `None.json` lookup.
2028
+
2029
+ `resource-recover --stage reconcile-attempt --recovery-receipt <record.json>
2030
+ --recovery-receipt-sha256 <sha256> --dry-run` evaluates `started-no-action-recovery/v1`.
2031
+ Execution additionally requires that dry-run's `--expected-plan-id`. It verifies the exact
2032
+ full source/resource baseline, hardlink topology, both preservation generations, Git/runtime
2033
+ authority and immutable original evidence; new journals, completion receipts or destructive
2034
+ action evidence block this narrow recovery. Absence of a completion receipt alone is insufficient.
2035
+
2036
+ Successful reconciliation appends `STARTED_NO_ACTION_RECOVERABLE`, with the assertion
2037
+ "attempt started, no action proven, safe to supersede". It does not reconstruct a missing
2038
+ historical refusal. A subsequent explicit `--stage resume` creates a new UUID under
2039
+ `attempts/`, binds its predecessor reconciliation, revalidates the baseline, and processes
2040
+ only dist, node_modules and preserved legacy state. Legacy `started.json`, receipts and
2041
+ reconciliation records are retained unchanged. A failed attempt needs a separate new
2042
+ reconciliation; it cannot be blindly retried. Any possible partial action remains blocked.
2043
+ `ATTEMPT_FAILED_BEFORE_ACTION` requires fresh no-action proof; `PARTIAL_ACTION` also covers
2044
+ uncertainty when that proof fails. Host visibility gaps remain warnings, without a claim of
2045
+ host-wide absence of references. Automatic release and disposable types are unchanged.
2046
+
2047
+ ### Exact tracked modified source preservation
2048
+
2049
+ `tracked-source-preservation/v1` preserves only unstaged ` M` regular files in an
2050
+ `ACTIVE` source allocation. The index must match the exact requested HEAD. Staged,
2051
+ untracked, deleted, renamed, symlink/type-changed, sparse or assume-unchanged source
2052
+ states are refused. Ignored resources remain outside this receipt's coverage.
2053
+
2054
+ The manifest binds allocation/owner/record version, worktree identity, base HEAD/tree,
2055
+ the complete dirty path set, Git status, original permission mode, byte size and SHA-256.
2056
+ An independently computed Git tree identifies the base tree plus the exact raw file
2057
+ overlay. This is not a normalized/staged tree: preservation never changes payload bytes,
2058
+ the index, HEAD or worktree. File and parent directory identities are checked before and
2059
+ after copying, including inode, ctime and link count. Runtime authority uses the existing
2060
+ managed/observed/uncertain blockers and records host visibility gaps without a host-wide
2061
+ absence claim.
2062
+
2063
+ The existing no-follow directory/regular-file primitives and immutable generation root
2064
+ are reused. Each generation contains `plan.json`, `manifest.json`, `receipt.json` and the
2065
+ exact file overlay under `tree/`. Files are sealed read-only; their original modes remain
2066
+ in the manifest for reconstruction. An existing generation is never overwritten, a failed
2067
+ copy has no success receipt, and verification checks both current source and preserved
2068
+ bytes against the pinned plan and receipt hash. Unmodified files come from the retained
2069
+ base HEAD. The contract bounds coverage to 256 modified files, 64 MiB per file and
2070
+ 128 MiB total. Git content filters/encoding and CRLF-expanding checkout configurations
2071
+ are unsupported; `core.autocrlf=input` does not transform preserved raw bytes.
2072
+
2073
+ Credential paths are refused before opening payloads. Recognizable private-key/provider
2074
+ secret markers also fail closed before that file is copied; diagnostics never include
2075
+ payloads. This source-only check is not a general secret detector or provider-state
2076
+ preservation backend. Review the exact selected source paths before executing a plan.
2077
+
2078
+ The module CLI is `python -m worktree_controller.source_preservation`: `plan` requires
2079
+ `--repo`, `--path`, `--allocation-id`, `--owner`, `--expected-record-version`, the full
2080
+ `--expected-head` and an exclusive `--plan-file` outside the checkout. `copy --plan-file`
2081
+ creates the generation; `verify --plan-file --receipt-sha256` independently verifies it.
2082
+ The receipt explicitly records `cleanup_authorized=false`: neither preservation nor its
2083
+ verification changes lifecycle or relaxes the ordinary dirty/release gates.
2084
+
2085
+ `tracked-source-cleanup/v1` is a separate, explicit operation. Its module CLI is
2086
+ `python -m worktree_controller.source_cleanup plan --plan-file <preservation-plan>
2087
+ --receipt-sha256 <trusted-sha256>`. Execution requires `execute` and the fresh
2088
+ `--expected-plan-id`. It restores exactly those tracked files to their bound HEAD blob
2089
+ bytes and executable mode. The index remains byte- and identity-identical; there is no
2090
+ reset, stash, staging, commit or implicit release. A modified source inode with multiple
2091
+ hardlinks is refused, so restoration cannot change an out-of-scope linked file.
2092
+
2093
+ Restoration pins each regular inode using `O_PATH | O_NOFOLLOW`, verifies the complete
2094
+ source identity and digest, and reopens only that descriptor for writing. Every write,
2095
+ truncate and mode restoration operates on the verified FD. Parent/leaf changes fail
2096
+ closed; external symlink targets are never opened for writing. Interrupted or short writes
2097
+ may leave an already-preserved source file partial. The Controller journal and independent
2098
+ immutable before/after/failure evidence then remain, block release and forbid blind replay.
2099
+ No automatic rollback or partial-source recovery is introduced by this increment.
2100
+
2101
+ After a verified source cleanup completes, a later Git index stat-cache rewrite does not
2102
+ invalidate archival evidence. Controller 1.2.1 rechecks exact HEAD/tree, every staged mode/blob,
2103
+ index flags, clean source and the preserved/restored bytes at closeout and release. The current
2104
+ index identity and bytes must stay stable throughout that verification; the historical action
2105
+ still proves that restoration itself never changed the index. Old receipts are not rewritten.
2106
+
2107
+ An interrupted typed cleanup with every action still PENDING and no action evidence
2108
+ can use `resource-recover --stage reconcile-typed --transaction-id ... --transaction-sha256 ...`
2109
+ with a fresh dry-run plan ID. It rechecks the complete in-scope inode/member/content baseline,
2110
+ resource classification, owner, Git and runtime. Only changes to an already external link-count
2111
+ remainder and its ctime may be observed; their historical cause remains NOT_PROVEN. Started or
2112
+ partial actions, unknown receipts, content and inode changes remain blocked. The immutable
2113
+ `typed-cleanup-no-action/v1` record supersedes the original pending attempt without changing its
2114
+ journal. A subsequent fresh ordinary cleanup gets a new transaction. Existing approved automatic
2115
+ release intents use the same reconciliation; a clean directory alone grants no such authority.
2116
+ A missing completion receipt or an exact immutable pre-action refusal receipt is supported.
2117
+ The latter must match the original journal/plan/actions, contain no action/runtime checkpoint,
2118
+ and is pinned by SHA-256 in the recovery record. Its historical result remains unchanged.
2119
+ Cleanup preflight refusals now retain the underlying blocker code for diagnosis.
2120
+
2121
+ Every action checks HEAD, dirty path set/digest, mode, index, parent/file identities,
2122
+ runtime authority and the immutable preservation generation. Successful source cleanup
2123
+ records separate completion evidence and formally removes its journal. Completion and
2124
+ release reverify that generation and the restored source. A task with no task-produced
2125
+ commit may then use the existing `complete-no-change` lifecycle; its current completion
2126
+ receipt references the archived source cleanup. This records no historical handoff,
2127
+ successful validation or publication. Typed cleanup also accepts this narrowly evidenced
2128
+ `NO_CHANGE_COMPLETED` state; its resource identity, unknown ignored content, runtime and
2129
+ ordinary release gates remain in force. Other no-change allocations gain no new typed
2130
+ cleanup permission from this contract.
2131
+
2132
+ `controller-transaction-conflict/v1` keeps the repository mutation lock while checking
2133
+ nonterminal conflicts by exact allocation and overlapping resource scope. The
2134
+ coordinator identifies its repository/allocation/transaction, worktree, operation
2135
+ and resource contract. Independent nonterminal resource scopes do not conflict;
2136
+ unknown identity/lifecycle fails closed. Journal counts are never a conflict predicate.
2137
+ Every journal identity is validated on each check, but terminal evidence is verified only for
2138
+ journals in the caller's scope (overlapping resource scope, or the retiring allocation), so large
2139
+ unrelated historical evidence does not slow an unrelated operation.
2140
+ Only verified immutable terminal evidence permits a retained journal to coexist with a
2141
+ new operation, including operations in the same allocation. Neither retention nor
2142
+ absence alone establishes terminal status. Doctor reports historical terminal attempts
2143
+ separately from unresolved transactions.
2144
+
2145
+ An interrupted `acquire` or `integration-refresh` journal closes only on positive
2146
+ current-state evidence. The completion shapes require the checkout directory, its
2147
+ administrative worktree registration, the binding derived from that entry, the record's
2148
+ task commit and the branch ref to agree exactly with the journal; the no-effect shapes
2149
+ require every one of those facts to be provably absent, including the target itself.
2150
+ Half-shapes — an orphan worktree, a dangling detached HEAD, a moved branch, a record
2151
+ that disagrees, or a residual checkout directory — keep the fence until the operator
2152
+ restores one of the two complete shapes. The classifier never rewrites or deletes a
2153
+ journal, reads refs only from the files backend (reftable disables inference), and runs
2154
+ only while the repository flock is held. Branch retirement's `TRANSACTION_ACTIVE` gate
2155
+ uses this same pending/terminal semantics as `doctor` and mutation admission.
2156
+ Dot-prefixed `.tmp` siblings inside `transactions/` are interrupted atomic writes that
2157
+ never became records: conflict checks, doctor, rollout and activation layout scans
2158
+ ignore them, and doctor reports them as a non-blocking residue count.
2159
+
2160
+ `repo.lock` arbitrates every writer. A mutation against a missing lock stops with
2161
+ `MISSING_REPOSITORY_LOCK`, and a symlinked, non-regular or unopenable lock stops with
2162
+ `REPOSITORY_LOCK_INVALID` for readers and writers alike; the lock is opened with
2163
+ `O_NOFOLLOW` and must be a regular readable-writable file, which is also the contract
2164
+ the public publication session enforces on behalf of its consumer. `adopt`, `acquire` and `enqueue-integration`
2165
+ recreate a missing lock as their bootstrap step. Doctor turns both states into
2166
+ blocking findings carrying the exact path, so recovery is `remove or recreate the
2167
+ reported path`, after which any writer re-establishes the arbiter.
2168
+
2169
+ `tracked-source-no-action-recovery/v1` accepts only a recorded transaction-scope refusal
2170
+ with empty actions/checks, no before/action evidence, and an exact fresh source, parent,
2171
+ HEAD, index, preservation-generation and runtime verification. Its module CLI is
2172
+ `python -m worktree_controller.source_recovery plan --repo <repo> --transaction-id <id>
2173
+ --receipt-sha256 <trusted-failed-receipt-sha256>`; `reconcile` additionally requires the
2174
+ fresh `--expected-plan-id`. Reconciliation adds an exclusive immutable record beside the
2175
+ original plan. The old receipt and journal remain byte-identical. The record proves
2176
+ `STARTED_NO_ACTION_RECOVERABLE` and binds one new, single-use successor attempt identity;
2177
+ the original attempt is `SOURCE_ATTEMPT_SUPERSEDED`, not a completed source cleanup.
2178
+ Fresh installed cleanup planning and execution must reverify the full baseline. Any
2179
+ possible partial action remains blocked. Structured underlying refusal and conflict
2180
+ identities survive coordinator/evidence-write errors. Rollout may inspect this state
2181
+ read-only to admit an installed recovery-capable runtime; it never performs reconciliation.
2182
+
2183
+
2184
+ `tracked-source-progress-recovery/v1` (1.13.2) is a separate append-only closeout for
2185
+ an interrupted attempt whose recorded actions form a proper restore prefix with a nonempty preserved remainder. Use
2186
+ `python -m worktree_controller.source_recovery plan-progress` with the same repository,
2187
+ transaction and failed-receipt SHA options; `reconcile-progress` additionally requires
2188
+ its fresh `--expected-plan-id`. All completed actions require their exact before/after
2189
+ chain. An optional last STARTED action must now match its bound HEAD bytes, size and
2190
+ mode on the original single-link inode; it is recorded as a current observation,
2191
+ never retroactively completed. Remaining dirty files must exactly match preservation.
2192
+ Both read-only planning and the locked append reverify authority, runtime, HEAD, index,
2193
+ parents and the immutable generation. Unknown evidence or partial bytes fail closed.
2194
+
2195
+ The exclusive `progress-reconciliation.json` retains the old receipt, journal and
2196
+ before/after records byte-identical. Shared transaction classification reports
2197
+ `SOURCE_ATTEMPT_PRESERVED` only after validating this frozen proof. This closes the
2198
+ historical attempt without source writes, cleanup completion, lifecycle transition,
2199
+ successor grant or release authority. Future legitimate task edits do not invalidate
2200
+ historical terminal evidence; current dirty-source release gates remain unchanged.
2201
+ An unresolved partial attempt still blocks activation: stage the exact recovery-capable
2202
+ commit, execute its supported module, then run the normal rollout/activation gates.
2203
+ Older runtimes that cannot validate this marker remain blocked by rollback preflight;
2204
+ never delete the marker or journal to force rollback compatibility.
2205
+
2206
+ ### Cleanup hardlinks and re-preservation recovery (0.9.7)
2207
+
2208
+ `cleanup-hardlink-topology/v1` records every non-directory inode, all cleanup-scope names,
2209
+ node identities and opaque content digests when the observed name count equals `st_nlink`.
2210
+ Same-resource and cross-resource links retain explicit membership. `cleanup-induced-hardlink-mutation/v1`
2211
+ accepts only the exact link-count decrement for the preceding completed action and its ctime
2212
+ effect; surviving paths, inode, type, mode, size, mtime, ownership, bytes and symlink literals
2213
+ remain unchanged. Every action saves exclusive before/after records under `cleanup-actions`.
2214
+ Deletion continues through verified directory FDs and checks each pinned inode's full identity.
2215
+
2216
+ Controller 1.2.0 additionally writes `cleanup-hardlink-topology/v2` when an inode has external
2217
+ links. It records their exact link-count remainder without searching for, opening or modifying
2218
+ external names. Only already-authorized in-scope names are unlinked. Every unlink pins the inode
2219
+ with `O_PATH|O_NOFOLLOW`, verifies its bytes (or symlink literal) before and after the action,
2220
+ and accepts exactly one link-count decrement plus its ctime effect. Regular bytes are read through
2221
+ that same FD with `O_NOATIME`; FIFO endpoints remain unopened. The final pinned observation is
2222
+ retained in `cleanup-induced-hardlink-mutation/v2`, including when no in-scope name survives.
2223
+ Detected content, identity, ownership, mode, size, mtime or unexpected link-count changes block
2224
+ further actions and retain partial evidence. This proves the observed unlink boundary; it does
2225
+ not exclude external writers globally or claim host-wide absence of references. Unknown content
2226
+ and existing runtime/reference checks retain their own gates.
2227
+
2228
+ The v1 encoding and historical evidence remain unchanged, and the current reader supports both
2229
+ versions. `capabilities.cleanup_hardlink_topology_version=2` is independent of software, metadata
2230
+ and writer-session versions. Activation and rollback use the target installed runtime's existing
2231
+ read-only recovery probe; a target unable to validate retained transactions/terminal evidence is
2232
+ refused. A binary downgrade alone is not a recovery operation.
2233
+
2234
+ `re-preservation-recovery/v1` supports exactly a failed legacy cleanup with `backend/.venv`
2235
+ COMPLETED and `frontend/dist`, `frontend/node_modules`, `.venv` PENDING. It makes no historical
2236
+ causal assertion: `historical_hardlink_cause=NOT_PROVEN`. A new preservation plan binds the
2237
+ original journal/receipt hashes, the current complete source tree and all remaining hardlinks.
2238
+ Current contents and stable identities must still match the earlier preserved state; only the
2239
+ unattributed nlink/ctime differences may exist before this new baseline is frozen. Subsequent
2240
+ source or topology changes invalidate the plan. FIFO nodes are preserved without opening them;
2241
+ symlink targets are never followed and provider files are copied/hashed only as opaque bytes.
2242
+
2243
+ Use the installed runtime's `python -I -m worktree_controller.preservation plan` with the existing
2244
+ allocation/source arguments plus `--recovery-transaction-id`, `--transaction-sha256`, and
2245
+ `--cleanup-receipt-sha256`; then use its `copy --plan-file` command. The new generation never
2246
+ overwrites the old one. `worktree-controller resource-recover --stage prepare --dry-run` requires the
2247
+ same original hashes and the new `--preservation-receipt`/`--preservation-receipt-sha256`.
2248
+ Executing prepare requires its exact `--expected-plan-id` and writes an immutable recovery
2249
+ record. `--stage resume --recovery-receipt ... --recovery-receipt-sha256 ...` revalidates it and
2250
+ uses the ordinary cleanup engine for only the three pending resources. A started execution
2251
+ cannot blindly replay after interruption. Partial cleanup continues to block allocation release.
2252
+
2253
+ Successful recovery writes an independent completion linked to every action and both preserved
2254
+ generations. The historical transaction file remains unchanged; doctor/release recognize its
2255
+ formal terminal evidence instead of deleting or rewriting its history. Allocation release is a
2256
+ separate guarded command. Managed references, observed references and managed uncertainty block;
2257
+ HOST_VISIBILITY_GAP remains warning/evidence and makes no host-wide absence claim. Unknown ignored
2258
+ resources remain blocked. This adds no ordinary disposable type, use lease or automatic release.
2259
+ Older runtimes do not interpret the terminal supplement and continue to block on the retained
2260
+ historical journal; recovery-aware lifecycle operations require Controller 0.9.7 or later.
2261
+
2262
+ To activate that first recovery-aware version, publish and stage it normally, then use the
2263
+ staged installed runtime to create and verify the new preservation generation. Pass its exact
2264
+ `--recovery-preservation-receipt` and `--recovery-preservation-receipt-sha256` to
2265
+ `scripts/controller-activate activate`. The frozen rollout gate asks that staged runtime for
2266
+ `resource-recover --stage rollout-check --dry-run`: it fully validates the current recovery
2267
+ baseline without writing recovery metadata. Only this exact recoverable transaction, or a
2268
+ verified append-only terminal recovery, can remain during activation. All other unfinished
2269
+ transactions, live mutations, publication leases and incompatible callers still block. Formal
2270
+ prepare/resume happens through the active installed runtime after activation.
2271
+
2272
+ ### Checks
2273
+
2274
+ Linux, Python 3.12 and an unprivileged user are the supported acceptance environment. Local checks and
2275
+ `.gitea/workflows/checks.yml` use the same entry point:
2276
+
2277
+ ```bash
2278
+ uv run --locked --python 3.12 --no-env-file python scripts/check-project \
2279
+ --work-dir /absolute/path/to/a/new/check-output-directory
2280
+ ```
2281
+
2282
+ The output directory must not exist. The command retains wheel/sdist artifacts,
2283
+ isolated installation environments, test fixtures and `pytest.xml` for inspection;
2284
+ choose a disposable parent and clean it through your environment's normal cleanup
2285
+ workflow after reviewing the result. Add `--offline` once the locked dependencies,
2286
+ Python interpreter and build backend are cached.
2287
+
2288
+ For an isolated PID namespace, resolve Python 3.12 first and run the check entry point
2289
+ directly as namespace PID 1:
2290
+
2291
+ ```bash
2292
+ wc_checks_python="$(uv python find 3.12)"
2293
+ unshare --user --map-current-user --pid --fork --mount-proc --kill-child=KILL \
2294
+ "$wc_checks_python" scripts/check-project \
2295
+ --work-dir /absolute/path/to/a/new/check-output-directory --offline
2296
+ ```
2297
+
2298
+ This optional mode requires unprivileged Linux namespaces. As PID 1, the entry point
2299
+ runs the checks in a child and continuously reaps adopted orphan fixtures. It forwards
2300
+ SIGINT and SIGTERM to that worker's process group and returns its exit status, using
2301
+ 128 plus the signal number for a signal exit. Command timeouts remain failures; when
2302
+ namespace init exits, Linux terminates the remaining namespace processes. The interpreter
2303
+ flags and check arguments are preserved. Outside PID 1 the ordinary checks run directly.
2304
+
2305
+ If your local environment provides the optional `tool-temp-dir` companion, create
2306
+ one owned acceptance root with a separate `tests` or `fixture` token in its prefix:
2307
+
2308
+ ```bash
2309
+ set -euo pipefail
2310
+ wc_checks_root="$(tool-temp-dir create worktree-controller-tests | \
2311
+ python3 -c 'import json, sys; print(json.load(sys.stdin)["path"])')"
2312
+ uv run --locked --python 3.12 --no-env-file python scripts/check-project \
2313
+ --work-dir "$wc_checks_root/project"
2314
+ ```
2315
+
2316
+ Use a new child such as `"$wc_checks_root/cross-tool"` for optional cross-tool output.
2317
+ After all checks have exited, review the retained results and preserve any evidence
2318
+ you need before cleaning the root:
2319
+
2320
+ ```bash
2321
+ tool-temp-dir validate-cleanup "$wc_checks_root" --allow-owned-fixture-fifo
2322
+ tool-temp-dir cleanup "$wc_checks_root" --allow-owned-fixture-fifo
2323
+ ```
2324
+
2325
+ The fixture prefix and explicit FIFO option allow native cleanup of the suite's
2326
+ owned FIFO fixtures under the unchanged nested output layout. Do not move fixture
2327
+ directories to satisfy cleanup or delete special files by hand. This companion is
2328
+ optional; ordinary acceptance and CI retain the generic output-directory workflow.
2329
+
2330
+ Checks cover Ruff, matching source/package CLI contracts and versions, wheel/sdist
2331
+ builds, rebuilding a wheel from the sdist, installed console commands, distribution
2332
+ README/license metadata and the complete self-contained pytest suite. Package smoke
2333
+ checks run outside the source checkout with isolated Python imports. Tests use fixture
2334
+ state and do not inherit caller credentials, proxy settings or host integration opt-ins.
2335
+ Mandatory version, contract, package metadata and tool entry-point stability checks remain
2336
+ active with Python `-O` or `PYTHONOPTIMIZE=1`.
2337
+ Git Finalizer consumer acceptance tests remain separately opt-in; their skips are shown
2338
+ in pytest results. CI runs on the existing `ubuntu-latest` runner with pinned actions
2339
+ and uv, without publishing packages. The job pins its Ubuntu image, enables Docker's
2340
+ init process to reap orphan fixtures, and installs GNU Time in that disposable container
2341
+ when `/usr/bin/time` is missing. No test or recovery guard is skipped. Manual workflow
2342
+ dispatch can select an existing `source_ref` (commit or tag); the workflow revision and
2343
+ checked-out source identity remain separate. When the disposable CI container starts as root,
2344
+ the workflow gives its checkout and job-local Python/cache/output directories to
2345
+ `nobody:nogroup` and runs acceptance as that user so filesystem permission tests remain effective.
2346
+
2347
+ ### Optional cross-tool acceptance
2348
+
2349
+ After the self-contained checks, run the forty-five acceptance cases against an explicitly
2350
+ selected installed Controller/Finalizer pair. Both tools and the Finalizer retirement companion
2351
+ are optional external prerequisites; the ordinary checks and CI do not require them.
2352
+
2353
+ Alongside retirement and publication recovery, the installed Controller cases cover direct and
2354
+ hosted handoff continuation, writes into other checkouts leased by the same process (including
2355
+ another repository), and refusal of writes into the frozen source or its symlink aliases. They
2356
+ check CLI exit status and unchanged fixture metadata, using subprocesses whose imports cannot
2357
+ fall back to this checkout. Unclassified requests, foreign sessions and unleased targets remain
2358
+ refused. The gate checks `host_handoff_continuation_version` independently of CLI and storage.
2359
+
2360
+ Eight installed-runtime cases also exercise real SIGINT while reporting a wait or re-polling
2361
+ admission, and preserve an already acquired and bound provider for ordinary exit reconciliation.
2362
+ They observe real procfs thread-count changes during five pool expansions and five contractions,
2363
+ retaining live file references. An exiting leader with a live thread produces actual vanished
2364
+ cwd/exe and denied fd observations: completion clears them on a bounded rescan, a persistent live
2365
+ thread stays uncertain, and permission denial alone stays uncertain without a rescan. Only
2366
+ scheduling is instrumented; process identities, errors and admission decisions come from the
2367
+ installed runtime and kernel. The load fixtures require Linux x86_64 or aarch64 and a commit-pinned
2368
+ Controller with the 1.24.2 behavior. Each worker uses the installed interpreter with isolated imports.
2369
+
2370
+ Five installed-CLI cases verify lease release after the host has reaped the holder. The exact
2371
+ recorded holder and lease ID release only writer entitlement, leaving the allocation and work
2372
+ unchanged. A missing or wrong lease ID, any changed holder field, a surviving member of the
2373
+ holder's process group, or another process still using the checkout must refuse without changing
2374
+ the recorded lease or allocation. Real fixture processes exercise these boundaries and close
2375
+ through ordinary no-change completion and guarded release once occupancy clears. The gate checks
2376
+ `exited_holder_release_version` independently of CLI and storage contracts.
2377
+
2378
+ Eight installed-tool cases exercise conflict recovery on the original integration intent. A
2379
+ prepared candidate is formally aborted and released, an intervening fixture integration changes
2380
+ the same file, and the original intent retries, observes a real merge conflict, retries again and
2381
+ publishes an exact two-parent resolution through the installed Git Finalizer. Queue position,
2382
+ frozen source and scope stay unchanged; old validation is cleared and explicit fixture content
2383
+ and Git review is bound again before publication. Public CLI refusals cover a candidate that was
2384
+ never aborted and an aborted candidate that was never released, without editing authority files.
2385
+ Resolution additionally refuses a wrong expected HEAD, an uncommitted candidate edit, a merge
2386
+ outside frozen scope and a commit with the wrong parent topology. These negative fixtures compare
2387
+ the complete public intent, candidate and source observations, publication lease and live remote
2388
+ main before and after refusal. The scope and parent cases deliberately create malformed Git input
2389
+ inside disposable fixtures; that input is not a Finalizer delivery. A resolved candidate also
2390
+ refuses a publication lease until fresh content/Git review is prepared, then publishes normally.
2391
+ All fixture checkouts close through guarded verbs. The gate independently checks
2392
+ `integration_conflict_resolution_version` and requires version 2 from the matching contract.
2393
+
2394
+ Three installed-CLI cases cover release-intent renewal with a real ten-second fixture TTL.
2395
+ The first expiry renews immediately without changing the frozen allocation. Repeated retention
2396
+ passes refuse an unrecognizable fixture environment, then report `RENEWAL_BACKOFF` after a second
2397
+ expiry without creating another active intent, changing the handoff or deleting its payload.
2398
+ Removing an explicit fixture workspace binding changes the record CAS and renews immediately;
2399
+ the registered environment and checkout release only through the existing verified publication
2400
+ and guarded cleanup flow. The fixtures wait for the normal one-minute retention grace and use
2401
+ public CLI responses throughout, without editing private metadata or patching installed code.
2402
+ The gate also checks `retention_convergence_version` independently of CLI and storage contracts.
2403
+
2404
+ Three installed-tool cases cover main advancing after two independent candidates are prepared.
2405
+ One publishes through the normal Controller lease and installed Finalizer; the other's lease
2406
+ request enters `NEEDS_REPREPARE`, clears its old validation and prepared receipt, and leaves its
2407
+ original intent, queue position, source and scope fixed without granting publication authority.
2408
+ An `integration-refresh` request with the old record CAS refuses without changing the public
2409
+ intent, candidate, source, lease or remote main. A current refresh rebuilds that same candidate
2410
+ on the published main; publication still refuses until fresh fixture content/Git review is bound.
2411
+ The new prepared receipt then permits normal Finalizer publication, preserving both changes and
2412
+ closing all published sources and candidates with complete formal release proof. No authority
2413
+ files are edited. The gate independently checks `integration_publication_execution_version`.
2414
+
2415
+ Three installed-tool cases cover the repository's single main publication lease and its real TTL.
2416
+ Two independent prepared candidates compete: an occupied lease refuses the second candidate
2417
+ without changing the public allocations, intents, lease or live main. After a two-second lease
2418
+ expires, its Finalizer request refuses before push; a replacement lease can publish while the
2419
+ old identity remains fenced without disturbing it. A separate fifteen-second lease uses the
2420
+ installed Finalizer's existing crash-after-push hook. Once that lease really expires, an already
2421
+ published candidate refuses replacement and recovers twice with the exact original lease/run/
2422
+ candidate identity, verified remote main and no repeated push. Normal completion closes the
2423
+ published source and candidate with complete formal proof. Fixture cleanup settles only its own
2424
+ remaining lease before guarded abort/release; no clock or private authority files are patched.
2425
+
2426
+ Three more installed-tool cases refuse a wrong run on the current publication lease, a dirty
2427
+ candidate after leasing, and separate fetch/push endpoints on two disposable local bare remotes.
2428
+ The real Finalizer returns `LEASE_MISMATCH`, `PUBLICATION_EVIDENCE_MISMATCH` and `INVALID_REMOTE`
2429
+ respectively. Refusals preserve the complete public allocations, intents and lease, all local refs
2430
+ and remote refs; neither endpoint changes. Each fixture restores only its own file or remote
2431
+ configuration fault before normal completion and cleanup. The original lease, run, candidate and
2432
+ validation then publish normally, with complete source and candidate release proof.
2433
+
2434
+ Two installed-tool cases recheck remote facts after publication leasing. An independent disposable
2435
+ clone advances main through the installed Finalizer's normal branch publication path; the old
2436
+ lease refuses `PUBLICATION_REMOTE_DRIFT` without changing public authority or any refs. Public
2437
+ lease abort, stale-base detection, refresh and fresh validation then publish both changes on the
2438
+ original intent with a new lease, without rewinding main. Another case temporarily relocates only
2439
+ its owned local bare remote. Finalizer refuses `PUBLICATION_REMOTE_UNAVAILABLE`; actual refs at
2440
+ the relocated fixture and public authority remain unchanged. Restoring the endpoint permits normal
2441
+ publication with the original lease. Both cases verify complete source/candidate release proof;
2442
+ fixture failure cleanup restores the endpoint and settles only its own exact lease.
2443
+
2444
+ Three installed public-session cases refuse completion before authorization and an authorized
2445
+ consumer's success claim without a remote push, and disconnect before authorization while an
2446
+ installed status reader waits for the repository lock. The blocked sessions release the lock
2447
+ without changing public authority or Git refs. Each case then publishes through the real Finalizer
2448
+ using its original lease, run and candidate, retaining the intent identity and complete release proof.
2449
+
2450
+ ```bash
2451
+ wc_controller_version=$(uv run --locked --python 3.12 --no-env-file python -c \
2452
+ 'import tomllib; from pathlib import Path; print(tomllib.loads(Path("pyproject.toml").read_text())["project"]["version"])')
2453
+
2454
+ uv run --locked --python 3.12 --no-env-file python scripts/check-cross-tool \
2455
+ --work-dir /absolute/path/to/a/new/cross-tool-output-directory \
2456
+ --controller /absolute/path/to/versioned/installed/worktree-controller \
2457
+ --controller-version "$wc_controller_version" \
2458
+ --git-finalizer /absolute/path/to/git-finalize --finalizer-version 1.8.18 \
2459
+ --retirement-companion /absolute/path/to/git-finalize-retirement-plan.py
2460
+ ```
2461
+
2462
+ Run the example from this checkout's root. It reads the expected Controller version from
2463
+ `pyproject.toml`; the selected installed Controller must match. The command verifies both installed
2464
+ versions and the Controller CLI/storage contracts and selected capabilities before running tests.
2465
+
2466
+ To add the twenty real-process rollout cases (seven activation, eight rollback and five automatic restoration), supply
2467
+ `--rollout-previous-controller /absolute/path/to/previous/installed/worktree-controller`.
2468
+ Both Controller entries must come from original commit-pinned installations; the activation
2469
+ fixture verifies their package bytes, receipts and release ancestry against frozen Git commits.
2470
+ The extended selection requires all sixty-five cases to pass without skips and records both pins.
2471
+
2472
+ Run either selection in a PID namespace to isolate process inventory from unrelated host
2473
+ activity. Prefix the command above with
2474
+ `unshare --user --map-current-user --mount --pid --fork --mount-proc`; the script uses the existing
2475
+ project reaper as PID 1. The current UID remains unprivileged. Each case has an independent HOME,
2476
+ temporary directory and active pointer. The frozen gate and installed CLI responses run normally;
2477
+ process barriers control transaction creation and completion before or after the staged check.
2478
+ Fixtures generate journals through the installed CLI and finish interrupted release through its
2479
+ original request. Cases verify a complete repeated gate, consumer rechecks, permanent recovery
2480
+ refusal, pointer drift, churn timeout and termination of only the attempt's process group.
2481
+ Rollback cases use the older target's own frozen gate and installed CLI. They cover successful
2482
+ rollback, a real mutation-lock refusal followed by a complete gate retry, the older gate's permanent
2483
+ transaction-set refusal, recovery errors, current consumer minimums, ancestor checks, pointer drift
2484
+ and timeout cleanup. A rollback acceptance pair must include an older gate that reports transaction
2485
+ set mismatches as `CONTROLLER_TRANSACTION_UNFINISHED`; it preserves that version's refusal contract.
2486
+ No host active pointer is changed by these fixtures.
2487
+
2488
+ Automatic restoration cases hide only the disposable fixture's Git directory during the new
2489
+ runtime's post-check, observing the installed CLI's real failure before restoring the directory.
2490
+ They then verify the previous runtime's exact frozen gate, successful restoration and its post-check,
2491
+ refusal after a new release-recovery error or consumer capability minimum, fresh review of a changed
2492
+ published compatibility catalog, and refusal to overwrite another activator's pointer. The previous
2493
+ runtime must lack `canonical_refresh_version` while the new runtime advertises version 1.
2494
+
2495
+ The Controller 1.22.4 / Git Finalizer 1.8.18 wheel pair was verified on 2026-10-13: all seven cases
2496
+ passed with zero skips, using CLI contract 54, storage layout 4 and continuation capability 2.
2497
+ The Controller 1.22.3 / Git Finalizer 1.8.18 wheel pair was verified on 2026-10-13: all seven cases
2498
+ passed with zero skips, using CLI contract 54, storage layout 4 and continuation capability 2.
2499
+ The Controller 1.22.1 / Git Finalizer 1.8.18 baseline was verified on 2026-10-12: all seven cases
2500
+ passed with zero skips, using CLI contract 54, storage layout 4 and continuation capability 2.
2501
+ The historical Controller 1.21.0 / Git Finalizer 1.8.18 baseline was verified on 2026-10-12: all three cases
2502
+ passed with zero skips, using CLI contract 53 and storage layout 4.
2503
+ The historical Controller 1.20.4 / Git Finalizer 1.8.18 baseline was verified on 2026-10-12: all three
2504
+ cases passed with zero skips, using CLI contract 52 and storage layout 4. The Controller 1.20.3 /
2505
+ Git Finalizer 1.8.18 baseline was verified on 2026-10-11 with the same three passes and zero skips.
2506
+ Use a versioned installed Controller entry point rather than a HOME-dependent
2507
+ launcher: fixtures have an isolated HOME and do not inherit caller credentials or proxy settings.
2508
+ Add `--offline` when dependencies are cached. The output directory must be new.
2509
+
2510
+ The cases cover publication interrupted after push, recovery and repeated execution, retirement
2511
+ proof drift rejection, occupied retirement authorization, and real canonical-transition-backed
2512
+ local deletion with idempotent completion. They use disposable repositories
2513
+ and local bare remotes, with the selected installed Controller serving the consumer's public RPCs
2514
+ and this matching-version checkout creating fixtures. Success requires all forty-five cases to pass
2515
+ with zero skips. `pytest.xml` and `cross-tool.json` retain the selected versions, contracts, tool
2516
+ entry-point hashes and test result; entry-point drift during the run also fails the gate.
2517
+
2518
+ ### CLI performance baseline
2519
+
2520
+ Run `uv run --locked python scripts/benchmark-cli --work-dir /absolute/new-output
2521
+ --controller before=/absolute/pinned/worktree-controller
2522
+ --controller after=/absolute/pinned/worktree-controller --repeats 7` as one command.
2523
+ The entry points should belong to isolated installed wheels. Resolve a runtime's versioned
2524
+ entry point before passing it; home-dependent launchers do not work with the fixture HOME.
2525
+ The output directory must be new. The script creates only synthetic fixture repositories,
2526
+ uses canonical metadata serializers and release-plan hashing, and rejects diagnostic findings
2527
+ or different JSON outputs (apart from `tool_version`). It does not contact remotes.
2528
+
2529
+ Each invocation is a fresh process. `first_call` is the first measured call after fixture setup
2530
+ and version inspection; it is not a storage-cold measurement. Repeated calls use warm OS caches
2531
+ and bytecode caches isolated under the output directory, avoiding differences from precompiled
2532
+ installed packages. The script records elapsed wall time, GNU Time peak RSS and stdout bytes
2533
+ in `results.json`, alongside all samples. Setup and output comparison are outside the timing.
2534
+ It requires Linux, Python 3.12 and GNU `/usr/bin/time`. Fixture generation measures no writer
2535
+ durability, and no host cache is flushed.
2536
+
2537
+ The 2026-10-01 baseline used Python 3.12.13 on Linux x86_64 with an AMD Ryzen 9 9950X,
2538
+ seven paired repeats and one live checkout. Each synthetic closed release has an allocation
2539
+ record, publication receipt, prepared receipt, release plan and release receipt. Publication
2540
+ receipts remain `REMOTE_VERIFIED` after closure, exercising immutable release-proof joins.
2541
+ History queries select the shared authority with `--limit 10`. Values below are medians in
2542
+ milliseconds, shown as **1.18.2 / 1.18.3**:
2543
+
2544
+ | Closed releases | status | doctor | lifecycle-history |
2545
+ | ---: | ---: | ---: | ---: |
2546
+ | 0 | 61.8 / 62.2 | 60.2 / 62.1 | 62.4 / 62.5 |
2547
+ | 100 | 74.3 / 73.6 | 72.0 / 74.1 | 77.3 / 75.5 |
2548
+ | 1,000 | 175.9 / 175.6 | 194.4 / 174.6 | 203.0 / 200.1 |
2549
+ | 5,000 | 681.6 / 658.0 | 952.0 / 658.3 | 698.5 / 670.6 |
2550
+
2551
+ At 5,000 releases, doctor improves by about 31%; its median peak RSS changes from 62.5 to
2552
+ 63.2 MiB. Status and history code is unchanged; their small timing differences are run variation.
2553
+ All three JSON outputs match across versions after excluding `tool_version`. The 10-entry
2554
+ history page is about 55 KiB at both 1,000 and 5,000 releases. These are local synthetic
2555
+ measurements, not portable latency guarantees; all commands still load and validate the full
2556
+ metadata snapshot, so time and memory continue to grow with history.
2557
+
2558
+ The 2026-10-06 comparison of installed 1.19.8, 1.19.9 and the 1.19.10 wheel used the same
2559
+ fixture, Python 3.12.13 and seven paired repeats. Values below are median milliseconds,
2560
+ shown as **1.19.8 / 1.19.9 / 1.19.10**:
2561
+
2562
+ | Closed releases | status | doctor | lifecycle-history |
2563
+ | ---: | ---: | ---: | ---: |
2564
+ | 0 | 100.7 / 103.4 / 108.6 | 152.8 / 172.5 / 136.6 | 118.3 / 112.9 / 110.7 |
2565
+ | 100 | 150.4 / 151.4 / 128.2 | 99.3 / 110.6 / 103.2 | 101.1 / 102.0 / 108.9 |
2566
+ | 1,000 | 272.0 / 345.6 / 282.3 | 264.0 / 281.0 / 248.0 | 271.5 / 296.0 / 259.4 |
2567
+ | 5,000 | 1049.6 / 1227.0 / 909.7 | 849.8 / 1083.9 / 908.7 | 958.2 / 1231.9 / 896.3 |
2568
+
2569
+ At 5,000 releases, median peak RSS in MiB was:
2570
+
2571
+ | Version | status | doctor | lifecycle-history |
2572
+ | --- | ---: | ---: | ---: |
2573
+ | 1.19.8 | 64.7 | 64.7 | 65.3 |
2574
+ | 1.19.9 | 103.0 | 103.8 | 104.6 |
2575
+ | 1.19.10 | 64.2 | 65.0 | 65.2 |
2576
+
2577
+ The normalized JSON outputs match for all sizes, commands and versions. Profiling confirms
2578
+ that each query loads one repository snapshot. The 1.19.9 command-scoped decode cache retained
2579
+ 25,001 decoded files at 5,000 releases without reusing them; 1.19.10 runs these three queries
2580
+ on the uncached read path and preserves full validation. Other commands keep identity-aware
2581
+ decode reuse within their invocation. Compared with 1.19.9, this run reduces large-query
2582
+ median latency by 16–27% and peak RSS by about 38%. Small timings and the differences from
2583
+ 1.19.8 vary on the shared host; these measurements do not establish portable latency bounds.
2584
+
2585
+ Add `--mode execution-admit --sizes 0 100 1000 --repeats 7` to measure execution admission
2586
+ with the same versioned installed entry points. Each sample uses a new isolated repository,
2587
+ HOME and resource ledger, containing the requested number of synthetic lost-exit host
2588
+ reservations and one actual live writer. The three cases cover immediately available admission,
2589
+ memory waiting that recovers a finished reservation, and an old run's identity recovery.
2590
+ The memory case includes one extra blocking reservation. Identity recovery deliberately measures
2591
+ the expected `EXECUTION_AUTHORITY_CHANGED` refusal after retaining the unfinished task; an
2592
+ untimed formal resume and admission must then succeed. No task completion is inferred.
2593
+
2594
+ This mode starts the selected entry point's Python interpreter and installed `CLI.main` in a
2595
+ fresh process with fixed memory and PSI probes; Git, metadata, locking and durable writes are
2596
+ real. Timing includes the fixture worker's bootstrap, but excludes fixture setup and acceptance
2597
+ postchecks. Bytecode caches are shared only within the output directory, and host caches are
2598
+ not flushed. Fixtures never use the caller's resource ledger, remotes, credentials or proxies.
2599
+ Bulk synthetic history is setup, not evidence of writer durability. Results retain entry-point
2600
+ and package hashes, measured CLI responses, wall time, peak RSS and postchecks for live-writer,
2601
+ scope, publication, request-idempotency and memory-budget invariants. `status=PASS` is written
2602
+ only after every sample passes and the installed identities are unchanged.
2603
+
2604
+ Unlike query mode, this mode does not require identical JSON across versions: 1.19.2 intentionally
2605
+ keeps unrelated finished reservations on its available path, while 1.19.1 reconciles them first.
2606
+ Both states are reported and checked; a lower latency does not count as successful recovery.
2607
+ From 1.19.6, automatic recovery batches proven bounded host exits into one atomic ledger write.
2608
+ Each row retains the same target, runtime-chain and process-group checks; repository recovery
2609
+ still runs separately, outside the host ledger lock. Explicit recovery interfaces are unchanged.
2610
+ From 1.19.7, `admission-queue --all-repositories --apply` also uses that batch while preserving
2611
+ the ordered per-request outcomes. A failed batch or an unproven row still uses ordinary diagnostics;
2612
+ repository-selected and dry-run passes leave host reservations unchanged. Normal host clear and
2613
+ typed dead-holder recovery retain their separate proof requirements.
2614
+
2615
+ Unrelated host process churn can cause a real procfs visibility refusal during old-run recovery.
2616
+ Such a sample stops the run and retains `recovery-refusals.json` and `refusal-inspect.json`; it
2617
+ must not be counted as a successful timing. Where unprivileged namespaces are supported, prefix
2618
+ the benchmark command with `unshare --user --map-current-user --pid --fork --mount-proc` to
2619
+ isolate the fixture processes while retaining real procfs checks. This uses the existing Linux
2620
+ `unshare` command, changes only that test process's namespaces, and grants no host privileges.
2621
+ Results record the PID, mount and user namespace identities.
2622
+ Use `--admission-cases available` (or either recovery case) to bound a focused experiment.
2623
+ `--timeout` limits each invocation; a timeout stops the run, terminates its process group and leaves
2624
+ the output at `RUNNING`, never `PASS`. Preserve that censored observation separately from
2625
+ completed paired samples before choosing a smaller scale or a longer explicit time budget.
2626
+
2627
+ The 2026-10-06 isolated PID-namespace baseline used Python 3.12.13 on Linux x86_64 and
2628
+ the installed 1.19.1 (`827c71a7`) / 1.19.2 (`094891c2`) packages. Seven paired repeats,
2629
+ with fixture setup and acceptance outside timing, produced these medians in milliseconds:
2630
+
2631
+ | Lost-exit history | Available 1.19.1 / 1.19.2 | Memory recovery 1.19.1 / 1.19.2 | Identity recovery 1.19.1 / 1.19.2 |
2632
+ | ---: | ---: | ---: | ---: |
2633
+ | 0 | 95.7 / 101.9 | 108.3 / 164.3 | 183.7 / 170.9 |
2634
+ | 100 | 1026.7 / 121.4 | 939.4 / 1202.4 | 982.3 / 997.8 |
2635
+
2636
+ At 100 entries, immediately available admission improves by about 88%. Waiting pays for an
2637
+ additional admission check; recovery is not generally faster. All completed samples preserve
2638
+ the live writer, scope, publication state, request identity and exact memory budget.
2639
+
2640
+ The 1,000-entry paired experiment stopped at its 60-second limit: one 1.19.1 available call
2641
+ completed in 56.4 seconds and the next timed out. There is no completed 1.19.1 paired median
2642
+ at that scale. A separate 1.19.2 run with a 180-second limit, one repeat plus a first call,
2643
+ passed all three cases: available 166.5 ms, memory recovery 54,922.9 ms, identity recovery
2644
+ 49,620.3 ms (the repeat samples, not seven-repeat medians). Large recovery backlogs therefore
2645
+ remain expensive. Host-procfs refusals and the timed-out comparison are retained separately
2646
+ and are excluded from successful timings. These are synthetic local results, not portable
2647
+ latency guarantees or evidence that real host procfs visibility will always be clear.
2648
+
2649
+ The 2026-10-06 isolated PID-namespace run compared installed 1.19.8, 1.19.9 and the 1.19.10
2650
+ wheel with seven paired repeats and a 30-second per-invocation limit. All three cases completed
2651
+ at every size. Median milliseconds are shown as **1.19.8 / 1.19.9 / 1.19.10**:
2652
+
2653
+ | Lost-exit history | Available | Memory recovery | Identity recovery |
2654
+ | ---: | ---: | ---: | ---: |
2655
+ | 0 | 97.7 / 96.1 / 96.3 | 117.0 / 113.6 / 136.9 | 188.6 / 178.2 / 223.2 |
2656
+ | 100 | 100.6 / 107.5 / 105.0 | 139.1 / 132.6 / 135.8 | 216.7 / 215.7 / 248.1 |
2657
+ | 1,000 | 146.3 / 147.8 / 146.7 | 472.4 / 345.0 / 378.4 | 359.8 / 400.6 / 392.8 |
2658
+
2659
+ Every measured sample passed the live-writer, scope, publication, request-idempotency and exact
2660
+ memory-budget checks, including guarded resume after identity recovery. The query-cache change
2661
+ does not change these paths; their timings vary across the shared-host run and show no consistent
2662
+ latency improvement. These fixtures contain lost-exit host reservations and a small repository;
2663
+ they do not measure admission against a large repository metadata history.
2664
+
2665
+ For either admission mode, add `--repo-sizes 0 1000 5000` to seed closed repository releases
2666
+ independently of `--sizes 0 1000`, which still controls lost-exit host reservations. The
2667
+ Cartesian product measures each axis and their interaction. Release history is seeded after
2668
+ writer setup and before timing, using the same serializers and plan hashing as query mode.
2669
+ Every sample also verifies that all closed records and publication/prepared/release proofs
2670
+ remain unchanged.
2671
+
2672
+ Use `--mode session-admit` to measure the complete new-session CLI path: canonical plan,
2673
+ durable checkout acquisition and execution admission. Its default cases are `available` and
2674
+ `memory-recovery`; checkout planning must succeed before the memory case reaches execution
2675
+ waiting. Existing allocations and identity recovery use the existing fallback/step-by-step
2676
+ path, so `identity-recovery` is accepted only in `execution-admit` mode. Session samples verify
2677
+ that a repeated start returns fallback with the same allocation and leaves the snapshot and
2678
+ ledger unchanged. Untimed checks then replay execution admission and bind an actual live
2679
+ fixture process, requiring its exact holder, writer lease, runtime binding and `WRITE_ALLOWED`
2680
+ guard. The existing scoped writer and exact memory budget remain acceptance gates.
2681
+
2682
+ Add `--profile` for one extra fresh-fixture diagnostic call per version/configuration. These
2683
+ calls use `cProfile` around installed `CLI.main` and record snapshot-load counts, metadata
2684
+ decoder counts and the largest cumulative costs in `profile_diagnostic`. They are excluded
2685
+ from normal latency and RSS medians. Profiles cover the CLI main thread; inventory worker
2686
+ threads and subprocess internals are excluded. All admission runs persist `status=RUNNING` before the
2687
+ first sample; a timeout or failed invariant never becomes a passing timing, and fixture
2688
+ holders are stopped on both success and failure. These remain local synthetic measurements,
2689
+ with fixed memory/PSI probes, warm storage caches and no caller ledger or credentials.
2690
+ The tracked fixture tree contains only its policy file, so these measurements cover metadata
2691
+ and host-history growth, not the checkout cost of a large source tree or executor startup.
2692
+
2693
+ The 2026-10-07 complete new-session comparison used installed 1.19.8 (`bc619da`) and
2694
+ 1.19.10 (`7da723c`), Python 3.12.13, isolated PID namespaces, alternating version order,
2695
+ seven paired repeats and a separate first call. All 192 timed calls passed history, live
2696
+ writer, scope, publication, fallback, holder/lease, request-identity and memory-budget checks.
2697
+ Median milliseconds are shown as **1.19.8 / 1.19.10**:
2698
+
2699
+ | Closed repository releases | Lost-exit host reservations | Session available | Session memory recovery |
2700
+ | ---: | ---: | ---: | ---: |
2701
+ | 0 | 0 | 293 / 384 | 427 / 363 |
2702
+ | 0 | 1,000 | 472 / 466 | 752 / 697 |
2703
+ | 1,000 | 0 | 1,341 / 718 | 1,506 / 1,081 |
2704
+ | 1,000 | 1,000 | 1,502 / 928 | 1,953 / 1,508 |
2705
+ | 5,000 | 0 | 5,070 / 3,051 | 7,652 / 4,837 |
2706
+ | 5,000 | 1,000 | 6,406 / 4,554 | 7,623 / 5,400 |
2707
+
2708
+ At 5,000 releases, session medians improve by 29–40%; with 1,000 host reservations,
2709
+ peak RSS medians are 135.7 / 120.1 MiB (available) and 135.5 / 122.9 MiB (memory
2710
+ recovery). A separate one-repeat profiling pilot at 1,000 releases sees five snapshot loads
2711
+ in either version, but each immutable prepared/publication/release receipt type is decoded
2712
+ 5,000 / 1,000 times.
2713
+ Its diagnostic timings are excluded from the seven-repeat table. Empty-history results vary
2714
+ on this shared host and show no consistent speedup.
2715
+
2716
+ A separate seven-repeat `execution-admit` comparison at 5,000 releases and 1,000 host
2717
+ reservations exposes the single-snapshot cost: available admission takes 1,048 / 1,448 ms
2718
+ and 72.3 / 118.2 MiB. Memory recovery takes 3,104 / 2,978 ms; identity recovery takes
2719
+ 7,809 / 5,358 ms, including its expected refusal and untimed guarded resume. All 48 timed
2720
+ calls pass their invariants. The initial execution profiling output was overwritten by an
2721
+ untimed replay; those diagnostics are excluded. The harness now captures the initial profile
2722
+ and disables profiling for subsequent acceptance calls.
2723
+
2724
+ The subsequent paired 1.19.10 / 1.19.11 wheel comparison uses the same 5,000-release,
2725
+ 1,000-reservation configuration, seven repeats and one separate first call per case. All 48
2726
+ timed calls and six separate profile calls pass their acceptance checks:
2727
+
2728
+ | Standalone execution case | Median milliseconds 1.19.10 / 1.19.11 | Median peak RSS MiB 1.19.10 / 1.19.11 |
2729
+ | --- | ---: | ---: |
2730
+ | Available | 1,473 / 976 | 118.7 / 71.5 |
2731
+ | Memory recovery | 3,274 / 2,646 | 123.2 / 121.8 |
2732
+ | Identity recovery | 6,561 / 6,445 | 124.4 / 124.4 |
2733
+
2734
+ Available admission reduces median latency by 34% and RSS by 40% in this run. Recovery RSS
2735
+ is similar. Recovery samples vary substantially on the shared host; their median reductions
2736
+ are not evidence of a reliable recovery speedup. Profiling confirms one snapshot for available
2737
+ admission, three for memory recovery and eight for identity recovery. The recovery candidate
2738
+ decodes each unchanged receipt type 10,000 times instead of 5,000 because caching starts after
2739
+ the first attempt. That extra decoding is the cost of avoiding cache retention on the ordinary
2740
+ path; profiles are diagnostic and their instrumented timings are excluded from these medians.
2741
+
2742
+ The 2026-10-07 complete `session-admit` comparison of installed 1.19.13 (`c59e0cd`) and the
2743
+ 1.19.14 wheel uses Python 3.12.13, isolated PID namespaces, alternating version order, seven
2744
+ paired repeats and a separate first call. All 192 timed calls and 24 separate profile calls
2745
+ pass history, live-writer, scope, publication, fallback, holder/lease, request-identity and exact
2746
+ memory-budget checks. Median milliseconds are shown as **1.19.13 / 1.19.14**:
2747
+
2748
+ | Closed repository releases | Lost-exit host reservations | Session available | Session memory recovery |
2749
+ | ---: | ---: | ---: | ---: |
2750
+ | 0 | 0 | 233 / 254 | 218 / 215 |
2751
+ | 0 | 1,000 | 434 / 413 | 678 / 601 |
2752
+ | 1,000 | 0 | 664 / 494 | 1,065 / 685 |
2753
+ | 1,000 | 1,000 | 875 / 961 | 2,278 / 1,080 |
2754
+ | 5,000 | 0 | 3,193 / 1,863 | 4,861 / 3,222 |
2755
+ | 5,000 | 1,000 | 4,734 / 2,188 | 6,460 / 3,262 |
2756
+
2757
+ At 5,000 releases, median latency falls by 34–54% in this run. With 1,000 host reservations,
2758
+ median peak RSS changes from 120.4 / 99.3 MiB (available) and 122.9 / 102.6 MiB (memory recovery).
2759
+ Profiling confirms five / three snapshot loads on the available path and seven / five on
2760
+ memory recovery. Planning reuses the session's initial read-only observation; acquisition still
2761
+ rechecks current metadata and authority under its exclusive lock, then uses that validated
2762
+ repository identity. Flat historical metadata files retain the same filename order and validation
2763
+ while sorting by filename instead of comparing complete `Path` objects.
2764
+ The unchanged receipt types are still decoded once per command. These are warm-cache synthetic
2765
+ shared-host measurements; empty and some 1,000-release combinations fluctuate and do not show
2766
+ a consistent speedup. Profile timings are excluded from the medians, and the fixture still does
2767
+ not measure a large tracked checkout or executor startup.
2768
+
2769
+ The subsequent seven-repeat installed 1.19.14 (`cfd8f3a`) / 1.19.15 wheel comparison focuses
2770
+ on 5,000 closed repository releases, with zero or 1,000 lost-exit host reservations. It uses
2771
+ the same Python 3.12.13, isolated PID namespaces, alternating version order and separate first
2772
+ calls. All 64 timed calls and eight separate profile calls pass the existing history,
2773
+ live-writer, scope, publication, fallback, holder/lease, request-identity and memory-budget
2774
+ checks. Medians are shown as **1.19.14 / 1.19.15**:
2775
+
2776
+ | Lost-exit host reservations | Session case | Median milliseconds | Median peak RSS MiB |
2777
+ | ---: | --- | ---: | ---: |
2778
+ | 0 | Available | 1,572 / 1,133 | 93.9 / 82.6 |
2779
+ | 0 | Memory recovery | 2,012 / 1,293 | 93.6 / 83.5 |
2780
+ | 1,000 | Available | 1,674 / 1,307 | 99.3 / 87.6 |
2781
+ | 1,000 | Memory recovery | 2,258 / 1,531 | 102.4 / 90.7 |
2782
+
2783
+ Median latency falls by 22–36% and peak RSS by about 11–12% in this run. Snapshot counts stay
2784
+ at three (available) and five (memory recovery), and unchanged receipt types are still decoded
2785
+ once per command. Fresh `os.scandir` enumeration of five flat historical metadata directories
2786
+ avoids repeated `Path` construction and hashing while preserving filename order and all
2787
+ schema/identity checks. Directory entries never survive a snapshot load; cached decodes retain
2788
+ their existing file-identity checks and command-local lifetime.
2789
+ A separate three-repeat query probe makes 48 calls across empty and 5,000-release histories:
2790
+ `status`, `doctor` and `lifecycle-history` return matching business payloads in both versions;
2791
+ small timing differences do not establish a query speedup. The one-repeat admission pilot and
2792
+ profile diagnostics are excluded from the seven-repeat medians. These remain warm-cache,
2793
+ synthetic shared-host measurements with fixed pressure/memory probes, rather than latency
2794
+ bounds or measurements of large tracked checkouts or executor startup.
2795
+
2796
+ An installed 1.19.15 (`f309ea5`) / 1.19.16 wheel comparison measures the subsequent ASCII hex
2797
+ validation change on Python 3.12.13. A compiled full-match pattern replaces Python character
2798
+ loops for OIDs and SHA-256 digests; exact lengths, lowercase acceptance, whitespace normalization,
2799
+ optional values and errors remain unchanged. Every snapshot still validates all metadata.
2800
+ The existing benchmark uses alternating version order, separate first calls and isolated
2801
+ fixtures. There are 64 timed session calls (seven repeats), 16 immediately available standalone
2802
+ admission calls (three repeats), 96 query calls (seven repeats), and 16 additional session memory
2803
+ recovery calls (seven repeats) to recheck a possible regression. All admission safety checks pass;
2804
+ query business payloads match across versions. Medians below are **1.19.15 / 1.19.16** with
2805
+ 5,000 closed repository releases:
2806
+
2807
+ | Command / case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB |
2808
+ | --- | ---: | ---: | ---: |
2809
+ | Session / available | 0 | 1,071 / 1,043 | 82.8 / 82.4 |
2810
+ | Session / memory recovery | 0 | 1,313 / 1,324 | 82.9 / 83.1 |
2811
+ | Session / available | 1,000 | 1,210 / 1,217 | 87.6 / 87.2 |
2812
+ | Session / memory recovery | 1,000 | 1,608 / 1,704 | 90.1 / 90.6 |
2813
+ | Standalone admission / available | 0 | 696 / 635 | 62.6 / 61.3 |
2814
+ | Standalone admission / available | 1,000 | 742 / 643 | 71.1 / 71.2 |
2815
+ | Status | 0 | 681 / 645 | 59.0 / 59.2 |
2816
+ | Doctor | 0 | 648 / 627 | 60.0 / 60.4 |
2817
+ | Lifecycle history | 0 | 704 / 656 | 60.4 / 60.7 |
2818
+
2819
+ Standalone admission improves by 9–13% in this run, with the candidate faster in all six paired
2820
+ repeat samples. The three large-history queries improve by 3–7%, with the candidate faster in
2821
+ 20 of 21 paired samples. Empty-history query changes are under 10 ms and show no consistent gain.
2822
+ The first session matrix ranges from 3% faster to 6% slower; the separately repeated 1,000-host
2823
+ memory case gives 1,769 / 1,652 ms and 91.0 / 90.7 MiB. The initial regression does not repeat,
2824
+ but these results do not establish a general session speedup. RSS is broadly unchanged.
2825
+ The one-repeat pilot and eight profile diagnostics are excluded from these medians; later pilot
2826
+ cases overlap regression tests and are not acceptance timings. Their snapshot counts remain
2827
+ three / five, with unchanged receipt types decoded once per command. These are synthetic,
2828
+ warm-cache measurements on a shared host, with fixed memory/pressure probes for admission.
2829
+ They do not establish portable latency bounds or measure large tracked checkouts/executor startup.
2830
+
2831
+ The subsequent installed 1.19.16 (`aea5ee9`) / 1.19.17 wheel comparison isolates compact host
2832
+ ledger serialization. Python 3.12.13 and the unchanged benchmark use isolated PID namespaces,
2833
+ alternating version order and separate first calls. All 128 timed calls pass the existing
2834
+ history, live-writer, scope, publication, holder/lease, request-identity and memory-budget checks:
2835
+ 64 session calls (seven paired repeats), 32 standalone admission calls (three paired repeats),
2836
+ and 32 zero-host session rechecks (seven paired repeats). With 5,000 closed repository releases,
2837
+ initial medians are shown as **1.19.16 / 1.19.17**:
2838
+
2839
+ | Command / case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB |
2840
+ | --- | ---: | ---: | ---: |
2841
+ | Session / available | 0 | 1,652 / 1,791 | 84.1 / 83.8 |
2842
+ | Session / memory recovery | 0 | 1,848 / 1,771 | 83.0 / 83.1 |
2843
+ | Session / available | 1,000 | 1,167 / 1,100 | 87.5 / 85.2 |
2844
+ | Session / memory recovery | 1,000 | 1,546 / 1,436 | 90.9 / 87.6 |
2845
+ | Standalone admission / available | 0 | 639 / 633 | 61.6 / 61.4 |
2846
+ | Standalone admission / memory recovery | 0 | 1,257 / 1,262 | 80.6 / 82.1 |
2847
+ | Standalone admission / available | 1,000 | 700 / 631 | 70.6 / 66.1 |
2848
+ | Standalone admission / memory recovery | 1,000 | 1,503 / 1,526 | 87.6 / 83.7 |
2849
+
2850
+ With 1,000 host reservations, session latency falls by 6–7%, with the candidate faster in six
2851
+ of seven pairs for each case. Immediately available standalone admission improves by 10%,
2852
+ with the candidate faster in all three pairs. Standalone memory recovery shows no latency gain;
2853
+ large-host median peak RSS falls by 2–4.5 MiB across these paths.
2854
+ The first session matrix includes a 13-second zero-host memory sample. A separate zero-host
2855
+ recheck gives 989 / 970 ms (available) and 1,310 / 1,280 ms (memory recovery), with peak RSS
2856
+ 82.5 / 82.6 and 83.1 / 83.2 MiB. The initial available-path regression and extreme memory
2857
+ sample do not repeat; these data do not establish a general zero-host speedup. Keep both trials
2858
+ separate rather than pooling them.
2859
+ The 16-call pilot and eight additional profile calls are excluded from formal medians.
2860
+ The pilot also contains an eight-second memory sample; elevated shared-host I/O and memory
2861
+ pressure was observed around these anomalies, without establishing their cause. Profile
2862
+ snapshot counts stay at three / five; the 1,000-host available profile still saves the ledger
2863
+ four times. Only its JSON encoding changes, preserving all durable save boundaries.
2864
+ Measurements remain synthetic and warm-cache, with fixed memory/pressure probes and a tiny
2865
+ tracked checkout. They do not measure executor startup or establish portable latency bounds.
2866
+
2867
+ An installed 1.19.17 (`ab50d05`) / 1.19.18 wheel comparison isolates the optional UUID
2868
+ validation fast path on Python 3.12.13. The unchanged benchmark alternates versions in isolated
2869
+ PID namespaces and separates first calls from seven-repeat medians. All 192 timed calls pass:
2870
+ 64 session calls, 96 query calls across empty and 5,000-release histories, and 32 session
2871
+ available-path rechecks. Admission checks preserve history, live writer, scope, publication,
2872
+ fallback, holder/lease, request identity and memory budgets; query business payloads match.
2873
+ With 5,000 closed repository releases, initial medians are **1.19.17 / 1.19.18**:
2874
+
2875
+ | Command / case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB |
2876
+ | --- | ---: | ---: | ---: |
2877
+ | Session / available | 0 | 1,011 / 1,039 | 82.3 / 82.2 |
2878
+ | Session / memory recovery | 0 | 1,244 / 1,113 | 82.8 / 82.8 |
2879
+ | Session / available | 1,000 | 1,039 / 1,074 | 85.0 / 85.2 |
2880
+ | Session / memory recovery | 1,000 | 1,606 / 1,499 | 87.3 / 87.2 |
2881
+ | Status | 0 | 600 / 550 | 59.4 / 59.4 |
2882
+ | Doctor | 0 | 583 / 558 | 60.0 / 60.1 |
2883
+ | Lifecycle history | 0 | 624 / 595 | 60.0 / 59.6 |
2884
+
2885
+ Memory-recovery session medians improve by 7–11%, with the candidate faster in seven of seven
2886
+ zero-host pairs and five of seven 1,000-host pairs. Large-history query medians improve by 4–8%,
2887
+ with the candidate faster in 18 of 21 pairs. Empty-history queries differ by 1–3 ms without a
2888
+ consistent gain, and RSS is broadly unchanged.
2889
+ The initial available sessions are 3% slower. A separate seven-repeat recheck gives
2890
+ 1,042 / 1,033 ms and 82.4 / 81.7 MiB for zero host reservations (three of seven pairs faster),
2891
+ and 1,121 / 1,077 ms and 84.7 / 84.9 MiB for 1,000 reservations (five of seven pairs faster).
2892
+ The initial median regression does not repeat; these results do not establish a general
2893
+ available-session speedup. Keep the initial and recheck trials separate. Outliers include a
2894
+ 6.2-second baseline and 2.1-second candidate in the initial zero-host available trial, and a
2895
+ 1.8-second candidate in the 1,000-host recheck; their cause is not established.
2896
+ Eight additional profile calls are excluded from medians. Snapshot counts stay at three / five,
2897
+ and historical decoder counts match across versions. In the zero-host available profile,
2898
+ 85,011 optional UUID checks take 0.193 / 0.091 cumulative seconds; these diagnostic timings
2899
+ are not ordinary CLI latency. Every identity still receives validation, and noncanonical UUID
2900
+ forms keep the original validator. Measurements are synthetic and warm-cache on a shared host,
2901
+ with fixed memory/pressure probes and a tiny tracked checkout. They do not measure executor
2902
+ startup or establish portable latency bounds.
2903
+
2904
+ The 2026-10-10 installed 1.19.22 (`a74ebcb`) / 1.19.23 wheel comparison measures reuse of the
2905
+ first nonempty-string normalization result on Python 3.12.13. The unchanged benchmark alternates
2906
+ versions in isolated PID namespaces, with separate first calls and seven paired repeats. All
2907
+ 64 timed session calls and eight separate profile calls pass their existing history, writer,
2908
+ scope, publication, fallback, holder/lease, request-identity and memory-budget checks. With
2909
+ 5,000 closed repository releases, initial medians are **1.19.22 / 1.19.23**:
2910
+
2911
+ | Session case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB | Candidate faster pairs |
2912
+ | --- | ---: | ---: | ---: | ---: |
2913
+ | Available | 0 | 935.5 / 911.6 | 82.6 / 82.6 | 3/7 |
2914
+ | Memory recovery | 0 | 1,139.5 / 1,139.5 | 82.9 / 82.5 | 4/7 |
2915
+ | Available | 1,000 | 999.1 / 993.8 | 85.4 / 85.2 | 4/7 |
2916
+ | Memory recovery | 1,000 | 1,381.0 / 1,435.7 | 87.2 / 87.6 | 2/7 |
2917
+
2918
+ The initial 1,000-host memory-recovery median is 4% slower. A separate seven-pair recheck
2919
+ reversing the first-version order gives 1,386.5 / 1,395.7 ms and 87.1 / 87.8 MiB, with the
2920
+ candidate faster in five of seven pairs. All 16 timed calls and two separate profile calls
2921
+ pass. Keep the initial and recheck trials separate; these results do not establish a general
2922
+ session-latency improvement or a repeatable 4% regression. Snapshot counts remain three / five,
2923
+ and unchanged receipt decoder counts match across versions.
2924
+
2925
+ A separate unprofiled function experiment uses eight fixed valid strings, 800,000 calls per
2926
+ sample and eleven alternating pairs. Median time is 46.0 / 40.9 ms, with the candidate faster
2927
+ in all eleven pairs (11% lower); 21 absence, type, Unicode-whitespace and NUL cases return the
2928
+ same text or error. The four initial profiles show 26–30% lower cumulative string-validation
2929
+ cost at 325,099 / 325,131 calls per command. Profiles are instrumented diagnostics excluded
2930
+ from CLI medians; the isolated function result is not an end-to-end latency claim.
2931
+
2932
+ Two seven-pair query trials each pass all 96 timed calls, with identical business payloads
2933
+ at zero and 5,000 releases. At 5,000 releases, medians in milliseconds are:
2934
+
2935
+ | Command | Initial 1.19.22 / 1.19.23 | Recheck 1.19.22 / 1.19.23 |
2936
+ | --- | ---: | ---: |
2937
+ | Status | 492.5 / 482.5 | 477.1 / 479.2 |
2938
+ | Doctor | 503.6 / 499.6 | 503.3 / 513.4 |
2939
+ | Lifecycle history | 512.5 / 519.0 | 529.1 / 533.9 |
2940
+
2941
+ Initial empty-history doctor and lifecycle-history medians differ by 4–5 ms; the reversed-order
2942
+ recheck is faster for the candidate in all three empty-history queries. Query gains are not
2943
+ consistent across trials. RSS differences stay below 1 MiB in these configurations. The measured
2944
+ benefit is reduced duplicate normalization work; schema validation, errors, snapshot freshness,
2945
+ decode-cache lifetime and durable writes are unchanged. Measurements remain synthetic and
2946
+ warm-cache on a shared host, with fixed pressure probes and a tiny tracked checkout, and do not
2947
+ establish portable latency bounds. The initial profiling pilot is excluded from formal medians.
2948
+
2949
+ The 2026-10-10 installed 1.19.23 (`bb0b90f`) / pre-integration 1.19.24 candidate wheel comparison measures command-local
2950
+ reuse of validated historical mappings. The unchanged benchmark uses Python 3.12.13, isolated
2951
+ PID namespaces, alternating version order, separate first calls and seven paired repeats.
2952
+ All 64 initial timed session calls and eight separate profile calls pass the existing history,
2953
+ writer, scope, publication, fallback, holder/lease, request-identity and budget checks. With
2954
+ 5,000 closed repository releases, medians are **1.19.23 / 1.19.24**:
2955
+
2956
+ | Session case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB | Candidate faster pairs |
2957
+ | --- | ---: | ---: | ---: | ---: |
2958
+ | Available | 0 | 904.6 / 904.6 | 81.9 / 85.6 | 3/7 |
2959
+ | Memory recovery | 0 | 1,119.0 / 1,042.9 | 82.4 / 86.0 | 6/7 |
2960
+ | Available | 1,000 | 969.4 / 1,016.8 | 85.4 / 87.5 | 2/7 |
2961
+ | Memory recovery | 1,000 | 1,355.8 / 1,281.3 | 86.9 / 90.0 | 6/7 |
2962
+
2963
+ The initial 1,000-host available median is 4.9% slower. A separate seven-pair recheck reverses
2964
+ the first-version order and passes all 32 timed calls and four separate profile calls:
2965
+
2966
+ | Session case | Lost-exit host reservations | Median milliseconds | Median peak RSS MiB | Candidate faster pairs |
2967
+ | --- | ---: | ---: | ---: | ---: |
2968
+ | Available | 1,000 | 996.4 / 951.7 | 84.1 / 86.9 | 5/7 |
2969
+ | Memory recovery | 1,000 | 1,379.1 / 1,331.7 | 87.1 / 91.0 | 5/7 |
2970
+
2971
+ Keep the trials separate: ordinary admission has no consistent gain or repeated 4.9%
2972
+ regression. The 1,000-host memory-recovery median is 5.5% / 3.4% lower across the two trials,
2973
+ with the candidate faster in 11 of their 14 pairs. The measured tradeoff is roughly 3–4 MiB
2974
+ of additional command-local peak RSS. Snapshot counts remain three / five, and metadata
2975
+ decoder counts match. The internal file-cache gateway `_decoded` is called 30,007 times
2976
+ instead of 75,010 / 125,018; unchanged collections reuse validated mappings after fresh
2977
+ directory enumeration and file-identity checks. This reduces repeated dispatch, filename
2978
+ validation and mapping construction, without skipping first-read JSON or schema validation.
2979
+
2980
+ Query checks pass all 96 initial calls at zero / 5,000 releases and 48 reversed-order recheck
2981
+ calls at 5,000 releases, with identical business payloads. At 5,000 releases, medians in
2982
+ milliseconds are:
2983
+
2984
+ | Command | Initial 1.19.23 / 1.19.24 | Recheck 1.19.23 / 1.19.24 |
2985
+ | --- | ---: | ---: |
2986
+ | Status | 474.1 / 464.2 | 495.4 / 507.2 |
2987
+ | Doctor | 474.9 / 480.7 | 510.9 / 512.7 |
2988
+ | Lifecycle history | 491.0 / 498.9 | 530.6 / 512.8 |
2989
+
2990
+ These query measurements do not establish a general latency improvement; peak RSS differences
2991
+ stay below 1 MiB. Results remain synthetic and warm-cache on a shared host, with fixed pressure
2992
+ probes and a tiny tracked checkout. They do not establish portable latency bounds. The initial
2993
+ three-pair profiling pilot is excluded from formal medians. Cache lifetime, mutable snapshot
2994
+ isolation, metadata freshness, authority rechecks, locking and durable writes keep their gates.
2995
+
2996
+ The final 2026-10-10 comparison uses installed 1.20.0 (`88094e0`) and the tested 1.20.1 wheel
2997
+ subsequently published as `db43f68`. It retains the unchanged benchmark, Python 3.12.13,
2998
+ isolated PID namespaces, alternating version order, separate first calls and seven paired
2999
+ repeats. With 5,000 closed repository releases and 1,000 lost-exit host reservations, all
3000
+ 32 timed session calls and four separate profile calls pass the existing history, writer,
3001
+ scope, publication, fallback, holder/lease, request-identity and budget checks. Initial medians
3002
+ are **1.20.0 / 1.20.1**:
3003
+
3004
+ | Session case | Median milliseconds | Median peak RSS MiB | Candidate faster pairs |
3005
+ | --- | ---: | ---: | ---: |
3006
+ | Available | 980.5 / 966.1 | 85.3 / 87.9 | 6/7 |
3007
+ | Memory recovery | 1,413.5 / 1,343.0 | 88.0 / 92.3 | 3/7 |
3008
+
3009
+ The initial memory-recovery samples include candidate outliers at 1.676 and 3.002 seconds.
3010
+ A separate memory-recovery recheck reverses the first-version order; all 16 timed calls
3011
+ and two separate profile calls pass:
3012
+
3013
+ | Session case | Median milliseconds | Median peak RSS MiB | Candidate faster pairs |
3014
+ | --- | ---: | ---: | ---: |
3015
+ | Memory recovery | 1,393.3 / 1,373.3 | 88.3 / 92.4 | 5/7 |
3016
+
3017
+ Keep the initial and recheck trials separate. Their memory-recovery medians are 5.0% / 1.4%
3018
+ lower, but the candidate is faster in only eight of the combined 14 pairs. The measured
3019
+ tradeoff is roughly 4 MiB of additional command-local peak RSS. These noisy samples do not
3020
+ establish a general session-latency improvement. Profile snapshot counts remain three / five,
3021
+ historical decoder counts match, and `_decoded` calls fall from 75,010 / 125,018 to 30,007.
3022
+ Instrumented profile timings are excluded from the latency medians.
3023
+
3024
+ Final query checks pass all 48 timed calls at 5,000 releases, with identical business payloads:
3025
+
3026
+ | Command | Median milliseconds, 1.20.0 / 1.20.1 | Median peak RSS MiB, 1.20.0 / 1.20.1 |
3027
+ | --- | ---: | ---: |
3028
+ | Status | 494.2 / 488.1 | 59.4 / 59.6 |
3029
+ | Doctor | 497.1 / 476.4 | 61.0 / 60.4 |
3030
+ | Lifecycle history | 499.2 / 514.5 | 60.5 / 60.4 |
3031
+
3032
+ Query results likewise do not establish a general latency improvement. Measurements remain
3033
+ synthetic and warm-cache on a shared host, with fixed memory/pressure probes and a tiny tracked
3034
+ checkout; they do not establish portable latency bounds. The earlier 1.19.24 tables describe
3035
+ the pre-integration candidate and do not replace this final 1.20.1 comparison.
3036
+
3037
+ ### Writer permission checks
3038
+
3039
+ `scripts/benchmark-cli --mode writer-check` measures both installed `writer-session check`
3040
+ and `writer-session host-check`. The four cases are an active feature write, a read after its
3041
+ holder's handoff, a refused write to the frozen checkout, and a refused foreign session. The
3042
+ selected runtime must advertise `host_handoff_continuation_version >= 2`; its CLI/storage
3043
+ versions, package/entry-point hashes and the benchmark source hash are retained.
3044
+
3045
+ For a baseline, run this from the development environment, substituting a versioned installed
3046
+ Controller entry point and a new output directory under a task-owned temporary root:
3047
+
3048
+ ```bash
3049
+ unshare --user --map-current-user --pid --fork --mount-proc --kill-child=KILL \
3050
+ .venv/bin/python scripts/benchmark-cli --mode writer-check \
3051
+ --work-dir /absolute/new-output \
3052
+ --controller current=/absolute/versioned/worktree-controller \
3053
+ --sizes 0 1000 5000 --repeats 7
3054
+ ```
3055
+
3056
+ `--sizes` counts synthetic closed repository releases (five metadata documents per release).
3057
+ The default sizes are 0, 1,000 and 5,000. Each call is a fresh installed CLI process descending
3058
+ from the fixture's actual live lease holder. Setup creates empty feature tasks and pins one
3059
+ handoff at its unchanged HEAD; setup memory/pressure probes are fixed, while the measured CLI
3060
+ uses its ordinary process and authority checks. No real repository, host ledger or remote is
3061
+ modified. `--repo-sizes` remains specific to admission modes. Add `--profile` for one extra
3062
+ diagnostic process per runtime/case/action/history size, using the selected installed interpreter
3063
+ and CLI with real locks and holder checks. `profile_diagnostic` records snapshot load counts,
3064
+ their cumulative time, historical decoder counts and the largest cumulative costs. Its instrumented
3065
+ latency and RSS are excluded from `first_call`, `samples` and medians.
3066
+
3067
+ First calls follow setup and identity probes; repeated calls use warm OS and task-local
3068
+ bytecode caches. Metadata hashing also warms caches outside the timing. These are synthetic
3069
+ shared-host measurements, not storage-cold timings or portable latency bounds. Multiple
3070
+ selected runtimes alternate order within each case. `results.json` retains every sample,
3071
+ median wall time and peak RSS; `calls/*.json` in each `writer-<size>` fixture retains exact
3072
+ arguments, exit status, decision and stderr.
3073
+
3074
+ Every call verifies the expected grant/refusal, allocation, scope, holder and lease evidence,
3075
+ unchanged repository/host JSON bytes, and an absent write target. Each timed and diagnostic response
3076
+ must also match across selected runtimes after excluding only `tool_version`. Cleanup releases the exact
3077
+ leases, reopens only the empty frozen fixture, and uses Controller `complete-no-change` and
3078
+ guarded `release` for both checkouts. `fixture-cleanup.json` retains their completion/release
3079
+ receipts and verifies zero fixture leases or linked checkouts. Check failures/timeouts retain
3080
+ partial output and still run this cleanup; the overall result becomes `PASS` only after all
3081
+ samples, cleanup and installed-runtime identity rechecks succeed. The containing fixture
3082
+ repositories and retained refs remain disposable evidence until their task-owned output root
3083
+ is cleaned.
3084
+
3085
+ The 2026-10-12 baseline uses installed Controller 1.22.2 (`a3111f9`), CLI contract 54,
3086
+ storage layout 4 and continuation version 2, with Python 3.12.13 on Linux x86_64 in an
3087
+ isolated PID namespace. All 192 timed calls passed their expected authority and metadata
3088
+ checks; all six fixture checkouts were released. Seven repeated samples per row give these
3089
+ direct / hosted median pairs:
3090
+
3091
+ | Case | 0 releases, ms | 1,000 releases, ms | 5,000 releases, ms | 5,000 releases, peak RSS MiB |
3092
+ | --- | ---: | ---: | ---: | ---: |
3093
+ | Active write | 76.6 / 77.7 | 163.0 / 162.1 | 554.0 / 510.1 | 76.82 / 77.06 |
3094
+ | Handoff read | 75.1 / 77.8 | 190.1 / 164.0 | 577.3 / 529.3 | 82.41 / 77.00 |
3095
+ | Frozen write refusal | 90.1 / 90.6 | 187.0 / 165.1 | 616.3 / 564.1 | 82.68 / 76.88 |
3096
+ | Foreign session refusal | 90.1 / 93.9 | 169.4 / 159.9 | 639.2 / 547.3 | 82.24 / 76.40 |
3097
+
3098
+ This is a baseline of one runtime on synthetic warm-cache history. Direct and hosted actions
3099
+ run in separate blocks; these figures do not establish a paired cross-path performance gain
3100
+ or an improvement over an earlier release.
3101
+
3102
+ The 2026-10-13 comparison uses the installed 1.22.2 runtime and an isolated 1.22.3 wheel with
3103
+ Python 3.12.13 in a PID namespace. All 384 timed calls and 48 separate profile calls pass the
3104
+ authority, unchanged-metadata and identical-business-JSON checks; all six fixture checkouts are
3105
+ released. Runtime order alternates within each of seven pairs, with a separate first call.
3106
+ Direct `check` medians are shown as **1.22.2 / 1.22.3**:
3107
+
3108
+ | Case | 0 releases, ms | 1,000 releases, ms | 5,000 releases, ms | 5,000 releases, peak RSS MiB |
3109
+ | --- | ---: | ---: | ---: | ---: |
3110
+ | Active write | 81.0 / 80.3 | 164.8 / 166.8 | 573.9 / 558.3 | 76.85 / 75.89 |
3111
+ | Read after handoff | 84.1 / 84.7 | 185.4 / 172.2 | 700.1 / 588.7 | 82.22 / 75.59 |
3112
+ | Frozen write refusal | 83.3 / 83.0 | 197.7 / 185.4 | 712.7 / 624.0 | 82.33 / 76.24 |
3113
+ | Foreign session refusal | 75.5 / 78.1 | 194.6 / 181.0 | 789.9 / 648.7 | 82.65 / 76.33 |
3114
+
3115
+ At 5,000 releases, each of the three continuation/refusal cases is faster in all seven pairs,
3116
+ with 12–18% lower median latency and about 6 MiB lower peak RSS. Their snapshot loads fall from
3117
+ two to one and live writer-lease decodes from four to two. Historical decoder counts match;
3118
+ the redundant history scan/validation is removed rather than cached across calls. The shared
3119
+ lock covers both the guard and continuation, so Controller authority mutations cannot split
3120
+ their snapshot. Active checks and every hosted case retain one load. Hosted 5,000-release
3121
+ medians vary from 0.6% slower to 5.1% faster; empty-history cases and these unchanged controls
3122
+ do not show a consistent gain. Profile timings are excluded from the table. These remain
3123
+ warm-cache synthetic measurements on a shared host, not portable latency guarantees or a
3124
+ full-suite performance claim.
3125
+
3126
+ The 2026-10-13 cache comparison uses installed 1.22.3 (`7f6db88`) and an isolated 1.22.4 wheel,
3127
+ Python 3.12.13, isolated PID namespaces, alternating runtime order and seven paired repeats.
3128
+ All 384 timed calls and 48 separate profile calls pass the authority, unchanged-metadata and
3129
+ identical-business-JSON checks; all six fixture checkouts are released. At 5,000 synthetic
3130
+ closed releases, medians are shown as **1.22.3 / 1.22.4**:
3131
+
3132
+ | Case | Command | Median ms | Peak RSS MiB | Faster pairs |
3133
+ | --- | --- | ---: | ---: | ---: |
3134
+ | Active write | `check` | 610.0 / 521.3 | 76.27 / 59.00 | 6/7 |
3135
+ | Active write | `host-check` | 536.0 / 508.2 | 76.32 / 58.67 | 7/7 |
3136
+ | Read after handoff | `check` | 559.5 / 510.8 | 76.36 / 59.13 | 7/7 |
3137
+ | Read after handoff | `host-check` | 598.5 / 534.3 | 76.48 / 59.00 | 6/7 |
3138
+ | Frozen write refusal | `check` | 582.7 / 522.8 | 75.81 / 58.56 | 6/7 |
3139
+ | Frozen write refusal | `host-check` | 620.8 / 598.3 | 76.27 / 58.65 | 7/7 |
3140
+ | Foreign session refusal | `check` | 594.6 / 555.2 | 76.12 / 58.57 | 7/7 |
3141
+ | Foreign session refusal | `host-check` | 658.5 / 562.6 | 76.28 / 58.54 | 6/7 |
3142
+
3143
+ The 5,000-release cases use about 17 MiB less peak RSS and have 3.6–14.6% lower median latency.
3144
+ Every profile retains one snapshot load and identical metadata decoder counts. Single-snapshot
3145
+ checks stop retaining decoded values, file-identity dictionaries and copies of historical mappings;
3146
+ the byte reader, full validation and shared-lock decision remain unchanged. At 1,000 releases,
3147
+ peak RSS falls by roughly 3 MiB, while latency gains vary by case. Empty-history medians range
3148
+ from 6.7% faster to 5.7% slower, with no consistent RSS change. These are warm-cache synthetic
3149
+ results on a shared host; they do not establish portable latency guarantees or full-suite performance.