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,1907 @@
1
+ """Command-line interface for worktree-controller."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from . import __version__
12
+ from .branch_retirement import (
13
+ authorize_branch_retirement,
14
+ branch_retirement_executions,
15
+ branch_retirement_status,
16
+ complete_branch_retirement,
17
+ plan_branch_retirement,
18
+ record_branch_retirement,
19
+ verify_branch_retirement,
20
+ )
21
+ from .controller import adopt, doctor, envelope, guard_write, plan, status
22
+ from .delivered_closeout import archive_retained, close_delivered
23
+ from .frozen_recovery import recover_frozen_checkout
24
+ from .governance import gc_apply, gc_plan
25
+ from .integration_conflict import resolve_integration
26
+ from .integration_retry import retry_integration
27
+ from .lifecycle import (
28
+ abandon_integration_intent,
29
+ abort_integration,
30
+ abort_publication_lease,
31
+ acknowledge_lifecycle_event,
32
+ acquire,
33
+ acquire_across,
34
+ acquire_publication_lease,
35
+ acquire_when_free,
36
+ adopt_delivered,
37
+ adopt_recovery,
38
+ claim_integration,
39
+ cleanup_disposable,
40
+ cleanup_typed_disposable,
41
+ close_preserved,
42
+ complete_no_change,
43
+ complete_publication_lease,
44
+ create_release_intent,
45
+ enqueue_integration,
46
+ externalize,
47
+ handoff,
48
+ integration_queue,
49
+ integration_recovery_matrix,
50
+ lifecycle_events,
51
+ park,
52
+ quarantine,
53
+ reconcile_integration_intents,
54
+ reconcile_publication_receipts,
55
+ reconcile_release_intents,
56
+ reconcile_release_plans,
57
+ reconcile_session,
58
+ record_equivalent_replay,
59
+ refresh_integration,
60
+ register_disposable,
61
+ release,
62
+ reopen,
63
+ reopen_integration,
64
+ resume,
65
+ review,
66
+ update_external_binding,
67
+ validate_integration,
68
+ )
69
+ from .models import (
70
+ LIFECYCLE_STATES,
71
+ ORDINARY_ROLES,
72
+ WORKTREE_ROLES,
73
+ ControllerError,
74
+ WriterHolder,
75
+ )
76
+ from .namespace_migration import (
77
+ LEGACY_ROOT_NAME,
78
+ migrate_namespace,
79
+ namespace_migration_receipts,
80
+ plan_namespace_migration,
81
+ verify_namespace,
82
+ )
83
+ from .reconciler import reconcile_writers
84
+ from .rendering import render
85
+ from .retirement_execution import KIND_CAS_DELETE
86
+ from .writer_lease import acquire_writer_lease, release_writer_lease, resolve_holder
87
+
88
+
89
+ def _common_parser() -> argparse.ArgumentParser:
90
+ parser = argparse.ArgumentParser(add_help=False)
91
+ parser.add_argument(
92
+ "--repo", type=Path, default=Path.cwd(), help="path inside the managed Git repository"
93
+ )
94
+ parser.add_argument("--json", action="store_true", help="emit the stable JSON contract")
95
+ return parser
96
+
97
+
98
+ def build_parser() -> argparse.ArgumentParser:
99
+ parser = argparse.ArgumentParser(
100
+ prog="worktree-controller",
101
+ description="Govern worktree budget, authority, ownership, and lifecycle",
102
+ )
103
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
104
+ commands = parser.add_subparsers(dest="command", required=True)
105
+ common = _common_parser()
106
+
107
+ status_parser = commands.add_parser("status", parents=[common], help="show Git and controller state")
108
+ status_parser.add_argument("--namespace-pid", type=int,
109
+ help="compare mounts with an existing writer process")
110
+ history_parser = commands.add_parser(
111
+ "lifecycle-history", parents=[common],
112
+ help="query live allocation and durable closed lifecycle evidence without mutation",
113
+ )
114
+ selector = history_parser.add_mutually_exclusive_group(required=True)
115
+ for name in ("allocation-id", "authority-key", "task-key", "integration-intent-id", "release-receipt-id"):
116
+ selector.add_argument(f"--{name}")
117
+ history_parser.add_argument("--limit", type=int, default=100)
118
+ history_parser.add_argument("--after", help="exclusive release receipt id cursor")
119
+ inspect_parser = commands.add_parser(
120
+ "release-recovery-inspect", parents=[common],
121
+ help="read the exact original release request without recovery or authorization",
122
+ )
123
+ inspect_parser.add_argument("--transaction-id", required=True)
124
+ guide_parser = commands.add_parser(
125
+ "recovery-guide", parents=[common], help="explain a blocked task and its next formal step without mutation",
126
+ )
127
+ guide_selector = guide_parser.add_mutually_exclusive_group(required=True)
128
+ guide_selector.add_argument("--path", type=Path)
129
+ guide_selector.add_argument("--intent-id")
130
+ admission_parser = commands.add_parser("admission-queue", parents=[common],
131
+ help="inspect or resume durable resource admission requests")
132
+ admission_parser.add_argument("--apply", action="store_true")
133
+ admission_parser.add_argument("--all-repositories", action="store_true")
134
+ host_parser = commands.add_parser("host-execution", parents=[common],
135
+ help="budget and track a process without a repository writer lease")
136
+ host_parser.add_argument("--action",
137
+ choices=("admit", "bind", "transfer", "clear", "cancel", "inspect"),
138
+ required=True)
139
+ host_parser.add_argument("--path", type=Path)
140
+ host_parser.add_argument("--request-id")
141
+ host_parser.add_argument("--client-id", default="agent-workspace")
142
+ host_parser.add_argument("--session-id")
143
+ host_parser.add_argument("--run-id")
144
+ host_parser.add_argument("--holder-pid", type=int)
145
+ host_parser.add_argument("--memory-bytes", type=int, default=2 * 1024**3)
146
+ host_parser.add_argument("--recover-dead-holder", action="store_true",
147
+ help="typed release of an ALLOCATED reservation whose own recorded holder "
148
+ "is provably dead; needs that holder digest and terminal evidence")
149
+ host_parser.add_argument("--expected-holder-sha256",
150
+ help="sha256 of the reservation's recorded holder, presented by the caller")
151
+ host_parser.add_argument("--terminal-evidence",
152
+ help="canonical JSON terminal-state record for the reservation's session/run")
153
+ for execution_action in ("admit", "cancel"):
154
+ execution_parser = commands.add_parser("execution-" + execution_action, parents=[common],
155
+ help="reserve or cancel resources for a never-launched execution")
156
+ execution_parser.add_argument("--path", type=Path, required=True)
157
+ execution_parser.add_argument("--owner", required=True)
158
+ execution_parser.add_argument("--client-id", required=True)
159
+ execution_parser.add_argument("--session-id", required=True)
160
+ execution_parser.add_argument("--run-id")
161
+ execution_parser.add_argument("--memory-bytes", type=int, default=2 * 1024**3)
162
+ admit_session_parser = commands.add_parser(
163
+ "session-admit", parents=[common],
164
+ help="acquire a new feature allocation and admit its execution in one process",
165
+ )
166
+ admit_session_parser.add_argument("--task-key", required=True)
167
+ admit_session_parser.add_argument("--authority-key", required=True)
168
+ admit_session_parser.add_argument("--module", required=True)
169
+ admit_session_parser.add_argument("--owner", required=True)
170
+ admit_session_parser.add_argument("--branch", required=True)
171
+ admit_session_parser.add_argument("--base-ref", required=True)
172
+ admit_session_parser.add_argument("--client-id", required=True)
173
+ admit_session_parser.add_argument("--session-id", required=True)
174
+ admit_session_parser.add_argument("--run-id")
175
+ bind_session_parser = commands.add_parser(
176
+ "session-bind", parents=[common],
177
+ help="grant, check and register one live executor's write entitlement in one process",
178
+ )
179
+ bind_session_parser.add_argument("--path", type=Path, required=True)
180
+ bind_session_parser.add_argument("--task-key", required=True)
181
+ bind_session_parser.add_argument("--authority-key", required=True)
182
+ bind_session_parser.add_argument("--owner", required=True)
183
+ bind_session_parser.add_argument("--client-id", required=True)
184
+ bind_session_parser.add_argument("--holder-pid", type=int, required=True)
185
+ bind_session_parser.add_argument("--session-id", required=True)
186
+ bind_session_parser.add_argument("--run-id")
187
+ bind_session_parser.add_argument("--kind", choices=["agent", "command", "test"], default="agent")
188
+ commands.add_parser(
189
+ "capabilities", parents=[common], help="read caller compatibility capabilities"
190
+ )
191
+
192
+ refresh_parser = commands.add_parser(
193
+ "canonical-refresh", parents=[common],
194
+ help="fast-forward an ACTIVE idle canonical checkout to published main",
195
+ )
196
+ refresh_parser.add_argument("--dry-run", action="store_true")
197
+
198
+ canonical_parser = commands.add_parser(
199
+ "canonical-transition", parents=[common],
200
+ help="advance a frozen canonical checkout to exact published main",
201
+ )
202
+ canonical_parser.add_argument("--path", type=Path, required=True)
203
+ canonical_parser.add_argument("--expected-record-version", type=int, required=True)
204
+ canonical_parser.add_argument("--owner", required=True)
205
+ canonical_parser.add_argument("--expected-branch", required=True)
206
+ canonical_parser.add_argument("--expected-head", required=True)
207
+ canonical_parser.add_argument("--expected-main-head", required=True)
208
+ canonical_parser.add_argument("--expected-remote-head", required=True)
209
+ canonical_parser.add_argument("--new-owner", required=True)
210
+ canonical_parser.add_argument("--new-task-key", required=True)
211
+ canonical_parser.add_argument("--new-authority-key", required=True)
212
+
213
+ plan_parser = commands.add_parser(
214
+ "plan", parents=[common], help="make a read-only allocation decision"
215
+ )
216
+ plan_parser.add_argument("--task-key", required=True)
217
+ plan_parser.add_argument("--authority-key")
218
+ plan_parser.add_argument("--module", required=True)
219
+ plan_parser.add_argument("--role", required=True, choices=sorted(WORKTREE_ROLES))
220
+ plan_parser.add_argument("--target", type=Path)
221
+
222
+ wait_parser = commands.add_parser(
223
+ "acquire-when-free",
224
+ parents=[common],
225
+ help="acquire, waiting a bounded time for capacity that may free",
226
+ )
227
+ wait_parser.add_argument("--task-key", required=True)
228
+ wait_parser.add_argument("--authority-key", required=True)
229
+ wait_parser.add_argument("--module", required=True)
230
+ wait_parser.add_argument("--role", required=True, choices=sorted(ORDINARY_ROLES))
231
+ wait_parser.add_argument("--owner", required=True)
232
+ wait_parser.add_argument("--branch", required=True)
233
+ wait_parser.add_argument("--base-ref", required=True)
234
+ wait_parser.add_argument("--path", type=Path)
235
+ wait_parser.add_argument("--scope", action="append", default=[])
236
+ wait_parser.add_argument("--deadline-seconds", type=int, required=True)
237
+ wait_parser.add_argument("--poll-seconds", type=float, default=2.0)
238
+
239
+ across_parser = commands.add_parser(
240
+ "acquire-across",
241
+ parents=[common],
242
+ help="admit one task in every required repository, or in none",
243
+ )
244
+ across_parser.add_argument(
245
+ "--request",
246
+ action="append",
247
+ required=True,
248
+ help="JSON object naming repository, task_key, authority_key, module, role, "
249
+ "owner, branch, base_ref and optional target/scope_paths",
250
+ )
251
+
252
+ guard_parser = commands.add_parser(
253
+ "write-guard", parents=[common], help="authorize a declared task write before editing"
254
+ )
255
+ guard_parser.add_argument("--path", type=Path, required=True)
256
+ guard_parser.add_argument("--task-key", required=True)
257
+ guard_parser.add_argument("--authority-key", required=True)
258
+ guard_parser.add_argument("--owner", required=True)
259
+ guard_parser.add_argument("--holder", help="pid or ancestor:<program> of the lease holder")
260
+ guard_parser.add_argument("--client-id", default="manual")
261
+ guard_parser.add_argument("--session-id")
262
+ guard_parser.add_argument("--run-id")
263
+ guard_parser.add_argument(
264
+ "--operation",
265
+ required=True,
266
+ choices=["feature-write", "integration-write", "recovery", "finalization"],
267
+ )
268
+
269
+ adopt_parser = commands.add_parser(
270
+ "adopt", parents=[common], help="adopt a registered worktree into metadata"
271
+ )
272
+ adopt_parser.add_argument("--path", type=Path, required=True)
273
+ adopt_parser.add_argument("--task-key", required=True)
274
+ adopt_parser.add_argument("--authority-key", required=True)
275
+ adopt_parser.add_argument("--module", required=True)
276
+ adopt_parser.add_argument("--role", required=True, choices=sorted(WORKTREE_ROLES))
277
+ adopt_parser.add_argument("--owner", required=True)
278
+ adopt_parser.add_argument("--lifecycle", required=True, choices=sorted(LIFECYCLE_STATES))
279
+ adopt_parser.add_argument("--base-commit")
280
+ adopt_parser.add_argument("--task-commit")
281
+ adopt_parser.add_argument("--scope", action="append", default=[])
282
+ adopt_parser.add_argument("--source-allocation", action="append", default=[])
283
+ adopt_parser.add_argument("--external-binding", action="append", default=[])
284
+
285
+ acquire_parser = commands.add_parser(
286
+ "acquire", parents=[common], help="create one policy-approved linked worktree"
287
+ )
288
+ acquire_parser.add_argument("--task-key", required=True)
289
+ acquire_parser.add_argument("--authority-key", required=True)
290
+ acquire_parser.add_argument("--module", required=True)
291
+ acquire_parser.add_argument("--role", required=True, choices=sorted(WORKTREE_ROLES))
292
+ acquire_parser.add_argument("--owner", required=True)
293
+ acquire_parser.add_argument("--branch", required=True)
294
+ acquire_parser.add_argument("--base-ref", required=True)
295
+ acquire_parser.add_argument("--path", type=Path)
296
+ acquire_parser.add_argument("--scope", action="append", default=[])
297
+ acquire_parser.add_argument("--source-allocation", action="append", default=[])
298
+ acquire_parser.add_argument("--source-handoff", action="append", default=[])
299
+ acquire_parser.add_argument("--external-binding", action="append", default=[])
300
+
301
+ commands.add_parser(
302
+ "integration-queue", parents=[common], help="show frozen integration intent queue"
303
+ )
304
+
305
+ enqueue_parser = commands.add_parser(
306
+ "integration-enqueue",
307
+ parents=[common],
308
+ help="queue a frozen source handoff without occupying a checkout",
309
+ )
310
+ enqueue_parser.add_argument("--task-key", required=True)
311
+ enqueue_parser.add_argument("--authority-key", required=True)
312
+ enqueue_parser.add_argument("--module", required=True)
313
+ enqueue_parser.add_argument("--owner", required=True)
314
+ enqueue_parser.add_argument("--source-allocation", action="append", default=[])
315
+ enqueue_parser.add_argument("--source-handoff", action="append", default=[])
316
+ enqueue_parser.add_argument("--source-snapshot", action="append", default=[])
317
+
318
+ claim_parser = commands.add_parser(
319
+ "integration-claim",
320
+ parents=[common],
321
+ help="claim the oldest queued intent into an available candidate slot",
322
+ )
323
+ claim_parser.add_argument("--owner", required=True)
324
+ claim_parser.add_argument("--intent-id")
325
+ claim_parser.add_argument("--path", type=Path)
326
+ claim_parser.add_argument("--branch")
327
+ claim_parser.add_argument("--resolve-conflicts", action="store_true")
328
+
329
+ resolve_parser = commands.add_parser(
330
+ "integration-resolve", parents=[common],
331
+ help="bind an owner-resolved two-parent merge to its pending conflict claim",
332
+ )
333
+ resolve_parser.add_argument("--path", type=Path, required=True)
334
+ resolve_parser.add_argument("--expected-record-version", type=int, required=True)
335
+ resolve_parser.add_argument("--owner", required=True)
336
+ resolve_parser.add_argument("--expected-head", required=True)
337
+ resolve_parser.add_argument("--finalizer-record-id", required=True)
338
+
339
+ refresh_parser = commands.add_parser(
340
+ "integration-refresh",
341
+ parents=[common],
342
+ help="rebuild a validated candidate from frozen handoffs and latest main",
343
+ )
344
+ refresh_parser.add_argument("--path", type=Path, required=True)
345
+ refresh_parser.add_argument("--expected-record-version", type=int, required=True)
346
+ refresh_parser.add_argument("--owner", required=True)
347
+
348
+ validate_parser = commands.add_parser(
349
+ "integration-validate",
350
+ parents=[common],
351
+ help="bind validation evidence to the exact candidate revision",
352
+ )
353
+ validate_parser.add_argument("--path", type=Path, required=True)
354
+ validate_parser.add_argument("--expected-record-version", type=int, required=True)
355
+ validate_parser.add_argument("--owner", required=True)
356
+ validate_parser.add_argument("--validation-evidence-id", required=True)
357
+ validate_parser.add_argument("--tests-receipt-id")
358
+ validate_parser.add_argument("--snapshot-receipt-id")
359
+
360
+ integration_reopen_parser = commands.add_parser(
361
+ "integration-reopen",
362
+ parents=[common],
363
+ help="invalidate prepared evidence before changing an integration candidate",
364
+ )
365
+ integration_reopen_parser.add_argument("--path", type=Path, required=True)
366
+ integration_reopen_parser.add_argument("--expected-record-version", type=int, required=True)
367
+ integration_reopen_parser.add_argument("--owner", required=True)
368
+
369
+ events_parser = commands.add_parser(
370
+ "lifecycle-events",
371
+ parents=[common],
372
+ help="list durable integration lifecycle events not acknowledged by a consumer",
373
+ )
374
+ events_parser.add_argument("--consumer", required=True)
375
+
376
+ event_ack_parser = commands.add_parser(
377
+ "lifecycle-event-ack",
378
+ parents=[common],
379
+ help="idempotently acknowledge one durable integration lifecycle event",
380
+ )
381
+ event_ack_parser.add_argument("--consumer", required=True)
382
+ event_ack_parser.add_argument("--event-id", required=True)
383
+
384
+ commands.add_parser(
385
+ "integration-recovery-matrix",
386
+ parents=[common],
387
+ help="show the supported integration transaction recovery contract",
388
+ )
389
+
390
+ equivalent_parser = commands.add_parser(
391
+ "integration-equivalent",
392
+ parents=[common],
393
+ help="bind explicit equivalent-replay evidence before integration validation",
394
+ )
395
+ equivalent_parser.add_argument("--path", type=Path, required=True)
396
+ equivalent_parser.add_argument("--expected-record-version", type=int, required=True)
397
+ equivalent_parser.add_argument("--owner", required=True)
398
+ equivalent_parser.add_argument("--source-allocation", required=True)
399
+ equivalent_parser.add_argument("--source-handoff", required=True)
400
+ equivalent_parser.add_argument("--replay-commit", required=True)
401
+ equivalent_parser.add_argument("--evidence-id", required=True)
402
+
403
+ no_change_parser = commands.add_parser(
404
+ "complete-no-change",
405
+ parents=[common],
406
+ help="record a genuine no-change completion for a task that produced no commit",
407
+ )
408
+ no_change_parser.add_argument("--path", type=Path, required=True)
409
+ no_change_parser.add_argument("--expected-record-version", type=int, required=True)
410
+ no_change_parser.add_argument("--owner", required=True)
411
+ delivered_parser = commands.add_parser(
412
+ "close-delivered", parents=[common],
413
+ help="close an owner-reviewed retained or legacy preserved checkout already published",
414
+ )
415
+ delivered_parser.add_argument("--path", type=Path, required=True)
416
+ delivered_parser.add_argument("--expected-record-version", type=int, required=True)
417
+ delivered_parser.add_argument("--owner-release", required=True)
418
+ delivered_parser.add_argument("--actor", required=True)
419
+ delivered_parser.add_argument("--dry-run", action="store_true")
420
+ delivered_parser.add_argument("--plan-id")
421
+ archive_parser = commands.add_parser(
422
+ "archive-retained", parents=[common],
423
+ help="preserve an unfinished clean retained checkout on its branch before release",
424
+ )
425
+ archive_parser.add_argument("--path", type=Path, required=True)
426
+ archive_parser.add_argument("--expected-record-version", type=int, required=True)
427
+ archive_parser.add_argument("--owner-release", required=True)
428
+ archive_parser.add_argument("--actor", required=True)
429
+ archive_parser.add_argument("--dry-run", action="store_true")
430
+ archive_parser.add_argument("--plan-id")
431
+ preserved_parser = commands.add_parser("close-preserved", parents=[common],
432
+ help="close exactly preserved source work without declaring business completion")
433
+ preserved_parser.add_argument("--path", type=Path, required=True)
434
+ preserved_parser.add_argument("--expected-record-version", type=int, required=True)
435
+ preserved_parser.add_argument("--owner", required=True)
436
+ abandon_parser = commands.add_parser(
437
+ "integration-abandon",
438
+ parents=[common],
439
+ help="terminate an integration intent whose preparation is already gone",
440
+ )
441
+ abandon_parser.add_argument("--intent-id", required=True)
442
+ abandon_parser.add_argument("--owner", required=True)
443
+ abandon_parser.add_argument("--reason", required=True)
444
+ abort_parser = commands.add_parser(
445
+ "integration-abort",
446
+ parents=[common],
447
+ help="terminate a failed or owner-aborted integration preparation",
448
+ )
449
+ abort_parser.add_argument("--path", type=Path, required=True)
450
+ abort_parser.add_argument("--expected-record-version", type=int, required=True)
451
+ abort_parser.add_argument("--owner", required=True)
452
+ abort_parser.add_argument("--reason", required=True)
453
+ retry_parser = commands.add_parser(
454
+ "integration-retry", parents=[common],
455
+ help="retry an aborted released candidate under its original frozen intent",
456
+ )
457
+ retry_parser.add_argument("--intent-id", required=True)
458
+ retry_parser.add_argument("--owner", required=True)
459
+ retry_parser.add_argument("--reason", required=True)
460
+ retry_parser.add_argument("--prerequisite-intent")
461
+ retry_parser.add_argument("--dry-run", action="store_true")
462
+ retry_parser.add_argument("--expected-plan-id")
463
+ session_parser = commands.add_parser(
464
+ "reconcile-session",
465
+ parents=[common],
466
+ help="terminal reconciliation when a task session stops",
467
+ )
468
+ session_parser.add_argument("--path", type=Path, required=True)
469
+ session_parser.add_argument("--expected-record-version", type=int, required=True)
470
+ session_parser.add_argument("--owner", required=True)
471
+ session_parser.add_argument(
472
+ "--trigger", required=True, choices=["task-terminal", "session-archive", "manual"]
473
+ )
474
+ lease_acquire = commands.add_parser(
475
+ "writer-lease-acquire",
476
+ parents=[common],
477
+ help="bind write entitlement to a verifiable live process",
478
+ )
479
+ lease_acquire.add_argument("--path", type=Path, required=True)
480
+ lease_acquire.add_argument("--owner")
481
+ lease_acquire.add_argument("--client-id", required=True)
482
+ lease_acquire.add_argument("--holder-pid", type=int, required=True)
483
+ lease_acquire.add_argument("--session-id")
484
+ lease_acquire.add_argument("--run-id")
485
+ lease_acquire.add_argument("--ttl-seconds", type=int)
486
+ lease_acquire.add_argument("--expected-record-version", type=int)
487
+ lease_release = commands.add_parser(
488
+ "writer-lease-release",
489
+ parents=[common],
490
+ help="give up write entitlement without asserting the task finished",
491
+ )
492
+ lease_release.add_argument("--path", type=Path, required=True)
493
+ lease_release.add_argument("--holder-pid", type=int)
494
+ lease_release.add_argument("--holder-boot-id")
495
+ lease_release.add_argument("--holder-start-ticks", type=int)
496
+ lease_release.add_argument("--client-id", default="manual")
497
+ lease_release.add_argument("--session-id")
498
+ lease_release.add_argument("--run-id")
499
+ lease_release.add_argument("--lease-id")
500
+ commands.add_parser("writer-session", help="strict executor admission and post-exit cleanup")
501
+ reconcile_writers_parser = commands.add_parser(
502
+ "reconcile-writers",
503
+ parents=[common],
504
+ help="report, and optionally converge, provably inactive writer entitlement",
505
+ )
506
+ reconcile_writers_parser.add_argument("--actor", default="lifecycle-reconciler")
507
+ reconcile_writers_parser.add_argument(
508
+ "--trigger", choices=("manual", "admission", "background"), default="manual"
509
+ )
510
+ reconcile_writers_parser.add_argument(
511
+ "--apply",
512
+ action="store_true",
513
+ help="converge instead of reporting; without it the command is read-only",
514
+ )
515
+ reconcile_writers_parser.add_argument(
516
+ "--only",
517
+ action="append",
518
+ default=[],
519
+ metavar="TASK_KEY",
520
+ help="converge only these allocations; everything else is reported but untouched",
521
+ )
522
+ reconcile_writers_parser.add_argument(
523
+ "--adopt-legacy",
524
+ action="store_true",
525
+ help="one-time classification of allocations created before writer leases",
526
+ )
527
+ park_parser = commands.add_parser(
528
+ "park", parents=[common], help="retain an unfinished checkout and free its writer slot"
529
+ )
530
+ park_parser.add_argument("--path", type=Path, required=True)
531
+ park_parser.add_argument("--expected-record-version", type=int, required=True)
532
+ park_parser.add_argument("--owner", required=True)
533
+ park_parser.add_argument("--checkpoint-id", help="content-addressed checkpoint retained by the existing Task")
534
+ park_parser.add_argument("--checkpoint-head", help="exact HEAD saved in that checkpoint")
535
+ park_parser.add_argument("--waiting-on", help="external dependency kept by the Task, not a reason to retain a directory")
536
+ resume_parser = commands.add_parser(
537
+ "resume", parents=[common], help="return a retained checkout to writer status"
538
+ )
539
+ resume_parser.add_argument("--path", type=Path, required=True)
540
+ resume_parser.add_argument("--expected-record-version", type=int, required=True)
541
+ resume_parser.add_argument("--owner", required=True)
542
+ adopt_parser = commands.add_parser(
543
+ "adopt-delivered",
544
+ parents=[common],
545
+ help="adopt an allocation whose deliverable is already in the publication branch",
546
+ )
547
+ adopt_parser.add_argument("--path", type=Path, required=True)
548
+ adopt_parser.add_argument("--expected-record-version", type=int, required=True)
549
+ adopt_parser.add_argument("--new-owner", required=True)
550
+ adopt_parser.add_argument("--actor", required=True)
551
+ review_parser = commands.add_parser(
552
+ "review", parents=[common], help="record owner review or explicit owner transfer"
553
+ )
554
+ review_parser.add_argument("--path", type=Path, required=True)
555
+ review_parser.add_argument("--expected-record-version", type=int, required=True)
556
+ review_parser.add_argument("--owner", required=True)
557
+ review_parser.add_argument("--new-owner")
558
+ review_parser.add_argument("--add-scope", action="append", default=[])
559
+ review_parser.add_argument("--expected-head")
560
+ review_parser.add_argument("--scope-reason")
561
+
562
+ handoff_parser = commands.add_parser(
563
+ "handoff", parents=[common], help="record an integration-ready or preserved handoff"
564
+ )
565
+ handoff_parser.add_argument("--path", type=Path, required=True)
566
+ handoff_parser.add_argument("--expected-record-version", type=int, required=True)
567
+ handoff_parser.add_argument("--kind", required=True, choices=["integration-ready", "preserved"])
568
+ handoff_parser.add_argument("--commit", required=True)
569
+ handoff_parser.add_argument("--finalizer-record-id", required=True)
570
+ handoff_parser.add_argument("--finalizer-commit")
571
+ handoff_parser.add_argument("--scope", action="append", default=[])
572
+ handoff_parser.add_argument(
573
+ "--scope-changed", action="store_true",
574
+ help="record the exact paths the handoff commit changed as the task scope",
575
+ )
576
+ handoff_parser.add_argument("--mainline-commit")
577
+
578
+ quarantine_parser = commands.add_parser(
579
+ "quarantine", parents=[common], help="isolate a drifted integration candidate"
580
+ )
581
+ quarantine_parser.add_argument("--path", type=Path, required=True)
582
+ quarantine_parser.add_argument("--expected-record-version", type=int, required=True)
583
+ quarantine_parser.add_argument("--owner", required=True)
584
+ quarantine_parser.add_argument("--reason", required=True)
585
+ quarantine_parser.add_argument("--expected-commit")
586
+
587
+ recovery_adopt_parser = commands.add_parser(
588
+ "recovery-adopt",
589
+ parents=[common],
590
+ help="move an existing checkout under governance recovery capacity",
591
+ )
592
+ recovery_adopt_parser.add_argument("--source-path", type=Path, required=True)
593
+ recovery_adopt_parser.add_argument("--recovery-path", type=Path, required=True)
594
+ recovery_adopt_parser.add_argument("--expected-source-record-version", type=int, required=True)
595
+ recovery_adopt_parser.add_argument(
596
+ "--expected-recovery-record-version", type=int, required=True
597
+ )
598
+ recovery_adopt_parser.add_argument("--source-owner", required=True)
599
+ recovery_adopt_parser.add_argument("--recovery-owner", required=True)
600
+ recovery_adopt_parser.add_argument("--scope", action="append", default=[])
601
+
602
+ externalize_parser = commands.add_parser(
603
+ "externalize", parents=[common], help="bind quarantined lineage to a recovery allocation"
604
+ )
605
+ externalize_parser.add_argument("--source-path", type=Path, required=True)
606
+ externalize_parser.add_argument("--recovery-path", type=Path, required=True)
607
+ externalize_parser.add_argument("--expected-source-record-version", type=int, required=True)
608
+ externalize_parser.add_argument("--expected-recovery-record-version", type=int, required=True)
609
+ externalize_parser.add_argument("--owner", required=True)
610
+
611
+ resource_register_parser = commands.add_parser(
612
+ "resource-register", parents=[common], help="register a rebuildable task-local resource"
613
+ )
614
+ resource_register_parser.add_argument("--path", type=Path, required=True)
615
+ resource_register_parser.add_argument("--expected-record-version", type=int, required=True)
616
+ resource_register_parser.add_argument("--owner", required=True)
617
+ resource_register_parser.add_argument("--resource-path", required=True)
618
+ resource_register_parser.add_argument("--kind", required=True, choices=["python-venv"])
619
+ resource_register_parser.add_argument(
620
+ "--reproducible", action=argparse.BooleanOptionalAction, required=True
621
+ )
622
+ resource_register_parser.add_argument("--creator", required=True)
623
+ resource_register_parser.add_argument(
624
+ "--teardown-policy", required=True, choices=["manual", "on-release"]
625
+ )
626
+
627
+ resource_clean_parser = commands.add_parser(
628
+ "resource-clean", parents=[common], help="remove a registered rebuildable resource"
629
+ )
630
+ resource_clean_parser.add_argument("--path", type=Path, required=True)
631
+ resource_clean_parser.add_argument("--expected-record-version", type=int, required=True)
632
+ resource_clean_parser.add_argument("--owner", required=True)
633
+ resource_mode = resource_clean_parser.add_mutually_exclusive_group(required=True)
634
+ resource_mode.add_argument("--resource-path")
635
+ resource_mode.add_argument(
636
+ "--typed", action="store_true", help="use the narrow typed cleanup contract"
637
+ )
638
+ resource_clean_parser.add_argument("--dry-run", action="store_true")
639
+ resource_clean_parser.add_argument("--expected-plan-id")
640
+ resource_clean_parser.add_argument("--preservation-receipt", type=Path)
641
+ resource_clean_parser.add_argument("--preservation-receipt-sha256")
642
+ resource_clean_parser.add_argument(
643
+ "--additional-preservation-receipt", type=Path, action="append", default=[]
644
+ )
645
+ resource_clean_parser.add_argument(
646
+ "--additional-preservation-receipt-sha256", action="append", default=[]
647
+ )
648
+
649
+ recovery_parser = commands.add_parser(
650
+ "resource-recover",
651
+ parents=[common],
652
+ help="append a re-preserved cleanup baseline or resume its remaining actions",
653
+ )
654
+ recovery_parser.add_argument(
655
+ "--stage",
656
+ required=True,
657
+ choices=["prepare", "prepare-multiple", "resume", "reconcile-attempt", "reconcile-typed", "rollout-check"],
658
+ )
659
+ recovery_parser.add_argument("--path", type=Path)
660
+ recovery_parser.add_argument("--owner")
661
+ recovery_parser.add_argument("--expected-record-version", type=int)
662
+ recovery_parser.add_argument("--transaction-id")
663
+ recovery_parser.add_argument("--transaction-sha256")
664
+ recovery_parser.add_argument("--cleanup-receipt-sha256")
665
+ recovery_parser.add_argument("--preservation-receipt", type=Path)
666
+ recovery_parser.add_argument("--preservation-receipt-sha256")
667
+ recovery_parser.add_argument("--expected-plan-id")
668
+ recovery_parser.add_argument("--recovery-receipt", type=Path)
669
+ recovery_parser.add_argument("--recovery-receipt-sha256")
670
+ recovery_parser.add_argument("--dry-run", action="store_true")
671
+
672
+ lease_acquire_parser = commands.add_parser(
673
+ "lease-acquire", parents=[common], help="acquire serialized main publication authority"
674
+ )
675
+ lease_acquire_parser.add_argument("--path", type=Path, required=True)
676
+ lease_acquire_parser.add_argument("--expected-record-version", type=int, required=True)
677
+ lease_acquire_parser.add_argument("--owner", required=True)
678
+ lease_acquire_parser.add_argument("--run-id")
679
+ lease_acquire_parser.add_argument("--ttl-seconds", type=int, default=900)
680
+
681
+ lease_complete_parser = commands.add_parser(
682
+ "lease-complete", parents=[common], help="verify publication and release its lease"
683
+ )
684
+ lease_complete_parser.add_argument("--path", type=Path, required=True)
685
+ lease_complete_parser.add_argument("--expected-record-version", type=int, required=True)
686
+ lease_complete_parser.add_argument("--lease-id", required=True)
687
+ lease_complete_parser.add_argument("--published-commit", required=True)
688
+ lease_complete_parser.add_argument("--finalizer-record-id", required=True)
689
+ lease_complete_parser.add_argument("--run-id", required=True)
690
+ lease_complete_parser.add_argument(
691
+ "--auto-release",
692
+ action=argparse.BooleanOptionalAction,
693
+ default=True,
694
+ help="guarded release after remote verification (default: enabled)",
695
+ )
696
+
697
+ publication_session_parser = commands.add_parser(
698
+ "publication-execution-session", parents=[common],
699
+ help="fence a consumer's exact publication through the public stdio protocol",
700
+ )
701
+ publication_session_parser.add_argument("--candidate-oid", required=True)
702
+ publication_session_parser.add_argument("--lease-id", required=True)
703
+ publication_session_parser.add_argument("--run-id", required=True)
704
+
705
+ lease_abort_parser = commands.add_parser(
706
+ "lease-abort", parents=[common], help="release unchanged publication authority"
707
+ )
708
+ lease_abort_parser.add_argument("--path", type=Path, required=True)
709
+ lease_abort_parser.add_argument("--expected-record-version", type=int, required=True)
710
+ lease_abort_parser.add_argument("--lease-id")
711
+ lease_abort_parser.add_argument("--owner", required=True)
712
+ lease_abort_parser.add_argument("--run-id", required=True)
713
+
714
+ release_parser = commands.add_parser(
715
+ "release", parents=[common], help="remove an evidence-cleared linked checkout"
716
+ )
717
+ release_parser.add_argument("--path", type=Path, required=True)
718
+ release_parser.add_argument("--expected-record-version", type=int, required=True)
719
+ release_parser.add_argument("--owner-release")
720
+ release_parser.add_argument("--intent-id")
721
+ release_parser.add_argument("--publication-receipt-id")
722
+ release_parser.add_argument("--teardown-reproducible-venv", action="store_true")
723
+ release_parser.add_argument("--dry-run", action="store_true")
724
+
725
+ gc_plan_parser = commands.add_parser(
726
+ "gc-plan", parents=[common], help="classify worktrees and preflight a selected release"
727
+ )
728
+ gc_plan_parser.add_argument("--path", type=Path)
729
+ gc_plan_parser.add_argument("--owner-release")
730
+ gc_plan_parser.add_argument("--intent-id")
731
+ gc_plan_parser.add_argument("--namespace-pid", type=int,
732
+ help="compare mounts with an existing writer process")
733
+
734
+ gc_apply_parser = commands.add_parser(
735
+ "gc-apply", parents=[common], help="apply a reviewed plan through guarded release"
736
+ )
737
+ gc_apply_parser.add_argument("--path", type=Path, required=True)
738
+ gc_apply_parser.add_argument("--plan-id", required=True)
739
+ gc_apply_parser.add_argument("--owner-release")
740
+ gc_apply_parser.add_argument("--intent-id")
741
+
742
+ release_intent_parser = commands.add_parser(
743
+ "release-intent", parents=[common], help="record a short-lived feature release intent"
744
+ )
745
+ release_intent_parser.add_argument("--path", type=Path, required=True)
746
+ release_intent_parser.add_argument("--expected-record-version", type=int, required=True)
747
+ release_intent_parser.add_argument("--owner", required=True)
748
+ release_intent_parser.add_argument(
749
+ "--trigger",
750
+ required=True,
751
+ choices=[
752
+ "integration-remote-verified",
753
+ "task-terminal",
754
+ "session-archive",
755
+ "manual",
756
+ "capacity-pressure",
757
+ "binding-cleared",
758
+ ],
759
+ )
760
+ release_intent_parser.add_argument("--integration-intent-id")
761
+
762
+ retirement_plan_parser = commands.add_parser(
763
+ "branch-retirement-plan",
764
+ parents=[common],
765
+ help="classify a released branch and persist an OID-bound retirement plan",
766
+ )
767
+ retirement_target = retirement_plan_parser.add_mutually_exclusive_group(required=True)
768
+ retirement_target.add_argument("--allocation-id")
769
+ retirement_target.add_argument("--branch")
770
+ retirement_plan_parser.add_argument("--remote", required=True)
771
+ retirement_plan_parser.add_argument("--integrated-into", required=True)
772
+ retirement_plan_parser.add_argument("--dry-run", action="store_true")
773
+
774
+ retirement_status_parser = commands.add_parser(
775
+ "branch-retirement-status",
776
+ parents=[common],
777
+ help="revalidate a retirement plan and show independent local/remote progress",
778
+ )
779
+ retirement_status_parser.add_argument("--plan-id", required=True)
780
+
781
+ retirement_verify_parser = commands.add_parser(
782
+ "branch-retirement-verify",
783
+ parents=[common],
784
+ help="verify one retirement operation immediately before mutation and return the CAS fields",
785
+ )
786
+ retirement_verify_parser.add_argument("--plan-id", required=True)
787
+ retirement_verify_parser.add_argument("--operation", choices=("local", "remote"), required=True)
788
+
789
+ retirement_authorize_parser = commands.add_parser(
790
+ "branch-retirement-authorize",
791
+ parents=[common],
792
+ help="grant one single-use execution authorization for one retirement operation",
793
+ )
794
+ retirement_authorize_parser.add_argument("--plan-id", required=True)
795
+ retirement_authorize_parser.add_argument("--operation", choices=("local", "remote"), required=True)
796
+ retirement_authorize_parser.add_argument("--request-id", required=True)
797
+ retirement_authorize_parser.add_argument(
798
+ "--holder",
799
+ default="self",
800
+ help="self, or ancestor:<program> of the process performing the mutation",
801
+ )
802
+ retirement_authorize_parser.add_argument("--client-id", default="retirement-executor")
803
+ retirement_authorize_parser.add_argument("--session-id")
804
+ retirement_authorize_parser.add_argument("--run-id")
805
+ retirement_authorize_parser.add_argument(
806
+ "--kind", choices=("cas-delete",), default="cas-delete", help="the only grantable kind"
807
+ )
808
+ retirement_authorize_parser.add_argument("--ttl-seconds", type=int, default=900)
809
+
810
+ retirement_complete_parser = commands.add_parser(
811
+ "branch-retirement-complete",
812
+ parents=[common],
813
+ help="close an authorized retirement using the Controller's own ref observation",
814
+ )
815
+ retirement_complete_parser.add_argument("--authorization-id", required=True)
816
+ retirement_complete_parser.add_argument("--token", required=True)
817
+ retirement_complete_parser.add_argument("--result-file", type=Path, required=True)
818
+
819
+ retirement_executions_parser = commands.add_parser(
820
+ "branch-retirement-executions",
821
+ parents=[common],
822
+ help="list, inspect or abandon outstanding retirement execution authorizations",
823
+ )
824
+ retirement_executions_parser.add_argument(
825
+ "--action", choices=("list", "inspect", "abandon"), required=True
826
+ )
827
+ retirement_executions_parser.add_argument("--authorization-id")
828
+ retirement_executions_parser.add_argument("--token")
829
+ retirement_executions_parser.add_argument("--reason")
830
+
831
+ retirement_record_parser = commands.add_parser(
832
+ "branch-retirement-record",
833
+ parents=[common],
834
+ help="record one consumer-reported retirement execution result",
835
+ )
836
+ retirement_record_parser.add_argument("--plan-id", required=True)
837
+ retirement_record_parser.add_argument("--operation", choices=("local", "remote"), required=True)
838
+ summary_target = retirement_record_parser.add_mutually_exclusive_group(required=True)
839
+ summary_target.add_argument(
840
+ "--summary-file",
841
+ type=Path,
842
+ help="legacy consumer summary; translated by the compatibility adapter",
843
+ )
844
+ summary_target.add_argument(
845
+ "--result-file", type=Path, help="provider-neutral retirement-execution-result/v1 document"
846
+ )
847
+
848
+ namespace_plan_parser = commands.add_parser(
849
+ "namespace-migration-plan",
850
+ parents=[common],
851
+ help="report the legacy namespace move for one repository without writing",
852
+ )
853
+ namespace_plan_parser.add_argument("--legacy-namespace", default=LEGACY_ROOT_NAME)
854
+ namespace_plan_parser.add_argument("--policy-from", type=Path)
855
+ namespace_migrate_parser = commands.add_parser(
856
+ "namespace-migrate",
857
+ parents=[common],
858
+ help="idempotently move the legacy metadata namespace under the repository lock",
859
+ )
860
+ namespace_migrate_parser.add_argument("--legacy-namespace", default=LEGACY_ROOT_NAME)
861
+ namespace_migrate_parser.add_argument("--policy-from", type=Path)
862
+ namespace_migrate_parser.add_argument("--actor", required=True)
863
+ namespace_migrate_parser.add_argument("--expected-plan-id", required=True)
864
+ namespace_migrate_parser.add_argument("--dry-run", action="store_true")
865
+ commands.add_parser(
866
+ "namespace-migration-receipts",
867
+ parents=[common],
868
+ help="list the immutable namespace migration receipts of one repository",
869
+ )
870
+ commands.add_parser(
871
+ "namespace-migration-verify",
872
+ parents=[common],
873
+ help="re-derive a completed namespace migration from immutable evidence",
874
+ )
875
+
876
+ reopen_parser = commands.add_parser(
877
+ "reopen", parents=[common], help="invalidate a frozen handoff before continuing work"
878
+ )
879
+ reopen_parser.add_argument("--path", type=Path, required=True)
880
+ reopen_parser.add_argument("--expected-record-version", type=int, required=True)
881
+ reopen_parser.add_argument("--owner", required=True)
882
+
883
+ for recovery_command in ("recover-handoff", "recover-published"):
884
+ recovery_parser = commands.add_parser(
885
+ recovery_command, parents=[common], help="recover a published frozen checkout with immutable evidence"
886
+ )
887
+ recovery_parser.add_argument("--path", type=Path, required=True)
888
+ recovery_parser.add_argument("--expected-record-version", type=int, required=True)
889
+ recovery_parser.add_argument("--expected-head", required=True)
890
+ recovery_parser.add_argument("--owner", required=True)
891
+ if recovery_command == "recover-published":
892
+ recovery_parser.add_argument("--branch", required=True)
893
+
894
+ for binding_action in ("add", "remove", "transfer"):
895
+ binding_parser = commands.add_parser(
896
+ f"binding-{binding_action}",
897
+ parents=[common],
898
+ help=f"{binding_action} an explicit runtime or workspace binding",
899
+ )
900
+ binding_parser.add_argument("--path", type=Path, required=True)
901
+ binding_parser.add_argument("--expected-record-version", type=int, required=True)
902
+ binding_parser.add_argument("--owner", required=True)
903
+ binding_parser.add_argument("--binding", required=True)
904
+ if binding_action == "transfer":
905
+ binding_parser.add_argument("--holder-pid", type=int, required=True)
906
+ if binding_action == "remove":
907
+ binding_parser.add_argument("--recover-archived-codex-session", action="store_true")
908
+ binding_parser.add_argument("--dry-run", action="store_true")
909
+
910
+ reconcile_parser = commands.add_parser(
911
+ "reconcile", help="reconcile immutable receipts or existing release intents"
912
+ )
913
+ reconcile_commands = reconcile_parser.add_subparsers(dest="reconcile_command", required=True)
914
+ receipts_parser = reconcile_commands.add_parser("receipts", parents=[common])
915
+ receipts_parser.add_argument("--actor", required=True)
916
+ receipts_parser.add_argument("--receipt-id", action="append", default=[])
917
+ receipts_parser.add_argument("--dry-run", action="store_true")
918
+ no_change_parser = reconcile_commands.add_parser(
919
+ "no-change-completion", parents=[common],
920
+ help="bind exact legacy no-change completion to its successful cleanup evidence",
921
+ )
922
+ no_change_parser.add_argument("--path", type=Path, required=True)
923
+ no_change_parser.add_argument("--owner", required=True)
924
+ no_change_parser.add_argument("--expected-record-version", type=int, required=True)
925
+ no_change_parser.add_argument("--completion-receipt-id", required=True)
926
+ no_change_parser.add_argument("--completion-receipt-sha256", required=True)
927
+ no_change_parser.add_argument("--cleanup-receipt-id", action="append", required=True)
928
+ no_change_parser.add_argument("--cleanup-receipt-sha256", action="append", required=True)
929
+ no_change_parser.add_argument("--dry-run", action="store_true")
930
+ no_change_parser.add_argument("--expected-plan-id")
931
+ intents_parser = reconcile_commands.add_parser("release-intents", parents=[common])
932
+ intents_parser.add_argument("--actor", required=True)
933
+ intents_parser.add_argument(
934
+ "--trigger",
935
+ required=True,
936
+ choices=[
937
+ "integration-remote-verified",
938
+ "task-terminal",
939
+ "session-archive",
940
+ "binding-cleared",
941
+ "manual",
942
+ "capacity-pressure",
943
+ ],
944
+ )
945
+ intents_parser.add_argument("--intent-id", action="append", default=[])
946
+ intents_parser.add_argument("--dry-run", action="store_true")
947
+ release_plans_parser = reconcile_commands.add_parser("release-plans", parents=[common])
948
+ release_plans_parser.add_argument("--actor", required=True)
949
+ release_plans_parser.add_argument("--dry-run", action="store_true")
950
+ retention_parser = reconcile_commands.add_parser(
951
+ "retention",
952
+ parents=[common],
953
+ help="close idle clean checkouts through guarded release (dry-run unless --apply)",
954
+ )
955
+ retention_parser.add_argument("--actor", required=True)
956
+ retention_parser.add_argument(
957
+ "--trigger", choices=("manual", "admission", "background"), default="manual"
958
+ )
959
+ retention_parser.add_argument("--apply", action="store_true")
960
+ registrations_parser = reconcile_commands.add_parser(
961
+ "binding-registrations", parents=[common],
962
+ help="tie legacy linked bindings to their current Git registration (dry-run first)",
963
+ )
964
+ registrations_parser.add_argument("--dry-run", action="store_true")
965
+ registrations_parser.add_argument("--expected-plan-id")
966
+ integration_intents_parser = reconcile_commands.add_parser(
967
+ "integration-intents", parents=[common]
968
+ )
969
+ integration_intents_parser.add_argument("--actor", required=True)
970
+ integration_intents_parser.add_argument("--intent-id", action="append", default=[])
971
+ integration_intents_parser.add_argument("--dry-run", action="store_true")
972
+ integration_intents_parser.add_argument("--dispatch", action="store_true")
973
+
974
+ doctor_parser = commands.add_parser("doctor", parents=[common], help="diagnose controller drift")
975
+ doctor_parser.add_argument("--namespace-pid", type=int,
976
+ help="compare mounts with an existing writer process")
977
+ return parser
978
+
979
+
980
+ def _print(payload: dict[str, Any], as_json: bool) -> None:
981
+ if as_json:
982
+ print(json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True))
983
+ else:
984
+ print(render(payload))
985
+
986
+
987
+ def main(argv: list[str] | None = None) -> int:
988
+ from .store import decode_cache
989
+
990
+ arguments = list(sys.argv[1:] if argv is None else argv)
991
+ # Queries and execution's first attempt need one snapshot. Recovery opts into reuse below.
992
+ if arguments and arguments[0] in {
993
+ "status", "doctor", "lifecycle-history", "release-recovery-inspect", "recovery-guide",
994
+ "execution-admit", "execution-cancel"
995
+ }:
996
+ return _main(arguments)
997
+ # Writer checks load each repository once; retain the CLI reader without its caches.
998
+ reuse = arguments[:2] not in (
999
+ ["writer-session", "check"], ["writer-session", "host-check"]
1000
+ )
1001
+ with decode_cache(reuse=reuse):
1002
+ return _main(arguments)
1003
+
1004
+
1005
+ def _main(argv: list[str] | None) -> int:
1006
+ arguments = list(sys.argv[1:] if argv is None else argv)
1007
+ if arguments and arguments[0] == "writer-session":
1008
+ from .writer_session import main as writer_session_main
1009
+
1010
+ return writer_session_main(arguments[1:])
1011
+ parser = build_parser()
1012
+ parsed = parser.parse_args(arguments)
1013
+ try:
1014
+ if parsed.command == "capabilities":
1015
+ from .writer_session import (
1016
+ HOST_HANDOFF_CONTINUATION_VERSION,
1017
+ HOST_MANAGED_WRITER_SESSION_VERSION,
1018
+ )
1019
+
1020
+ payload = envelope("capabilities")
1021
+ payload["decision"] = {
1022
+ "code": "CAPABILITIES",
1023
+ "contract_version": 60,
1024
+ "exited_holder_release_version": 1,
1025
+ "canonical_refresh_version": 1,
1026
+ "host_handoff_continuation_version": HOST_HANDOFF_CONTINUATION_VERSION,
1027
+ "release_recovery_inspection_version": 1,
1028
+ "rollout_transaction_diagnostics_version": 1,
1029
+ "no_change_cleanup_closeout_version": 1,
1030
+ "canonical_transition_version": 1,
1031
+ "canonical_main_refresh_version": 1,
1032
+ "integration_lineage_admission_version": 1,
1033
+ "integration_keyed_document_scope_version": 1,
1034
+ "canonical_transition_retirement_version": 1,
1035
+ "closed_lifecycle_query_version": 1,
1036
+ "source_release_summary_version": 1,
1037
+ "tracked_source_progress_recovery_version": 1,
1038
+ "published_checkout_recovery_version": 1,
1039
+ "published_handoff_resource_registration_version": 1,
1040
+ "multiple_preservation_cleanup_version": 1,
1041
+ "multiple_preservation_recovery_version": 1,
1042
+ "preservation_supersession_version": 1,
1043
+ "retained_typed_cleanup_version": 1,
1044
+ "aborted_integration_preserved_cleanup_version": 1,
1045
+ "preservation_leaf_version": 1,
1046
+ "integration_publication_execution_version": 1,
1047
+ "integration_retry_version": 1,
1048
+ "integration_conflict_resolution_version": 2,
1049
+ "session_admission_version": 1,
1050
+ "capabilities_version": 1,
1051
+ "storage_layout_version": 5,
1052
+ "writer_session_version": 2,
1053
+ "holder_resolver_version": 1,
1054
+ "host_managed_writer_session_version": HOST_MANAGED_WRITER_SESSION_VERSION,
1055
+ "resource_admission_version": 1,
1056
+ "acquire_no_effect_recovery_version": 1,
1057
+ "execution_admission_version": 1,
1058
+ "host_execution_admission_version": 2,
1059
+ "runtime_binding_version": 1,
1060
+ "archived_codex_binding_recovery_version": 1,
1061
+ "branch_retirement_version": 1,
1062
+ "branch_retirement_verify_version": 1,
1063
+ "branch_retirement_execution_version": 1,
1064
+ "namespace_migration_version": 1,
1065
+ "governance_version": 2,
1066
+ "delivered_closeout_version": 1,
1067
+ "retained_archive_version": 1,
1068
+ "retention_convergence_version": 2,
1069
+ "session_end_selector_version": 1,
1070
+ "session_closeout_report_version": 1,
1071
+ "session_closeout_expectations_version": 1,
1072
+ "recovery_guidance_version": 1,
1073
+ "hosted_session_closeout_check_version": 1,
1074
+ "checkout_owner_action_version": 1,
1075
+ "task_checkpoint_park_version": 1,
1076
+ "binding_registration_backfill_version": 1,
1077
+ "gc_apply_replay_version": 1,
1078
+ "doctor_mount_version": 2,
1079
+ "cleanup_hardlink_topology_version": 2,
1080
+ "typed_cleanup_no_action_version": 1,
1081
+ "typed_cleanup_no_action_receipt_version": 1,
1082
+ "typed_cleanup_verify_boundary_version": 1,
1083
+ "supported_writer_session_versions": [1, 2],
1084
+ "writer_lease_schema_version": 2,
1085
+ }
1086
+ elif parsed.command == "publication-execution-session":
1087
+ from .publication_execution import run_session
1088
+
1089
+ return run_session(
1090
+ parsed.repo, candidate_oid=parsed.candidate_oid,
1091
+ lease_id=parsed.lease_id, run_id=parsed.run_id,
1092
+ )
1093
+ elif parsed.command == "status":
1094
+ payload = status(parsed.repo, namespace_pid=parsed.namespace_pid)
1095
+ elif parsed.command == "canonical-refresh":
1096
+ from .canonical_refresh import refresh_canonical
1097
+
1098
+ payload = refresh_canonical(parsed.repo, dry_run=parsed.dry_run)
1099
+ elif parsed.command == "canonical-transition":
1100
+ from .canonical_transition import transition_canonical
1101
+
1102
+ payload = transition_canonical(
1103
+ parsed.repo, target=parsed.path,
1104
+ expected_record_version=parsed.expected_record_version,
1105
+ owner=parsed.owner, expected_branch=parsed.expected_branch,
1106
+ expected_head=parsed.expected_head,
1107
+ expected_main_head=parsed.expected_main_head,
1108
+ expected_remote_head=parsed.expected_remote_head,
1109
+ new_owner=parsed.new_owner, new_task_key=parsed.new_task_key,
1110
+ new_authority_key=parsed.new_authority_key,
1111
+ )
1112
+ elif parsed.command == "lifecycle-history":
1113
+ from .lifecycle_history import lifecycle_history
1114
+
1115
+ payload = lifecycle_history(
1116
+ parsed.repo, allocation_id=parsed.allocation_id,
1117
+ authority_key=parsed.authority_key, task_key=parsed.task_key,
1118
+ integration_intent_id=parsed.integration_intent_id,
1119
+ release_receipt_id=parsed.release_receipt_id,
1120
+ limit=parsed.limit, after=parsed.after,
1121
+ )
1122
+ elif parsed.command == "recovery-guide":
1123
+ from .recovery_guidance import recovery_guide
1124
+
1125
+ payload = recovery_guide(parsed.repo, target=parsed.path, intent_id=parsed.intent_id)
1126
+ elif parsed.command == "release-recovery-inspect":
1127
+ from . import cleanup_recovery
1128
+
1129
+ payload = cleanup_recovery.inspect_release(parsed.repo, transaction_id=parsed.transaction_id)
1130
+ elif parsed.command == "admission-queue":
1131
+ from .resource_admission import reconcile as reconcile_admission
1132
+
1133
+ payload = reconcile_admission(repository_path=None if parsed.all_repositories else parsed.repo,
1134
+ apply=parsed.apply)
1135
+ elif parsed.command == "host-execution":
1136
+ from .resource_admission import host_execution
1137
+
1138
+ evidence = None
1139
+ if parsed.terminal_evidence is not None:
1140
+ try:
1141
+ evidence = json.loads(parsed.terminal_evidence)
1142
+ except ValueError:
1143
+ raise ControllerError("INVALID_REQUEST", "terminal evidence must be canonical JSON") from None
1144
+ payload = host_execution(parsed.action, target=parsed.path, request_id=parsed.request_id,
1145
+ client_id=parsed.client_id, session_id=parsed.session_id, run_id=parsed.run_id,
1146
+ holder_pid=parsed.holder_pid, memory_bytes=parsed.memory_bytes,
1147
+ recover_dead_holder=parsed.recover_dead_holder,
1148
+ expected_holder_sha256=parsed.expected_holder_sha256,
1149
+ terminal_evidence=evidence)
1150
+ elif parsed.command == "session-admit":
1151
+ from .convergence import admission_convergence
1152
+ from .session_admission import admit_session
1153
+
1154
+ payload = admit_session(
1155
+ parsed.repo, task_key=parsed.task_key, authority_key=parsed.authority_key,
1156
+ module=parsed.module, owner=parsed.owner, branch=parsed.branch,
1157
+ base_ref=parsed.base_ref, client_id=parsed.client_id,
1158
+ session_id=parsed.session_id, run_id=parsed.run_id,
1159
+ )
1160
+ if payload["decision"].get("code") in {"SESSION_ADMITTED", "RESOURCE_WAIT"}:
1161
+ # Started after admission, so the pass never holds the lock this
1162
+ # admission needs; it still runs on every admission that changed state.
1163
+ convergence = admission_convergence(parsed.repo)
1164
+ if convergence is not None:
1165
+ payload["retention_convergence"] = convergence
1166
+ elif parsed.command == "session-bind":
1167
+ from .session_admission import bind_session
1168
+
1169
+ payload = bind_session(
1170
+ parsed.repo, target=parsed.path, task_key=parsed.task_key,
1171
+ authority_key=parsed.authority_key, owner=parsed.owner,
1172
+ client_id=parsed.client_id, holder_pid=parsed.holder_pid,
1173
+ session_id=parsed.session_id, run_id=parsed.run_id, kind=parsed.kind,
1174
+ )
1175
+ elif parsed.command in {"execution-admit", "execution-cancel"}:
1176
+ from .resource_admission import admit_execution
1177
+
1178
+ payload = admit_execution(parsed.repo, target=parsed.path, owner=parsed.owner,
1179
+ client_id=parsed.client_id, session_id=parsed.session_id,
1180
+ run_id=parsed.run_id, memory_bytes=parsed.memory_bytes,
1181
+ cancel=parsed.command == "execution-cancel", _cache_recovery=True)
1182
+ elif parsed.command == "plan":
1183
+ payload = plan(
1184
+ parsed.repo,
1185
+ task_key=parsed.task_key,
1186
+ authority_key=parsed.authority_key,
1187
+ module=parsed.module,
1188
+ role=parsed.role,
1189
+ target=parsed.target,
1190
+ )
1191
+ elif parsed.command == "write-guard":
1192
+ payload = guard_write(
1193
+ parsed.repo,
1194
+ target=parsed.path,
1195
+ task_key=parsed.task_key,
1196
+ authority_key=parsed.authority_key,
1197
+ owner=parsed.owner,
1198
+ operation=parsed.operation,
1199
+ holder=(
1200
+ resolve_holder(
1201
+ parsed.holder,
1202
+ client_id=parsed.client_id,
1203
+ session_id=parsed.session_id,
1204
+ run_id=parsed.run_id,
1205
+ )
1206
+ if parsed.holder
1207
+ else None
1208
+ ),
1209
+ )
1210
+ elif parsed.command == "adopt":
1211
+ payload = adopt(
1212
+ parsed.repo,
1213
+ target=parsed.path,
1214
+ task_key=parsed.task_key,
1215
+ authority_key=parsed.authority_key,
1216
+ module=parsed.module,
1217
+ role=parsed.role,
1218
+ owner=parsed.owner,
1219
+ lifecycle=parsed.lifecycle,
1220
+ base_commit=parsed.base_commit,
1221
+ task_commit=parsed.task_commit,
1222
+ scope_paths=tuple(parsed.scope),
1223
+ source_allocations=tuple(parsed.source_allocation),
1224
+ external_bindings=tuple(parsed.external_binding),
1225
+ )
1226
+ elif parsed.command == "acquire":
1227
+ from .convergence import admission_convergence
1228
+
1229
+ try:
1230
+ payload = acquire(
1231
+ parsed.repo,
1232
+ task_key=parsed.task_key,
1233
+ authority_key=parsed.authority_key,
1234
+ module=parsed.module,
1235
+ role=parsed.role,
1236
+ owner=parsed.owner,
1237
+ branch=parsed.branch,
1238
+ base_ref=parsed.base_ref,
1239
+ target=parsed.path,
1240
+ scope_paths=tuple(parsed.scope),
1241
+ source_allocations=tuple(parsed.source_allocation),
1242
+ source_handoffs=tuple(parsed.source_handoff),
1243
+ external_bindings=tuple(parsed.external_binding),
1244
+ )
1245
+ finally:
1246
+ # After the acquisition, including a refused one, so the detached
1247
+ # pass never queues this admission behind its repository lock.
1248
+ convergence = admission_convergence(parsed.repo)
1249
+ if convergence is not None:
1250
+ payload["retention_convergence"] = convergence
1251
+ elif parsed.command == "integration-queue":
1252
+ payload = integration_queue(parsed.repo)
1253
+ elif parsed.command == "integration-enqueue":
1254
+ payload = enqueue_integration(
1255
+ parsed.repo,
1256
+ task_key=parsed.task_key,
1257
+ authority_key=parsed.authority_key,
1258
+ module=parsed.module,
1259
+ owner=parsed.owner,
1260
+ source_allocations=tuple(parsed.source_allocation),
1261
+ source_handoffs=tuple(parsed.source_handoff),
1262
+ source_snapshot_ids=tuple(parsed.source_snapshot),
1263
+ )
1264
+ elif parsed.command == "integration-claim":
1265
+ payload = claim_integration(
1266
+ parsed.repo,
1267
+ owner=parsed.owner,
1268
+ intent_id=parsed.intent_id,
1269
+ target=parsed.path,
1270
+ branch=parsed.branch,
1271
+ resolve_conflicts=parsed.resolve_conflicts,
1272
+ )
1273
+ elif parsed.command == "integration-resolve":
1274
+ payload = resolve_integration(
1275
+ parsed.repo, target=parsed.path,
1276
+ expected_record_version=parsed.expected_record_version, owner=parsed.owner,
1277
+ expected_head=parsed.expected_head, finalizer_record_id=parsed.finalizer_record_id,
1278
+ )
1279
+ elif parsed.command == "integration-refresh":
1280
+ payload = refresh_integration(
1281
+ parsed.repo,
1282
+ target=parsed.path,
1283
+ expected_record_version=parsed.expected_record_version,
1284
+ owner=parsed.owner,
1285
+ )
1286
+ elif parsed.command == "integration-validate":
1287
+ payload = validate_integration(
1288
+ parsed.repo,
1289
+ target=parsed.path,
1290
+ expected_record_version=parsed.expected_record_version,
1291
+ owner=parsed.owner,
1292
+ validation_evidence_id=parsed.validation_evidence_id,
1293
+ tests_receipt_id=parsed.tests_receipt_id,
1294
+ snapshot_receipt_id=parsed.snapshot_receipt_id,
1295
+ )
1296
+ elif parsed.command == "integration-reopen":
1297
+ payload = reopen_integration(
1298
+ parsed.repo,
1299
+ target=parsed.path,
1300
+ expected_record_version=parsed.expected_record_version,
1301
+ owner=parsed.owner,
1302
+ )
1303
+ elif parsed.command == "lifecycle-events":
1304
+ payload = lifecycle_events(parsed.repo, consumer=parsed.consumer)
1305
+ elif parsed.command == "lifecycle-event-ack":
1306
+ payload = acknowledge_lifecycle_event(
1307
+ parsed.repo,
1308
+ consumer=parsed.consumer,
1309
+ event_id=parsed.event_id,
1310
+ )
1311
+ elif parsed.command == "integration-recovery-matrix":
1312
+ payload = integration_recovery_matrix(parsed.repo)
1313
+ elif parsed.command == "integration-equivalent":
1314
+ payload = record_equivalent_replay(
1315
+ parsed.repo,
1316
+ target=parsed.path,
1317
+ expected_record_version=parsed.expected_record_version,
1318
+ owner=parsed.owner,
1319
+ source_allocation_id=parsed.source_allocation,
1320
+ source_handoff=parsed.source_handoff,
1321
+ replay_commit=parsed.replay_commit,
1322
+ evidence_id=parsed.evidence_id,
1323
+ )
1324
+ elif parsed.command == "close-delivered":
1325
+ payload = close_delivered(
1326
+ parsed.repo, target=parsed.path,
1327
+ expected_record_version=parsed.expected_record_version,
1328
+ owner_release=parsed.owner_release, actor=parsed.actor,
1329
+ dry_run=parsed.dry_run, plan_id=parsed.plan_id,
1330
+ )
1331
+ elif parsed.command == "archive-retained":
1332
+ payload = archive_retained(
1333
+ parsed.repo, target=parsed.path,
1334
+ expected_record_version=parsed.expected_record_version,
1335
+ owner_release=parsed.owner_release, actor=parsed.actor,
1336
+ dry_run=parsed.dry_run, plan_id=parsed.plan_id,
1337
+ )
1338
+ elif parsed.command in {"complete-no-change", "close-preserved"}:
1339
+ close = close_preserved if parsed.command == "close-preserved" else complete_no_change
1340
+ payload = close(
1341
+ parsed.repo,
1342
+ target=parsed.path,
1343
+ expected_record_version=parsed.expected_record_version,
1344
+ owner=parsed.owner,
1345
+ )
1346
+ elif parsed.command == "integration-abandon":
1347
+ payload = abandon_integration_intent(
1348
+ parsed.repo,
1349
+ intent_id=parsed.intent_id,
1350
+ owner=parsed.owner,
1351
+ reason=parsed.reason,
1352
+ )
1353
+ elif parsed.command == "integration-abort":
1354
+ payload = abort_integration(
1355
+ parsed.repo,
1356
+ target=parsed.path,
1357
+ expected_record_version=parsed.expected_record_version,
1358
+ owner=parsed.owner,
1359
+ reason=parsed.reason,
1360
+ )
1361
+ elif parsed.command == "integration-retry":
1362
+ payload = retry_integration(
1363
+ parsed.repo, intent_id=parsed.intent_id, owner=parsed.owner,
1364
+ reason=parsed.reason, prerequisite_intent_id=parsed.prerequisite_intent,
1365
+ dry_run=parsed.dry_run, expected_plan_id=parsed.expected_plan_id,
1366
+ )
1367
+ elif parsed.command == "reconcile-session":
1368
+ payload = reconcile_session(
1369
+ parsed.repo,
1370
+ target=parsed.path,
1371
+ expected_record_version=parsed.expected_record_version,
1372
+ owner=parsed.owner,
1373
+ trigger=parsed.trigger,
1374
+ )
1375
+ elif parsed.command == "writer-lease-acquire":
1376
+ payload = acquire_writer_lease(
1377
+ parsed.repo,
1378
+ target=parsed.path,
1379
+ owner=parsed.owner,
1380
+ client_id=parsed.client_id,
1381
+ holder_pid=parsed.holder_pid,
1382
+ session_id=parsed.session_id,
1383
+ run_id=parsed.run_id,
1384
+ ttl_seconds=parsed.ttl_seconds,
1385
+ expected_record_version=parsed.expected_record_version,
1386
+ )
1387
+ elif parsed.command == "writer-lease-release":
1388
+ payload = release_writer_lease(
1389
+ parsed.repo,
1390
+ target=parsed.path,
1391
+ holder_pid=parsed.holder_pid,
1392
+ lease_id=parsed.lease_id,
1393
+ holder=(
1394
+ WriterHolder(
1395
+ client_id=parsed.client_id,
1396
+ pid=parsed.holder_pid,
1397
+ boot_id=parsed.holder_boot_id,
1398
+ start_ticks=parsed.holder_start_ticks,
1399
+ session_id=parsed.session_id,
1400
+ run_id=parsed.run_id,
1401
+ )
1402
+ if all(
1403
+ value is not None
1404
+ for value in (
1405
+ parsed.holder_pid,
1406
+ parsed.holder_boot_id,
1407
+ parsed.holder_start_ticks,
1408
+ )
1409
+ )
1410
+ else None
1411
+ ),
1412
+ )
1413
+ elif parsed.command == "reconcile-writers":
1414
+ payload = reconcile_writers(
1415
+ parsed.repo,
1416
+ actor=parsed.actor,
1417
+ trigger=parsed.trigger,
1418
+ dry_run=not parsed.apply,
1419
+ adopt_legacy=parsed.adopt_legacy,
1420
+ only=tuple(parsed.only),
1421
+ )
1422
+ elif parsed.command == "acquire-when-free":
1423
+ payload = acquire_when_free(
1424
+ parsed.repo,
1425
+ deadline_seconds=parsed.deadline_seconds,
1426
+ poll_seconds=parsed.poll_seconds,
1427
+ task_key=parsed.task_key,
1428
+ authority_key=parsed.authority_key,
1429
+ module=parsed.module,
1430
+ role=parsed.role,
1431
+ owner=parsed.owner,
1432
+ branch=parsed.branch,
1433
+ base_ref=parsed.base_ref,
1434
+ target=parsed.path,
1435
+ scope_paths=tuple(parsed.scope),
1436
+ )
1437
+ elif parsed.command == "acquire-across":
1438
+ payload = acquire_across([json.loads(item) for item in parsed.request])
1439
+ elif parsed.command == "park":
1440
+ payload = park(
1441
+ parsed.repo,
1442
+ target=parsed.path,
1443
+ expected_record_version=parsed.expected_record_version,
1444
+ owner=parsed.owner,
1445
+ checkpoint_id=parsed.checkpoint_id,
1446
+ checkpoint_head=parsed.checkpoint_head,
1447
+ waiting_on=parsed.waiting_on,
1448
+ )
1449
+ elif parsed.command == "resume":
1450
+ payload = resume(
1451
+ parsed.repo,
1452
+ target=parsed.path,
1453
+ expected_record_version=parsed.expected_record_version,
1454
+ owner=parsed.owner,
1455
+ )
1456
+ elif parsed.command == "adopt-delivered":
1457
+ payload = adopt_delivered(
1458
+ parsed.repo,
1459
+ target=parsed.path,
1460
+ expected_record_version=parsed.expected_record_version,
1461
+ new_owner=parsed.new_owner,
1462
+ actor=parsed.actor,
1463
+ )
1464
+ elif parsed.command == "review":
1465
+ payload = review(
1466
+ parsed.repo,
1467
+ target=parsed.path,
1468
+ expected_record_version=parsed.expected_record_version,
1469
+ owner=parsed.owner,
1470
+ new_owner=parsed.new_owner,
1471
+ add_scope_paths=tuple(parsed.add_scope),
1472
+ expected_head=parsed.expected_head,
1473
+ scope_reason=parsed.scope_reason,
1474
+ )
1475
+ elif parsed.command == "handoff":
1476
+ payload = handoff(
1477
+ parsed.repo,
1478
+ target=parsed.path,
1479
+ expected_record_version=parsed.expected_record_version,
1480
+ kind=parsed.kind,
1481
+ commit=parsed.commit,
1482
+ finalizer_record_id=parsed.finalizer_record_id,
1483
+ finalizer_commit=parsed.finalizer_commit,
1484
+ scope_paths=tuple(parsed.scope),
1485
+ mainline_commit=parsed.mainline_commit,
1486
+ scope_changed=parsed.scope_changed,
1487
+ )
1488
+ elif parsed.command == "quarantine":
1489
+ payload = quarantine(
1490
+ parsed.repo,
1491
+ target=parsed.path,
1492
+ expected_record_version=parsed.expected_record_version,
1493
+ owner=parsed.owner,
1494
+ reason=parsed.reason,
1495
+ expected_commit=parsed.expected_commit,
1496
+ )
1497
+ elif parsed.command == "externalize":
1498
+ payload = externalize(
1499
+ parsed.repo,
1500
+ source_target=parsed.source_path,
1501
+ recovery_target=parsed.recovery_path,
1502
+ expected_source_record_version=parsed.expected_source_record_version,
1503
+ expected_recovery_record_version=parsed.expected_recovery_record_version,
1504
+ owner=parsed.owner,
1505
+ )
1506
+ elif parsed.command == "recovery-adopt":
1507
+ payload = adopt_recovery(
1508
+ parsed.repo,
1509
+ source_target=parsed.source_path,
1510
+ recovery_target=parsed.recovery_path,
1511
+ expected_source_record_version=parsed.expected_source_record_version,
1512
+ expected_recovery_record_version=parsed.expected_recovery_record_version,
1513
+ source_owner=parsed.source_owner,
1514
+ recovery_owner=parsed.recovery_owner,
1515
+ scope_paths=tuple(parsed.scope),
1516
+ )
1517
+ elif parsed.command == "resource-register":
1518
+ payload = register_disposable(
1519
+ parsed.repo,
1520
+ target=parsed.path,
1521
+ expected_record_version=parsed.expected_record_version,
1522
+ owner=parsed.owner,
1523
+ resource_path=parsed.resource_path,
1524
+ kind=parsed.kind,
1525
+ reproducible=parsed.reproducible,
1526
+ creator=parsed.creator,
1527
+ teardown_policy=parsed.teardown_policy,
1528
+ )
1529
+ elif parsed.command == "resource-clean":
1530
+ if parsed.typed:
1531
+ payload = cleanup_typed_disposable(
1532
+ parsed.repo,
1533
+ target=parsed.path,
1534
+ expected_record_version=parsed.expected_record_version,
1535
+ owner=parsed.owner,
1536
+ dry_run=parsed.dry_run,
1537
+ expected_plan_id=parsed.expected_plan_id,
1538
+ preservation_receipt=parsed.preservation_receipt,
1539
+ preservation_receipt_sha256=parsed.preservation_receipt_sha256,
1540
+ additional_preservation_receipts=tuple(parsed.additional_preservation_receipt),
1541
+ additional_preservation_receipt_sha256=tuple(
1542
+ parsed.additional_preservation_receipt_sha256
1543
+ ),
1544
+ )
1545
+ else:
1546
+ if (
1547
+ parsed.dry_run
1548
+ or parsed.expected_plan_id
1549
+ or parsed.preservation_receipt
1550
+ or parsed.preservation_receipt_sha256
1551
+ or parsed.additional_preservation_receipt
1552
+ or parsed.additional_preservation_receipt_sha256
1553
+ ):
1554
+ raise ControllerError("INVALID_RESOURCE", "dry-run and plan id require --typed")
1555
+ payload = cleanup_disposable(
1556
+ parsed.repo,
1557
+ target=parsed.path,
1558
+ expected_record_version=parsed.expected_record_version,
1559
+ owner=parsed.owner,
1560
+ resource_path=parsed.resource_path,
1561
+ )
1562
+ elif parsed.command == "resource-recover":
1563
+ from . import cleanup_recovery
1564
+
1565
+ if parsed.stage == "rollout-check":
1566
+ if not parsed.dry_run:
1567
+ raise ControllerError("INVALID_RECOVERY", "rollout check requires --dry-run")
1568
+ payload = cleanup_recovery.rollout_check(
1569
+ parsed.repo,
1570
+ receipt_path=parsed.preservation_receipt,
1571
+ receipt_sha=parsed.preservation_receipt_sha256,
1572
+ )
1573
+ elif parsed.stage == "reconcile-typed":
1574
+ if not parsed.transaction_id or not parsed.transaction_sha256:
1575
+ raise ControllerError("INVALID_RECOVERY", "exact typed transaction and digest required")
1576
+ from .cleanup_attempts import reconcile_typed
1577
+
1578
+ payload = reconcile_typed(parsed.repo, parsed.transaction_id, parsed.transaction_sha256,
1579
+ dry_run=parsed.dry_run, expected_plan_id=parsed.expected_plan_id)
1580
+ elif parsed.stage == "prepare-multiple":
1581
+ if not all([
1582
+ parsed.path, parsed.owner, parsed.expected_record_version,
1583
+ parsed.transaction_id, parsed.transaction_sha256,
1584
+ parsed.cleanup_receipt_sha256,
1585
+ ]):
1586
+ raise ControllerError(
1587
+ "INVALID_RECOVERY", "exact original multiple cleanup bindings required"
1588
+ )
1589
+ from . import multiple_cleanup_recovery
1590
+
1591
+ payload = multiple_cleanup_recovery.prepare(
1592
+ parsed.repo, target=parsed.path, owner=parsed.owner,
1593
+ version=parsed.expected_record_version,
1594
+ recovery=multiple_cleanup_recovery.context(
1595
+ parsed.transaction_id, parsed.transaction_sha256,
1596
+ parsed.cleanup_receipt_sha256,
1597
+ ),
1598
+ dry_run=parsed.dry_run, expected_plan_id=parsed.expected_plan_id,
1599
+ )
1600
+ elif parsed.stage == "prepare":
1601
+ if not all(
1602
+ [
1603
+ parsed.path,
1604
+ parsed.owner,
1605
+ parsed.expected_record_version,
1606
+ parsed.transaction_id,
1607
+ parsed.transaction_sha256,
1608
+ parsed.cleanup_receipt_sha256,
1609
+ parsed.preservation_receipt,
1610
+ parsed.preservation_receipt_sha256,
1611
+ ]
1612
+ ):
1613
+ raise ControllerError(
1614
+ "INVALID_RECOVERY", "exact original and new preservation bindings required"
1615
+ )
1616
+ payload = cleanup_recovery.prepare(
1617
+ parsed.repo,
1618
+ target=parsed.path,
1619
+ owner=parsed.owner,
1620
+ version=parsed.expected_record_version,
1621
+ recovery=cleanup_recovery.context(
1622
+ parsed.transaction_id,
1623
+ parsed.transaction_sha256,
1624
+ parsed.cleanup_receipt_sha256,
1625
+ ),
1626
+ receipt_path=parsed.preservation_receipt,
1627
+ receipt_sha=parsed.preservation_receipt_sha256,
1628
+ dry_run=parsed.dry_run,
1629
+ expected_plan_id=parsed.expected_plan_id,
1630
+ )
1631
+ else:
1632
+ if not parsed.recovery_receipt or not parsed.recovery_receipt_sha256:
1633
+ raise ControllerError(
1634
+ "INVALID_RECOVERY", "immutable recovery record and trusted digest required"
1635
+ )
1636
+ recovery_options = dict(
1637
+ recovery_path=parsed.recovery_receipt,
1638
+ recovery_sha=parsed.recovery_receipt_sha256,
1639
+ dry_run=parsed.dry_run,
1640
+ )
1641
+ if parsed.stage == "reconcile-attempt":
1642
+ payload = cleanup_recovery.reconcile_attempt(
1643
+ parsed.repo, **recovery_options, expected_plan_id=parsed.expected_plan_id
1644
+ )
1645
+ else:
1646
+ payload = cleanup_recovery.resume(parsed.repo, **recovery_options)
1647
+ elif parsed.command == "lease-acquire":
1648
+ payload = acquire_publication_lease(
1649
+ parsed.repo,
1650
+ target=parsed.path,
1651
+ expected_record_version=parsed.expected_record_version,
1652
+ owner=parsed.owner,
1653
+ run_id=parsed.run_id,
1654
+ ttl_seconds=parsed.ttl_seconds,
1655
+ )
1656
+ elif parsed.command == "lease-complete":
1657
+ payload = complete_publication_lease(
1658
+ parsed.repo,
1659
+ target=parsed.path,
1660
+ expected_record_version=parsed.expected_record_version,
1661
+ lease_id=parsed.lease_id,
1662
+ published_commit=parsed.published_commit,
1663
+ finalizer_record_id=parsed.finalizer_record_id,
1664
+ run_id=parsed.run_id,
1665
+ auto_release=parsed.auto_release,
1666
+ )
1667
+ elif parsed.command == "lease-abort":
1668
+ payload = abort_publication_lease(
1669
+ parsed.repo,
1670
+ target=parsed.path,
1671
+ expected_record_version=parsed.expected_record_version,
1672
+ lease_id=parsed.lease_id,
1673
+ owner=parsed.owner,
1674
+ run_id=parsed.run_id,
1675
+ )
1676
+ elif parsed.command == "release":
1677
+ payload = release(
1678
+ parsed.repo,
1679
+ target=parsed.path,
1680
+ expected_record_version=parsed.expected_record_version,
1681
+ owner_release=parsed.owner_release,
1682
+ teardown_reproducible_venv=parsed.teardown_reproducible_venv,
1683
+ dry_run=parsed.dry_run,
1684
+ release_intent_id=parsed.intent_id,
1685
+ publication_receipt_id=parsed.publication_receipt_id,
1686
+ )
1687
+ elif parsed.command == "gc-plan":
1688
+ payload = gc_plan(
1689
+ parsed.repo,
1690
+ target=parsed.path,
1691
+ owner_release=parsed.owner_release,
1692
+ release_intent_id=parsed.intent_id,
1693
+ namespace_pid=parsed.namespace_pid,
1694
+ )
1695
+ elif parsed.command == "gc-apply":
1696
+ payload = gc_apply(
1697
+ parsed.repo,
1698
+ target=parsed.path,
1699
+ expected_gc_plan_id=parsed.plan_id,
1700
+ owner_release=parsed.owner_release,
1701
+ release_intent_id=parsed.intent_id,
1702
+ )
1703
+ elif parsed.command == "release-intent":
1704
+ payload = create_release_intent(
1705
+ parsed.repo,
1706
+ target=parsed.path,
1707
+ expected_record_version=parsed.expected_record_version,
1708
+ owner=parsed.owner,
1709
+ trigger=parsed.trigger,
1710
+ integration_intent_id=parsed.integration_intent_id,
1711
+ )
1712
+ elif parsed.command == "branch-retirement-plan":
1713
+ payload = plan_branch_retirement(
1714
+ parsed.repo,
1715
+ allocation_id=parsed.allocation_id,
1716
+ branch=parsed.branch,
1717
+ remote=parsed.remote,
1718
+ integrated_into=parsed.integrated_into,
1719
+ dry_run=parsed.dry_run,
1720
+ )
1721
+ elif parsed.command == "branch-retirement-status":
1722
+ payload = branch_retirement_status(parsed.repo, plan_id=parsed.plan_id)
1723
+ elif parsed.command == "branch-retirement-verify":
1724
+ payload = verify_branch_retirement(
1725
+ parsed.repo,
1726
+ plan_id=parsed.plan_id,
1727
+ operation=parsed.operation,
1728
+ )
1729
+ elif parsed.command == "branch-retirement-authorize":
1730
+ payload = authorize_branch_retirement(
1731
+ parsed.repo,
1732
+ plan_id=parsed.plan_id,
1733
+ operation=parsed.operation,
1734
+ request_id=parsed.request_id,
1735
+ holder=parsed.holder,
1736
+ client_id=parsed.client_id,
1737
+ session_id=parsed.session_id,
1738
+ run_id=parsed.run_id,
1739
+ kind=KIND_CAS_DELETE,
1740
+ ttl_seconds=parsed.ttl_seconds,
1741
+ )
1742
+ elif parsed.command == "branch-retirement-complete":
1743
+ payload = complete_branch_retirement(
1744
+ parsed.repo,
1745
+ authorization_id=parsed.authorization_id,
1746
+ token=parsed.token,
1747
+ result_path=parsed.result_file,
1748
+ )
1749
+ elif parsed.command == "branch-retirement-executions":
1750
+ payload = branch_retirement_executions(
1751
+ parsed.repo,
1752
+ action=parsed.action,
1753
+ authorization_id=parsed.authorization_id,
1754
+ token=parsed.token,
1755
+ reason=parsed.reason,
1756
+ )
1757
+ elif parsed.command == "branch-retirement-record":
1758
+ payload = record_branch_retirement(
1759
+ parsed.repo,
1760
+ plan_id=parsed.plan_id,
1761
+ operation=parsed.operation,
1762
+ summary_path=parsed.summary_file,
1763
+ result_path=parsed.result_file,
1764
+ )
1765
+ elif parsed.command == "namespace-migration-plan":
1766
+ payload = plan_namespace_migration(
1767
+ parsed.repo,
1768
+ legacy_namespace=parsed.legacy_namespace,
1769
+ policy_from=parsed.policy_from,
1770
+ )
1771
+ elif parsed.command == "namespace-migrate":
1772
+ payload = migrate_namespace(
1773
+ parsed.repo,
1774
+ legacy_namespace=parsed.legacy_namespace,
1775
+ policy_from=parsed.policy_from,
1776
+ actor=parsed.actor,
1777
+ expected_plan_id=parsed.expected_plan_id,
1778
+ dry_run=parsed.dry_run,
1779
+ )
1780
+ elif parsed.command == "namespace-migration-receipts":
1781
+ payload = namespace_migration_receipts(parsed.repo)
1782
+ elif parsed.command == "namespace-migration-verify":
1783
+ payload = verify_namespace(parsed.repo)
1784
+ elif parsed.command in {"recover-handoff", "recover-published"}:
1785
+ payload = recover_frozen_checkout(
1786
+ parsed.repo, target=parsed.path,
1787
+ expected_record_version=parsed.expected_record_version,
1788
+ expected_head=parsed.expected_head, owner=parsed.owner,
1789
+ branch=getattr(parsed, "branch", None),
1790
+ )
1791
+ elif parsed.command == "reopen":
1792
+ payload = reopen(
1793
+ parsed.repo,
1794
+ target=parsed.path,
1795
+ expected_record_version=parsed.expected_record_version,
1796
+ owner=parsed.owner,
1797
+ )
1798
+ elif parsed.command in {"binding-add", "binding-remove", "binding-transfer"}:
1799
+ payload = update_external_binding(
1800
+ parsed.repo,
1801
+ target=parsed.path,
1802
+ expected_record_version=parsed.expected_record_version,
1803
+ owner=parsed.owner,
1804
+ binding=parsed.binding,
1805
+ holder_pid=getattr(parsed, "holder_pid", None),
1806
+ action=parsed.command.removeprefix("binding-"),
1807
+ recover_archived_codex_session=getattr(
1808
+ parsed, "recover_archived_codex_session", False),
1809
+ dry_run=getattr(parsed, "dry_run", False),
1810
+ )
1811
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "no-change-completion":
1812
+ from .delivered_closeout import reconcile_no_change_completion
1813
+
1814
+ payload = reconcile_no_change_completion(
1815
+ parsed.repo, target=parsed.path, owner=parsed.owner,
1816
+ expected_record_version=parsed.expected_record_version,
1817
+ completion_id=parsed.completion_receipt_id,
1818
+ completion_sha256=parsed.completion_receipt_sha256,
1819
+ cleanup_ids=tuple(parsed.cleanup_receipt_id),
1820
+ cleanup_sha256=tuple(parsed.cleanup_receipt_sha256),
1821
+ dry_run=parsed.dry_run, expected_plan_id=parsed.expected_plan_id,
1822
+ )
1823
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "receipts":
1824
+ payload = reconcile_publication_receipts(
1825
+ parsed.repo,
1826
+ actor=parsed.actor,
1827
+ receipt_ids=tuple(parsed.receipt_id),
1828
+ dry_run=parsed.dry_run,
1829
+ )
1830
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "release-intents":
1831
+ payload = reconcile_release_intents(
1832
+ parsed.repo,
1833
+ actor=parsed.actor,
1834
+ trigger=parsed.trigger,
1835
+ intent_ids=tuple(parsed.intent_id),
1836
+ dry_run=parsed.dry_run,
1837
+ )
1838
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "release-plans":
1839
+ payload = reconcile_release_plans(
1840
+ parsed.repo, actor=parsed.actor, dry_run=parsed.dry_run
1841
+ )
1842
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "retention":
1843
+ from .convergence import converge_retention
1844
+
1845
+ payload = converge_retention(
1846
+ parsed.repo,
1847
+ actor=parsed.actor,
1848
+ trigger=parsed.trigger,
1849
+ dry_run=not parsed.apply,
1850
+ )
1851
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "binding-registrations":
1852
+ from .registration_backfill import reconcile_binding_registrations
1853
+
1854
+ payload = reconcile_binding_registrations(
1855
+ parsed.repo, dry_run=parsed.dry_run, expected_plan_id=parsed.expected_plan_id
1856
+ )
1857
+ elif parsed.command == "reconcile" and parsed.reconcile_command == "integration-intents":
1858
+ payload = reconcile_integration_intents(
1859
+ parsed.repo,
1860
+ actor=parsed.actor,
1861
+ intent_ids=tuple(parsed.intent_id),
1862
+ dry_run=parsed.dry_run,
1863
+ dispatch=parsed.dispatch,
1864
+ )
1865
+ elif parsed.command == "doctor":
1866
+ payload = doctor(parsed.repo, namespace_pid=parsed.namespace_pid)
1867
+ else:
1868
+ parser.error("unknown command")
1869
+ except (ControllerError, OSError) as exc:
1870
+ payload = envelope(parsed.command)
1871
+ code = exc.code if isinstance(exc, ControllerError) else "FILESYSTEM_ERROR"
1872
+ payload["errors"] = [{"code": code, "message": str(exc)}]
1873
+ payload["decision"] = {"code": "ERROR", "reason": str(exc)}
1874
+ if parsed.command == "canonical-refresh":
1875
+ from .canonical_refresh import CanonicalProcessOccupancyError
1876
+
1877
+ if isinstance(exc, CanonicalProcessOccupancyError):
1878
+ payload["decision"]["process_diagnostics"] = exc.diagnostics
1879
+ if (
1880
+ parsed.command == "resource-recover" and parsed.stage == "rollout-check"
1881
+ and isinstance(exc, cleanup_recovery.RolloutTransactionError)
1882
+ ):
1883
+ payload["decision"]["transaction_diagnostics"] = [exc.transaction_diagnostic]
1884
+ if parsed.command == "writer-lease-acquire":
1885
+ payload["decision"]["admission"] = "REFUSED"
1886
+ if parsed.command == "session-bind" and isinstance(getattr(exc, "details", None), dict):
1887
+ payload["decision"]["lease_outcome"] = exc.details
1888
+ _print(payload, parsed.json)
1889
+ return 2
1890
+ _print(payload, parsed.json)
1891
+ if (payload.get("decision") or {}).get("code") in {
1892
+ "RESOURCE_CLEANUP_BLOCKED",
1893
+ "RECOVERY_ATTEMPT_INCOMPLETE",
1894
+ }:
1895
+ return 1
1896
+ if parsed.command == "doctor" and payload.get("decision", {}).get("code") == "FAIL":
1897
+ return 1
1898
+ if (
1899
+ parsed.command == "write-guard"
1900
+ and payload.get("decision", {}).get("code") != "WRITE_ALLOWED"
1901
+ ):
1902
+ return 1
1903
+ return 0
1904
+
1905
+
1906
+ if __name__ == "__main__":
1907
+ raise SystemExit(main())