worktree-controller 1.30.5__tar.gz → 1.30.6__tar.gz

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 (70) hide show
  1. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/PKG-INFO +14 -6
  2. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/README.md +13 -5
  3. worktree_controller-1.30.6/docs/first-change.md +140 -0
  4. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/docs/getting-started.md +4 -3
  5. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/docs/operations.md +8 -1
  6. worktree_controller-1.30.6/docs/publishing.md +104 -0
  7. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/docs/reference.md +5 -2
  8. worktree_controller-1.30.6/examples/change_task.py +242 -0
  9. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/pyproject.toml +1 -1
  10. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/pyproject.toml.orig +1 -1
  11. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/__init__.py +1 -1
  12. {worktree_controller-1.30.5 → worktree_controller-1.30.6/src/worktree_controller}/tool_cli_contract.json +1 -1
  13. {worktree_controller-1.30.5/src/worktree_controller → worktree_controller-1.30.6}/tool_cli_contract.json +1 -1
  14. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/LICENSE +0 -0
  15. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/docs/architecture.md +0 -0
  16. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/examples/quickstart.py +0 -0
  17. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/activation_layout.py +0 -0
  18. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/branch_retirement.py +0 -0
  19. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/canonical_refresh.py +0 -0
  20. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/canonical_transition.py +0 -0
  21. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/cleanup_attempts.py +0 -0
  22. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/cleanup_evidence.py +0 -0
  23. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/cleanup_recovery.py +0 -0
  24. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/cli.py +0 -0
  25. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/controller.py +0 -0
  26. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/convergence.py +0 -0
  27. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/delivered_closeout.py +0 -0
  28. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/disposable.py +0 -0
  29. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/environment.py +0 -0
  30. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/frozen_recovery.py +0 -0
  31. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/git_backend.py +0 -0
  32. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/governance.py +0 -0
  33. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/hardlinks.py +0 -0
  34. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/integration_cleanup.py +0 -0
  35. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/integration_conflict.py +0 -0
  36. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/integration_retry.py +0 -0
  37. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/keyed_document_scope.py +0 -0
  38. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/lifecycle.py +0 -0
  39. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/lifecycle_history.py +0 -0
  40. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/lifecycle_observation.py +0 -0
  41. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/metadata_codec.py +0 -0
  42. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/metadata_relocation.py +0 -0
  43. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/models.py +0 -0
  44. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/mount_diagnostics.py +0 -0
  45. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/multiple_cleanup_recovery.py +0 -0
  46. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/namespace_migration.py +0 -0
  47. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/native_session_recovery.py +0 -0
  48. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/owner_actions.py +0 -0
  49. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/policy.py +0 -0
  50. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/preservation.py +0 -0
  51. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/preserved_cleanup.py +0 -0
  52. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/publication_execution.py +0 -0
  53. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/reconciler.py +0 -0
  54. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/recovery_guidance.py +0 -0
  55. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/registration_backfill.py +0 -0
  56. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/registration_prune.py +0 -0
  57. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/rendering.py +0 -0
  58. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/resource_admission.py +0 -0
  59. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/retirement_adapters.py +0 -0
  60. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/retirement_execution.py +0 -0
  61. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/semantics.py +0 -0
  62. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/session.py +0 -0
  63. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/session_admission.py +0 -0
  64. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/source_cleanup.py +0 -0
  65. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/source_preservation.py +0 -0
  66. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/source_recovery.py +0 -0
  67. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/store.py +0 -0
  68. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/transaction_scope.py +0 -0
  69. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/writer_lease.py +0 -0
  70. {worktree_controller-1.30.5 → worktree_controller-1.30.6}/src/worktree_controller/writer_session.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: worktree-controller
3
- Version: 1.30.5
3
+ Version: 1.30.6
4
4
  Summary: Govern Git worktree budgets, authority, ownership, and lifecycle
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -23,7 +23,7 @@ It checks repository policy before allocating a worktree, grants writing authori
23
23
  process, and records handoff, integration, recovery and release evidence. Git remains the source
24
24
  of truth for files, branches, commits and registered worktrees.
25
25
 
26
- **Current release: 1.30.5 · Linux · Python 3.12 · Apache-2.0**
26
+ **Current release: 1.30.6 · Linux · Python 3.12 · Apache-2.0**
27
27
 
28
28
  ## Install
29
29
 
@@ -32,14 +32,16 @@ Use a virtual environment in a new directory:
32
32
  ```bash
33
33
  python3.12 -m venv .venv
34
34
  . .venv/bin/activate
35
- python -m pip install worktree-controller==1.30.5
35
+ python -m pip install worktree-controller==1.30.6
36
36
  worktree-controller --version
37
37
  worktree-controller capabilities --json
38
38
  ```
39
39
 
40
40
  WC requires Git and readable Linux `/proc`. Other operating systems and Python versions are not
41
41
  supported by this release. Its one runtime dependency is `websockets>=15,<16`.
42
- Software version 1.30.5 retains CLI contract **60** and storage layout **5**.
42
+ On Ubuntu, if creating the environment reports that `ensurepip` is unavailable, install the
43
+ distribution's `python3.12-venv` package first (for example, `sudo apt install python3.12-venv`).
44
+ Software version 1.30.6 retains CLI contract **60** and storage layout **5**.
43
45
 
44
46
  ## Try it in a disposable repository
45
47
 
@@ -48,7 +50,7 @@ With the environment above active:
48
50
  ```bash
49
51
  git clone https://github.com/xuanheng-tech/worktree-controller.git wc
50
52
  cd wc
51
- git checkout v1.30.5
53
+ git checkout v1.30.6
52
54
  python examples/quickstart.py --workspace "$HOME/wc-first-run"
53
55
  ```
54
56
 
@@ -63,6 +65,10 @@ The [getting started guide](https://github.com/xuanheng-tech/worktree-controller
63
65
  explains the output and how to proceed to tasks that change files. The example also ships in the
64
66
  source distribution; it requires only WC and Git, with no companion delivery tools.
65
67
 
68
+ To actually change a file, publish to a local bare remote, integrate and recover a paused task,
69
+ follow [your first change](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/first-change.md).
70
+ That complete example additionally uses public Git Finalizer and Snapshot Runner installations.
71
+
66
72
  ## Use WC in a real project
67
73
 
68
74
  A governed repository tracks `.agents/worktree-policy.toml` and registers its checkouts with WC.
@@ -74,7 +80,8 @@ for task admission, writer identity, handoff, integration and safe release.
74
80
  WC manages checkout authority and lifecycle. It does not implement your task, commit changes or
75
81
  publish a branch. Real handoff and integration flows use separate delivery evidence from
76
82
  [Git Finalizer](https://github.com/xuanheng-tech/git-finalizer).
77
- Context Loader and Snapshot Runner are optional companion tools; see
83
+ Context Loader and the maintenance checker are optional. The complete integration example uses
84
+ Snapshot Runner for the explicit review receipts required by its policy; see
78
85
  [optional integrations](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/reference.md#optional-integrations-and-dependencies).
79
86
 
80
87
  When blocked, run `worktree-controller recovery-guide --repo /path/to/repo --path /path/to/checkout --json`
@@ -90,6 +97,7 @@ a successful process exit alone does not prove that all worktrees are settled.
90
97
  - [Architecture](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/architecture.md)
91
98
  - [Exact CLI and capability contract](https://github.com/xuanheng-tech/worktree-controller/blob/main/tool_cli_contract.json)
92
99
  - [Changelog](https://github.com/xuanheng-tech/worktree-controller/blob/main/CHANGELOG.md)
100
+ - [Publishing and recovery](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/publishing.md)
93
101
  - [Releases](https://github.com/xuanheng-tech/worktree-controller/releases) · [PyPI](https://pypi.org/project/worktree-controller/)
94
102
  - [Report a bug](https://github.com/xuanheng-tech/worktree-controller/issues) · [Contribute](https://github.com/xuanheng-tech/worktree-controller/blob/main/CONTRIBUTING.md)
95
103
  - [Report a security issue](https://github.com/xuanheng-tech/worktree-controller/blob/main/SECURITY.md)
@@ -6,7 +6,7 @@ It checks repository policy before allocating a worktree, grants writing authori
6
6
  process, and records handoff, integration, recovery and release evidence. Git remains the source
7
7
  of truth for files, branches, commits and registered worktrees.
8
8
 
9
- **Current release: 1.30.5 · Linux · Python 3.12 · Apache-2.0**
9
+ **Current release: 1.30.6 · Linux · Python 3.12 · Apache-2.0**
10
10
 
11
11
  ## Install
12
12
 
@@ -15,14 +15,16 @@ Use a virtual environment in a new directory:
15
15
  ```bash
16
16
  python3.12 -m venv .venv
17
17
  . .venv/bin/activate
18
- python -m pip install worktree-controller==1.30.5
18
+ python -m pip install worktree-controller==1.30.6
19
19
  worktree-controller --version
20
20
  worktree-controller capabilities --json
21
21
  ```
22
22
 
23
23
  WC requires Git and readable Linux `/proc`. Other operating systems and Python versions are not
24
24
  supported by this release. Its one runtime dependency is `websockets>=15,<16`.
25
- Software version 1.30.5 retains CLI contract **60** and storage layout **5**.
25
+ On Ubuntu, if creating the environment reports that `ensurepip` is unavailable, install the
26
+ distribution's `python3.12-venv` package first (for example, `sudo apt install python3.12-venv`).
27
+ Software version 1.30.6 retains CLI contract **60** and storage layout **5**.
26
28
 
27
29
  ## Try it in a disposable repository
28
30
 
@@ -31,7 +33,7 @@ With the environment above active:
31
33
  ```bash
32
34
  git clone https://github.com/xuanheng-tech/worktree-controller.git wc
33
35
  cd wc
34
- git checkout v1.30.5
36
+ git checkout v1.30.6
35
37
  python examples/quickstart.py --workspace "$HOME/wc-first-run"
36
38
  ```
37
39
 
@@ -46,6 +48,10 @@ The [getting started guide](https://github.com/xuanheng-tech/worktree-controller
46
48
  explains the output and how to proceed to tasks that change files. The example also ships in the
47
49
  source distribution; it requires only WC and Git, with no companion delivery tools.
48
50
 
51
+ To actually change a file, publish to a local bare remote, integrate and recover a paused task,
52
+ follow [your first change](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/first-change.md).
53
+ That complete example additionally uses public Git Finalizer and Snapshot Runner installations.
54
+
49
55
  ## Use WC in a real project
50
56
 
51
57
  A governed repository tracks `.agents/worktree-policy.toml` and registers its checkouts with WC.
@@ -57,7 +63,8 @@ for task admission, writer identity, handoff, integration and safe release.
57
63
  WC manages checkout authority and lifecycle. It does not implement your task, commit changes or
58
64
  publish a branch. Real handoff and integration flows use separate delivery evidence from
59
65
  [Git Finalizer](https://github.com/xuanheng-tech/git-finalizer).
60
- Context Loader and Snapshot Runner are optional companion tools; see
66
+ Context Loader and the maintenance checker are optional. The complete integration example uses
67
+ Snapshot Runner for the explicit review receipts required by its policy; see
61
68
  [optional integrations](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/reference.md#optional-integrations-and-dependencies).
62
69
 
63
70
  When blocked, run `worktree-controller recovery-guide --repo /path/to/repo --path /path/to/checkout --json`
@@ -73,6 +80,7 @@ a successful process exit alone does not prove that all worktrees are settled.
73
80
  - [Architecture](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/architecture.md)
74
81
  - [Exact CLI and capability contract](https://github.com/xuanheng-tech/worktree-controller/blob/main/tool_cli_contract.json)
75
82
  - [Changelog](https://github.com/xuanheng-tech/worktree-controller/blob/main/CHANGELOG.md)
83
+ - [Publishing and recovery](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/publishing.md)
76
84
  - [Releases](https://github.com/xuanheng-tech/worktree-controller/releases) · [PyPI](https://pypi.org/project/worktree-controller/)
77
85
  - [Report a bug](https://github.com/xuanheng-tech/worktree-controller/issues) · [Contribute](https://github.com/xuanheng-tech/worktree-controller/blob/main/CONTRIBUTING.md)
78
86
  - [Report a security issue](https://github.com/xuanheng-tech/worktree-controller/blob/main/SECURITY.md)
@@ -0,0 +1,140 @@
1
+ # Deliver your first change
2
+
3
+ This example changes one README, commits and publishes its branch through Git Finalizer, hands
4
+ it off to WC, reviews and validates an integration candidate, and publishes local main. It can
5
+ pause after handoff and continue in a new process with the original task and integration request.
6
+ It creates a **local bare Git remote**, never contacts a hosting service, and needs no credentials.
7
+ Use a disposable workspace; do not point it at an existing project.
8
+
9
+ ## Install the example's tools
10
+
11
+ WC itself needs Linux, Python 3.12, Git and readable `/proc`. Install the released version using
12
+ the [README](../README.md#install). On Ubuntu, a Python installation without `ensurepip` also
13
+ needs its distribution's `python3.12-venv` package before `python3.12 -m venv` can work.
14
+
15
+ This complete delivery example also uses these public tools:
16
+
17
+ - [Git Finalizer 1.9.3](https://github.com/xuanheng-tech/git-finalizer#installation): download its
18
+ published release tarball and checksum file, verify them, and keep all `git-finalize*` companions
19
+ together as its installation instructions describe. It is not a PyPI package.
20
+ - [Snapshot Runner 2.5.11](https://github.com/xuanheng-tech/snapshot-runner#requirements-and-installation):
21
+ install its PyPI package in a separate virtual environment. Its own interpreter needs
22
+ Python 3.12.13 or a later supported version; using Python 3.12.13+ for both environments meets
23
+ WC's Python 3.12 requirement too. WC's core Python requirement is unchanged.
24
+
25
+ For example, with a suitable Python 3.12 already installed and a new `runner-venv` path:
26
+
27
+ ```bash
28
+ python3.12 --version
29
+ python3.12 -m venv /absolute/path/to/runner-venv
30
+ /absolute/path/to/runner-venv/bin/python -m pip install 'snapshot-runner==2.5.11'
31
+ ```
32
+
33
+ Use the WC environment's Python to run the example. Pass the two installed entrypoints
34
+ explicitly, or put their directories on PATH. `tool-skill-sync`, Context Loader, agent clients,
35
+ model APIs and personal Skills are not required by this example.
36
+
37
+ WC policy schema 4 and newer requires separate explicit **test and Snapshot receipt identities**
38
+ at `integration-validate`. The example creates real Snapshot Runner artifacts and reads their
39
+ README evidence; it checks the expected README content and clean candidate separately. A tool's
40
+ successful evidence collection alone is not a passed test or code review. No fake receipt, lowered
41
+ policy version or changed Controller state is used.
42
+
43
+ ## Run the whole task
44
+
45
+ From this release's source checkout, with the WC environment active:
46
+
47
+ ```bash
48
+ python examples/change_task.py --workspace "$HOME/wc-first-change" \
49
+ --finalizer /absolute/path/to/git-finalize \
50
+ --snapshot-runner /absolute/path/to/runner-venv/bin/snapshot-runner
51
+ ```
52
+
53
+ The workspace path must be new and its parent must exist. The script refuses an existing path
54
+ without changing it. The source archive includes both `change_task.py` and its neighboring
55
+ `quickstart.py`; keep them together. Missing tools are refused before the workspace is created.
56
+
57
+ `PASS` means all of the following were checked:
58
+
59
+ 1. The admitted feature's real writer passed the write guard before changing `README.md`.
60
+ 2. The exact file scope was reviewed, committed and published to the local remote with Finalizer.
61
+ 3. The writer ended and its exact commit was handed off, then queued and claimed through WC.
62
+ 4. Tests and Snapshot evidence were bound to the candidate before its main-publication lease.
63
+ 5. Finalizer and WC independently verified the published commit; WC released both linked checkouts.
64
+ 6. Canonical main was refreshed, the delivered README was checked, strict closeout proof covered
65
+ both known allocations, and `doctor` passed.
66
+
67
+ The example intentionally changes only a known text file, so its content review is narrow and
68
+ deterministic. Real application changes need their own tests and appropriate review.
69
+ This local demonstration does not authorize a hosted publication or production deployment.
70
+
71
+ ## Pause and resume the same task
72
+
73
+ Use a different new workspace for this exercise:
74
+
75
+ ```bash
76
+ python examples/change_task.py --workspace "$HOME/wc-change-resume" \
77
+ --finalizer /absolute/path/to/git-finalize \
78
+ --snapshot-runner /absolute/path/to/runner-venv/bin/snapshot-runner \
79
+ --pause-after-handoff
80
+ ```
81
+
82
+ The output is **`PAUSED`**, not `PASS`. The change and its branch have reached the local remote,
83
+ the feature writer is released, and `handoff.json` records the original task's source, repository
84
+ and integration identities. The integration is queued, with no candidate checkout yet.
85
+ The preserved source is still awaiting integration; this is not completed business work.
86
+
87
+ Run in a new process with the **same** workspace and installed tools:
88
+
89
+ ```bash
90
+ python examples/change_task.py --workspace "$HOME/wc-change-resume" \
91
+ --finalizer /absolute/path/to/git-finalize \
92
+ --snapshot-runner /absolute/path/to/runner-venv/bin/snapshot-runner --resume
93
+ ```
94
+
95
+ Resume checks the existing repository, local remote, source identity and uncommitted files. It
96
+ claims the original queued intent, or observes the same unchanged claimed candidate. It does not
97
+ create another task, replace source evidence, reset a branch or delete unknown files. Repeating
98
+ resume after verified completion only rechecks the delivered result and closeout evidence.
99
+
100
+ ## Inspect a failure and keep the evidence
101
+
102
+ Every command's actual exit code, stdout and stderr is saved in `commands.jsonl`. The workspace
103
+ also retains Finalizer receipts, Snapshot summaries and artifacts, content-check results, branches,
104
+ the local remote and final `closeout.json`. Linked checkouts are removed only through WC.
105
+
106
+ An unexpected refusal stops with nonzero exit status and preserves the current result. This
107
+ example's resume entry covers queued or unchanged claimed handoffs and verified completion;
108
+ it is not a general crash-recovery engine. For a different interruption point, unknown files,
109
+ identity drift or a publication in progress, inspect the saved command and the formal
110
+ [recovery guidance](operations.md#recovery-guidance-and-session-closeout). Do not rerun without
111
+ `--resume` into an old workspace or clear metadata to make the example continue.
112
+
113
+ The isolated state is under the workspace's `home/`. For inspection, activate the WC environment
114
+ and use the actual workspace path:
115
+
116
+ ```bash
117
+ workspace="$HOME/wc-change-resume"
118
+ HOME="$workspace/home" XDG_STATE_HOME="$workspace/home/.local/state" \
119
+ worktree-controller status --repo "$workspace/main" --json
120
+ ```
121
+
122
+ Use the original `intent_id` from `handoff.json` with `recovery-guide --intent-id` even after a
123
+ checkout is released. Follow each returned owner/CAS step and reobserve. `doctor PASS` and an
124
+ observation command's exit 0 do not prove recovery, integration or task completion.
125
+
126
+ ## Repeat the regression as a contributor
127
+
128
+ The default test suite checks refusal of existing workspaces and missing prerequisites. The real
129
+ cross-tool regression is explicit opt-in, runs entirely against temporary local repositories,
130
+ and covers unknown-file preservation, source drift, pause/resume and completed replay:
131
+
132
+ ```bash
133
+ WC_DEMO_FINALIZER=/absolute/path/to/git-finalize \
134
+ WC_DEMO_SNAPSHOT_RUNNER=/absolute/path/to/runner-venv/bin/snapshot-runner \
135
+ uv run --locked --python 3.12 --no-env-file python -m pytest -q tests/test_change_task.py \
136
+ -p no:cacheprovider --basetemp /absolute/path/to/a/new/demo-pytest
137
+ ```
138
+
139
+ Install the contributor environment as described in [CONTRIBUTING](../CONTRIBUTING.md). Without
140
+ both opt-in entrypoints, the cross-tool case is skipped; a skipped case is not acceptance evidence.
@@ -63,9 +63,10 @@ not establish completion.
63
63
 
64
64
  The example deliberately performs a genuine no-change inspection, so `complete-no-change` is
65
65
  appropriate. A task that edits files or creates commits must preserve or deliver that work;
66
- it cannot use this shortcut. See the [operations guide](operations.md) for the existing
67
- handoff, integration and recovery contracts and [Git Finalizer](https://github.com/xuanheng-tech/git-finalizer)
68
- for delivery evidence. WC itself does not commit, push or approve a production release.
66
+ it cannot use this shortcut. Follow [your first change](first-change.md) for an executable local
67
+ commit, handoff, integration and pause/resume example with public companion tools. See the
68
+ [operations guide](operations.md) for the existing contracts. WC itself does not commit, push
69
+ or approve a production release.
69
70
 
70
71
  For real session closeout, pass the complete recorded selector (`--client-id`, `--session-id`,
71
72
  `--run-id` when present, and `--holder`) and every known `--expected-allocation-id` to
@@ -7,8 +7,15 @@ the [getting started guide](getting-started.md) provides an isolated first run.
7
7
 
8
8
  ## Start or resume development
9
9
 
10
- 1. Run `tool-skill-sync doctor --repo <checkout> --summary`, then Controller `status --json`.
10
+ For a worked local task, use [your first change](first-change.md). Core inspection/allocation needs
11
+ WC and Git. The integration flow below additionally uses Git Finalizer and separate test and
12
+ Snapshot evidence; policy schema 4+ requires both receipt identities explicitly. The worked example
13
+ uses the public Snapshot Runner package. Context Loader and `tool-skill-sync` are optional.
14
+
15
+ 1. Run Controller `--version`, `capabilities --json`, `status --json` and `doctor --json`.
11
16
  Read the selected repository policy and distinguish installed runtime from source candidates.
17
+ If your site installed the optional maintenance checker, also run
18
+ `tool-skill-sync doctor --repo <checkout> --summary` for its tool/Skill pairing checks.
12
19
  2. Use `plan` for the existing task/authority. Reuse its allocation when supported; otherwise use
13
20
  `acquire` with explicit owner, branch, base and scope. A waiting request is not an allocation.
14
21
  3. Bind the real writer process and session/run through `writer-session` or `writer-lease-acquire`.
@@ -0,0 +1,104 @@
1
+ # Publishing WC packages
2
+
3
+ The `Publish release` workflow in `.github/workflows/release.yml` releases an existing stable tag
4
+ that is already integrated into public `main`. It does not create tags, commit, merge or push Git
5
+ branches. Source delivery continues to use the repository's Controller and Git Finalizer flow.
6
+ The software version, CLI contract and storage layout remain independently versioned.
7
+
8
+ ## One-time setup
9
+
10
+ The repository uses a GitHub environment named **`pypi`**, restricted to workflow runs from **main**.
11
+ The environment grants no additional review exemption. Keep any existing platform protections.
12
+
13
+ In the PyPI project's **Manage → Publishing → Add a new publisher → GitHub** form, register:
14
+
15
+ | Field | Exact value |
16
+ | --- | --- |
17
+ | PyPI project | `worktree-controller` |
18
+ | Owner | `xuanheng-tech` |
19
+ | Repository | `worktree-controller` |
20
+ | Workflow filename | `release.yml` |
21
+ | Environment | `pypi` |
22
+
23
+ The owner completes this authenticated PyPI account step. Do not put a long-lived PyPI token in
24
+ GitHub secrets. The workflow uses `uv publish --trusted-publishing always`: an unavailable or
25
+ mismatched publisher fails rather than falling back to another credential. Existing local
26
+ credentials are not read, copied, revoked or changed by this workflow.
27
+
28
+ See [PyPI's publisher setup](https://docs.pypi.org/trusted-publishers/adding-a-publisher/) and
29
+ [uv's GitHub integration](https://docs.astral.sh/uv/guides/integration/github/#publishing-to-pypi).
30
+
31
+ ## Audit an existing release
32
+
33
+ Run from an authenticated GitHub CLI with access to the public repository:
34
+
35
+ ```bash
36
+ gh workflow run release.yml --repo xuanheng-tech/worktree-controller --ref main \
37
+ -f tag=v1.30.5 -F publish=false
38
+ ```
39
+
40
+ This checks and builds the exact tag in a job with read-only repository permissions. Automation
41
+ comes from the immutable workflow commit; the payload is checked out separately at its tag.
42
+ The workflow records a manifest and checksums and audits the existing GitHub/PyPI inventory.
43
+ For an already published release, it also downloads and installs both distributions in fresh
44
+ environments and runs the onboarding example shipped in the source archive.
45
+
46
+ For an unpublished tag, audit reports the missing remote files and skips post-publication
47
+ installation. A successful audit is preparation evidence, not proof of a new publication or a
48
+ successful OIDC upload. WC's packaged onboarding contract introduced in 1.30.5 is required.
49
+
50
+ ## Publish a real version
51
+
52
+ After the actual source change and version update have been validated and integrated, create and
53
+ publish its exact stable version tag through the normal release process. Then trigger once:
54
+
55
+ ```bash
56
+ gh workflow run release.yml --repo xuanheng-tech/worktree-controller --ref main \
57
+ -f tag=vX.Y.Z -F publish=true
58
+ ```
59
+
60
+ Replace `vX.Y.Z` with that existing tag. The tag must exactly match `pyproject.toml` and the CLI
61
+ contract's software version. A tag outside main, a moved tag, dirty source, unknown file or
62
+ checksum mismatch stops the workflow. No force operation or Controller-state edit is used.
63
+
64
+ The workflow performs these stages:
65
+
66
+ 1. Run `scripts/check-project` on the tag: lint, full default suite, wheel/sdist build, sdist
67
+ rebuild and installed checks. Freeze one authoritative wheel/sdist pair in an immutable run
68
+ artifact. Rebuilding the sdist for validation does not replace that pair.
69
+ 2. With publishing permissions, recheck the source identity and both remote inventories. Store
70
+ the exact pair and SHA256SUMS in a GitHub draft, upload only missing PyPI files using short-lived
71
+ publishing credentials, verify the registry, and make the matching GitHub release public.
72
+ Building or installing package code never runs in this privileged job.
73
+ 3. In a separate job without publishing permissions, independently download both PyPI files,
74
+ compare their hashes, install them with fresh caches, verify metadata and all three CLI entry
75
+ points, and run the source archive's onboarding example. A short bounded retry handles a newly
76
+ uploaded version missing from the package index; functional failures are not retried.
77
+
78
+ ## Recover a failed or interrupted run
79
+
80
+ Inspect the failing step and its real exit code. A nonzero uploader exit remains a failed step,
81
+ even if a file reached the server. The GitHub draft and immutable Actions artifact retain the
82
+ original bytes. Resolve the stated blocker and use **Re-run failed jobs** in that same run:
83
+
84
+ ```bash
85
+ gh run rerun RUN_ID --failed --repo xuanheng-tech/worktree-controller
86
+ ```
87
+
88
+ The publish job consumes the successful build job's original artifact name, including its original
89
+ run attempt. It checks already uploaded bytes, uploads only missing files, and never uses
90
+ `--clobber` or blindly skips an existing PyPI filename. An already complete matching release is
91
+ observed without another upload. Unknown assets, yanked files, identity drift and different bytes
92
+ remain blocked; do not delete or replace them to make a retry pass.
93
+
94
+ A repository-wide workflow concurrency group serializes release runs without interrupting an
95
+ active publisher. GitHub retains only one pending run per concurrency group, so use failed-job
96
+ recovery instead of submitting a queue of duplicate dispatches. Bundles and check evidence have
97
+ 90-day retention. Once an artifact expires, a new run must build and validate again; if its bytes
98
+ differ from existing remote files, it stops and requires review of the retained original evidence.
99
+ It never overwrites the old release.
100
+
101
+ Receipts distinguish a read-only audit, `PUBLISHED_VERIFIED`, and `FRESH_INSTALLS_VERIFIED`.
102
+ Skipped tests remain skipped. Local installation or activation of a maintainer's WC runtime is a
103
+ separate operation; publishing a package does not update that runtime or authorize a production
104
+ release in any repository governed by WC.
@@ -7,14 +7,17 @@ The [CLI contract](../tool_cli_contract.json) is authoritative for exact interfa
7
7
  ## Optional integrations and dependencies
8
8
 
9
9
  The core control plane has a single runtime dependency (`websockets>=15,<16`) and works standalone
10
- against any local Git repository on the supported platform. Everything below is optional and is
11
- exercised only when the corresponding context or companion tool is present:
10
+ against any local Git repository on the supported platform. The integrations below are optional
11
+ for core inspection/allocation; selecting a delivery flow can add explicit evidence requirements:
12
12
 
13
13
  - **Companion delivery tools.** WC composes with, but does not require or embed, the separate
14
14
  **Context Loader** (context reading), **Snapshot Runner** (read-only review evidence) and
15
15
  **Git Finalizer** (commit and publication). Each owns its own authority; WC never merges,
16
16
  publishes or commits on their behalf. The handoff and integration flows in
17
17
  [docs/operations.md](operations.md) assume Git Finalizer is installed for delivery evidence.
18
+ Policy schema 4+ also requires explicit test and Snapshot receipt identities for preparation;
19
+ [your first change](first-change.md) uses the public Snapshot Runner package to produce them.
20
+ Context Loader and the optional maintenance checker are not required by that example.
18
21
  - **Client-specific recovery.** The `archived_codex_binding_recovery_version` capability
19
22
  (`--recover-archived-codex-session`) recovers a released writer lease whose original session
20
23
  belonged to the Codex CLI client, by verifying that client's own archived-session and
@@ -0,0 +1,242 @@
1
+ #!/usr/bin/env python3
2
+ """Deliver a README change to a local bare remote, with an optional handoff pause."""
3
+
4
+ import argparse
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import sys
10
+ from pathlib import Path
11
+
12
+ from quickstart import POLICY
13
+
14
+ OWNER = "demo-owner"
15
+ CONTENT = "# WC demo\nA delivered change.\n"
16
+ CHECK = """
17
+ import pathlib, sys
18
+ if (pathlib.Path(sys.argv[1]) / 'README.md').read_text() != sys.argv[2]:
19
+ raise SystemExit('README change did not match the reviewed result')
20
+ print('README change verified')
21
+ """
22
+
23
+
24
+ def require(condition, message):
25
+ if not condition:
26
+ raise RuntimeError(message)
27
+
28
+
29
+ def main():
30
+ parser = argparse.ArgumentParser(description=__doc__)
31
+ parser.add_argument("--workspace", type=Path, required=True)
32
+ parser.add_argument("--finalizer", type=Path, help="installed public git-finalize entrypoint")
33
+ parser.add_argument("--snapshot-runner", type=Path, help="installed public snapshot-runner entrypoint")
34
+ parser.add_argument("--pause-after-handoff", action="store_true")
35
+ parser.add_argument("--resume", action="store_true", help="continue this demo's saved handoff")
36
+ args = parser.parse_args()
37
+ workspace = args.workspace.absolute()
38
+ if args.resume:
39
+ if args.pause_after_handoff:
40
+ parser.error("--resume and --pause-after-handoff are separate invocations")
41
+ if workspace.is_symlink() or not (workspace / "handoff.json").is_file():
42
+ parser.error("--resume needs this demo's existing workspace and handoff.json")
43
+ elif workspace.exists() or workspace.is_symlink() or not workspace.parent.is_dir():
44
+ parser.error("--workspace must be a new path under an existing directory")
45
+ if sys.platform != "linux" or sys.version_info[:2] != (3, 12):
46
+ parser.error("the demo requires Linux and Python 3.12")
47
+ controller = Path(sys.executable).with_name("worktree-controller")
48
+ if not controller.is_file():
49
+ found = shutil.which("worktree-controller")
50
+ if not found:
51
+ parser.error("install WC in this Python environment first")
52
+ controller = Path(found)
53
+ finalizer = args.finalizer or shutil.which("git-finalize")
54
+ if not finalizer or not Path(finalizer).is_file() or not os.access(finalizer, os.X_OK):
55
+ parser.error("install Git Finalizer, or pass --finalizer /absolute/path/to/git-finalize")
56
+ finalizer = Path(finalizer).absolute()
57
+ runner = args.snapshot_runner or shutil.which("snapshot-runner")
58
+ if not runner or not Path(runner).is_file() or not os.access(runner, os.X_OK):
59
+ parser.error("install Snapshot Runner, or pass --snapshot-runner /absolute/path/to/snapshot-runner")
60
+ runner = Path(runner).absolute()
61
+ session_end = controller.with_name("worktree-session-end")
62
+ if not session_end.is_file() or not shutil.which("git"):
63
+ parser.error("Git and worktree-session-end must be installed")
64
+ if not args.resume:
65
+ workspace.mkdir()
66
+ (workspace / "home").mkdir()
67
+ (workspace / "tmp").mkdir()
68
+ repo, source, candidate = (workspace / name for name in ("main", "change", "integration"))
69
+ for path in (repo, source, candidate, workspace / "origin.git", workspace / "handoff.json"):
70
+ require(not path.is_symlink(), f"demo paths must not be symlinks: {path}")
71
+ environment = {key: os.environ[key] for key in ("LANG", "LC_ALL", "TZ") if key in os.environ}
72
+ home = workspace / "home"
73
+ state = home / ".local/state"
74
+ state.mkdir(parents=True, exist_ok=True, mode=0o700)
75
+ state.chmod(0o700)
76
+ environment.update(
77
+ PATH=os.pathsep.join((str(controller.parent), str(finalizer.parent), os.environ.get("PATH", os.defpath))),
78
+ HOME=str(home), TMPDIR=str(workspace / "tmp"),
79
+ XDG_STATE_HOME=str(home / ".local/state"), XDG_CONFIG_HOME=str(home / ".config"),
80
+ XDG_CACHE_HOME=str(home / ".cache"), GIT_CONFIG_NOSYSTEM="1",
81
+ GIT_CONFIG_GLOBAL=os.devnull, GIT_TERMINAL_PROMPT="0",
82
+ PYTHONNOUSERSITE="1", PYTHONDONTWRITEBYTECODE="1",
83
+ )
84
+ identity = ["--client-id", "wc-change-demo", "--session-id", "change-demo", "--run-id", "change-demo"]
85
+
86
+ def run(*arguments, expected=0):
87
+ result = subprocess.run([str(arg) for arg in arguments], cwd=workspace, env=environment,
88
+ stdin=subprocess.DEVNULL, capture_output=True, text=True,
89
+ timeout=120, check=False)
90
+ with (workspace / "commands.jsonl").open("a") as stream:
91
+ stream.write(json.dumps({"argv": [str(arg) for arg in arguments],
92
+ "returncode": result.returncode, "stdout": result.stdout,
93
+ "stderr": result.stderr}) + "\n")
94
+ require(result.returncode == expected,
95
+ f"{arguments[0]} exited {result.returncode}, expected {expected}; see commands.jsonl")
96
+ return result.stdout
97
+
98
+ def save(name, value):
99
+ (workspace / name).write_text(json.dumps(value, indent=2) + "\n")
100
+
101
+ def wc(command, *arguments):
102
+ value = json.loads(run(controller, command, "--repo", repo, "--json", *arguments))
103
+ require(not value.get("errors"), f"Controller refused {command}; see commands.jsonl")
104
+ print(f"{command}: {(value.get('decision') or {}).get('code', 'observed')}", flush=True)
105
+ return value
106
+
107
+ def row(path):
108
+ return next(item for item in wc("status")["worktrees"] if item["path"] == str(path))
109
+
110
+ def gf(name, *arguments):
111
+ value = json.loads(run(finalizer, "--summary", *arguments))
112
+ save(name, value)
113
+ require(value["status"] == "success", f"Finalizer stopped; see {name}")
114
+ return value
115
+
116
+ def check(path, name):
117
+ output = run(sys.executable, "-I", "-c", CHECK, path, CONTENT)
118
+ require(not run("git", "-C", path, "status", "--porcelain"), "candidate must be clean")
119
+ save(name, {"status": "PASS", "checked_file": "README.md", "stdout": output,
120
+ "commit": run("git", "-C", path, "rev-parse", "HEAD").strip()})
121
+
122
+ def snapshot(path, name, *, committed):
123
+ arguments = ["branch-review", "--repo", path, "main"] if committed else [
124
+ "diff-audit", "--repo", path, "--scope-path", "README.md"]
125
+ summary = json.loads(run(runner, *arguments, "--summary"))
126
+ save(name, summary)
127
+ require(summary["status"] == "complete" and not summary["truncated"]
128
+ and not summary["evidence_gap"] and not summary["warnings"], "Snapshot evidence incomplete")
129
+ evidence = json.loads(run(runner, "read", summary["snapshot_id"], "--repo", path, "--path", "README.md"))
130
+ save(name.replace(".json", "-read.json"), evidence)
131
+ require(evidence["found"] and not evidence["truncated"] and not evidence["evidence_gap"],
132
+ "README snapshot could not be read")
133
+ return summary["snapshot_id"]
134
+
135
+ if not args.resume:
136
+ run("git", "init", "--bare", "--initial-branch=main", workspace / "origin.git")
137
+ run("git", "init", "--initial-branch=main", repo)
138
+ for key, value in (("user.name", "WC Demo"), ("user.email", "demo@example.invalid")):
139
+ run("git", "-C", repo, "config", key, value)
140
+ run("git", "-C", repo, "remote", "add", "origin", workspace / "origin.git")
141
+ policy = repo / ".agents/worktree-policy.toml"
142
+ policy.parent.mkdir()
143
+ policy.write_text(POLICY)
144
+ (repo / "README.md").write_text("# WC demo\nA local change task.\n")
145
+ gf("bootstrap-finalizer.json", "--initial-publish", "--remote", "origin", "--repo", repo,
146
+ "--message", "Initialize local demo", "--", "README.md", ".agents/worktree-policy.toml")
147
+ wc("adopt", "--path", repo, "--task-key", "demo-control", "--authority-key", "demo-control",
148
+ "--module", "demo", "--role", "control-plane", "--owner", OWNER, "--lifecycle", "ACTIVE")
149
+ request = ["--task-key", "demo-change", "--authority-key", "demo-change", "--module", "demo", "--role", "feature"]
150
+ require(wc("plan", *request)["decision"]["code"] == "CREATE_ALLOWED", "admission refused")
151
+ acquired = wc("acquire", *request, "--owner", OWNER, "--branch", "demo/change",
152
+ "--base-ref", "main", "--path", source, "--scope", "README.md")
153
+ allocation = acquired["decision"]["allocation_id"]
154
+ lease = wc("writer-lease-acquire", "--path", source, "--owner", OWNER,
155
+ "--holder-pid", os.getpid(), *identity)["decision"]
156
+ guard = wc("write-guard", "--path", source, "--task-key", "demo-change",
157
+ "--authority-key", "demo-change", "--owner", OWNER, "--holder", os.getpid(),
158
+ *identity, "--operation", "feature-write")
159
+ require(guard["decision"]["code"] == "WRITE_ALLOWED", "writer was refused")
160
+ (source / "README.md").write_text(CONTENT)
161
+ run(sys.executable, "-I", "-c", CHECK, source, CONTENT)
162
+ source_snapshot = snapshot(source, "source-snapshot.json", committed=False)
163
+ delivered = gf("source-finalizer.json", "--initial-branch-publish", "--remote", "origin",
164
+ "--remote-branch", "demo/change", "--repo", source,
165
+ "--message", "Deliver the README change", "--", "README.md")
166
+ commit = delivered["commit"]["sha"]
167
+ holder = lease["holder"]
168
+ wc("writer-lease-release", "--path", source, "--lease-id", lease["lease_id"],
169
+ "--holder-pid", holder["pid"], "--holder-boot-id", holder["boot_id"],
170
+ "--holder-start-ticks", holder["start_ticks"], *identity)
171
+ wc("handoff", "--path", source, "--expected-record-version", row(source)["record_version"],
172
+ "--kind", "integration-ready", "--commit", commit, "--finalizer-commit", commit,
173
+ "--finalizer-record-id", workspace / "source-finalizer.json")
174
+ queued = wc("integration-enqueue", "--task-key", "demo-integration", "--authority-key",
175
+ "demo-integration", "--module", "demo", "--owner", OWNER,
176
+ "--source-allocation", allocation, "--source-snapshot", source_snapshot)["decision"]
177
+ checkpoint = {"schema_version": 1, "intent_id": queued["intent_id"],
178
+ "source_allocation": allocation, "source_commit": commit,
179
+ "repository_id": wc("status")["repository"]["repository_id"]}
180
+ save("handoff.json", checkpoint)
181
+ if args.pause_after_handoff:
182
+ print(f"PAUSED: source published to local origin, writer released; resume with --resume --workspace {workspace}")
183
+ return
184
+ else:
185
+ checkpoint = json.loads((workspace / "handoff.json").read_text())
186
+ require(checkpoint["schema_version"] == 1, "unsupported demo checkpoint")
187
+ require(run("git", "-C", repo, "rev-parse", "--show-toplevel").strip() == str(repo),
188
+ "repository path changed")
189
+ require(run("git", "-C", repo, "remote", "get-url", "origin").strip() == str(workspace / "origin.git"),
190
+ "demo remote changed")
191
+ require(wc("status")["repository"]["repository_id"] == checkpoint["repository_id"],
192
+ "repository identity changed")
193
+ require(run("git", "-C", repo, "rev-parse", "demo/change^{commit}").strip() == checkpoint["source_commit"],
194
+ "saved source identity changed")
195
+ for path in (repo, source, candidate):
196
+ if path.exists():
197
+ require(not run("git", "-C", path, "status", "--porcelain"),
198
+ f"uncommitted files remain in {path}; preserve them and follow recovery-guide")
199
+
200
+ guide = wc("recovery-guide", "--intent-id", checkpoint["intent_id"])["decision"]
201
+ if guide.get("terminal_status") != "PUBLISHED_RELEASED":
202
+ require(guide["intent"]["state"] in {"QUEUED", "PREPARING_INTEGRATION"},
203
+ "this demo resumes only queued or unchanged claimed work; follow recovery-guide")
204
+ claimed = wc("integration-claim", "--intent-id", checkpoint["intent_id"], "--owner", OWNER,
205
+ "--path", candidate, "--branch", "demo/integration")["decision"]
206
+ check(candidate, "candidate-check.json")
207
+ candidate_snapshot = snapshot(candidate, "candidate-snapshot.json", committed=True)
208
+ wc("integration-validate", "--path", candidate, "--owner", OWNER,
209
+ "--expected-record-version", row(candidate)["record_version"],
210
+ "--validation-evidence-id", workspace / "candidate-check.json",
211
+ "--tests-receipt-id", workspace / "candidate-check.json",
212
+ "--snapshot-receipt-id", candidate_snapshot)
213
+ publication = wc("lease-acquire", "--path", candidate, "--owner", OWNER,
214
+ "--expected-record-version", row(candidate)["record_version"],
215
+ "--run-id", "demo-publication")["decision"]
216
+ published = gf("publication-finalizer.json", "--publish-integration-candidate", claimed["candidate_oid"],
217
+ "--repo", candidate, "--lease-id", publication["lease_id"], "--run-id", "demo-publication")
218
+ require(published["remote_verify"]["oid"] == claimed["candidate_oid"], "remote identity mismatch")
219
+ wc("lease-complete", "--path", candidate, "--expected-record-version", row(candidate)["record_version"],
220
+ "--lease-id", publication["lease_id"], "--published-commit", claimed["candidate_oid"],
221
+ "--finalizer-record-id", published["record_id"], "--run-id", "demo-publication")
222
+ candidate_allocation = claimed["allocation_id"]
223
+ else:
224
+ candidate_allocation = guide["intent"]["allocation_id"]
225
+ wc("canonical-refresh")
226
+ check(repo, "main-check.json")
227
+ strict = json.loads(run(session_end, "--path", repo, *identity, "--holder", os.getpid(),
228
+ "--expected-allocation-id", checkpoint["source_allocation"],
229
+ "--expected-allocation-id", candidate_allocation, "--check", "--require-complete"))
230
+ save("closeout.json", strict)
231
+ require(strict["summary"]["terminal_complete"] is True, "checkout closeout incomplete")
232
+ require(wc("doctor")["decision"]["code"] == "PASS", "doctor did not pass")
233
+ require(not source.exists() and not candidate.exists(), "linked checkouts remain; follow their release guidance")
234
+ print(f"PASS: change integrated into local main; both checkouts released; branches and evidence kept: {workspace}")
235
+
236
+
237
+ if __name__ == "__main__":
238
+ try:
239
+ main()
240
+ except (OSError, RuntimeError, ValueError, KeyError, StopIteration, subprocess.SubprocessError) as error:
241
+ print(f"Demo stopped; results are preserved. {error}", file=sys.stderr)
242
+ raise SystemExit(1) from error
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "worktree-controller"
7
- version = "1.30.5"
7
+ version = "1.30.6"
8
8
  description = "Govern Git worktree budgets, authority, ownership, and lifecycle"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12,<3.13"
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "worktree-controller"
7
- version = "1.30.5"
7
+ version = "1.30.6"
8
8
  description = "Govern Git worktree budgets, authority, ownership, and lifecycle"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12,<3.13"
@@ -1,3 +1,3 @@
1
1
  """Worktree lifecycle controller."""
2
2
 
3
- __version__ = "1.30.5"
3
+ __version__ = "1.30.6"
@@ -1065,7 +1065,7 @@
1065
1065
  "session_launcher_entrypoint": "worktree-session",
1066
1066
  "session_entrypoint": "worktree-session-end",
1067
1067
  "tool_name": "worktree-controller",
1068
- "tool_version": "1.30.5",
1068
+ "tool_version": "1.30.6",
1069
1069
  "delivered_closeout_version": 1,
1070
1070
  "retained_archive_version": 1,
1071
1071
  "retention_convergence_version": 2,
@@ -1065,7 +1065,7 @@
1065
1065
  "session_launcher_entrypoint": "worktree-session",
1066
1066
  "session_entrypoint": "worktree-session-end",
1067
1067
  "tool_name": "worktree-controller",
1068
- "tool_version": "1.30.5",
1068
+ "tool_version": "1.30.6",
1069
1069
  "delivered_closeout_version": 1,
1070
1070
  "retained_archive_version": 1,
1071
1071
  "retention_convergence_version": 2,