worktree-controller 1.30.6__tar.gz → 1.31.0__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.
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/PKG-INFO +12 -5
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/README.md +11 -4
- worktree_controller-1.31.0/docs/daily-workflow.md +127 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/first-change.md +4 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/operations.md +4 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/reference.md +3 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/examples/change_task.py +12 -9
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/pyproject.toml +1 -1
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/pyproject.toml.orig +1 -1
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/__init__.py +1 -1
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/cli.py +14 -1
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/lifecycle.py +25 -3
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/recovery_guidance.py +7 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/rendering.py +42 -0
- worktree_controller-1.31.0/src/worktree_controller/task_workflow.py +269 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/tool_cli_contract.json +37 -2
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/tool_cli_contract.json +37 -2
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/LICENSE +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/architecture.md +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/getting-started.md +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/docs/publishing.md +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/examples/quickstart.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/activation_layout.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/branch_retirement.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/canonical_refresh.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/canonical_transition.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/cleanup_attempts.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/cleanup_evidence.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/cleanup_recovery.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/controller.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/convergence.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/delivered_closeout.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/disposable.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/environment.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/frozen_recovery.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/git_backend.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/governance.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/hardlinks.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/integration_cleanup.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/integration_conflict.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/integration_retry.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/keyed_document_scope.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/lifecycle_history.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/lifecycle_observation.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/metadata_codec.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/metadata_relocation.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/models.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/mount_diagnostics.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/multiple_cleanup_recovery.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/namespace_migration.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/native_session_recovery.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/owner_actions.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/policy.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/preservation.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/preserved_cleanup.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/publication_execution.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/reconciler.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/registration_backfill.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/registration_prune.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/resource_admission.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/retirement_adapters.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/retirement_execution.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/semantics.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/session.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/session_admission.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/source_cleanup.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/source_preservation.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/source_recovery.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/store.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/transaction_scope.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/writer_lease.py +0 -0
- {worktree_controller-1.30.6 → worktree_controller-1.31.0}/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.
|
|
3
|
+
Version: 1.31.0
|
|
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.
|
|
26
|
+
**Current release: 1.31.0 · Linux · Python 3.12 · Apache-2.0**
|
|
27
27
|
|
|
28
28
|
## Install
|
|
29
29
|
|
|
@@ -32,7 +32,7 @@ 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.
|
|
35
|
+
python -m pip install worktree-controller==1.31.0
|
|
36
36
|
worktree-controller --version
|
|
37
37
|
worktree-controller capabilities --json
|
|
38
38
|
```
|
|
@@ -41,7 +41,7 @@ WC requires Git and readable Linux `/proc`. Other operating systems and Python v
|
|
|
41
41
|
supported by this release. Its one runtime dependency is `websockets>=15,<16`.
|
|
42
42
|
On Ubuntu, if creating the environment reports that `ensurepip` is unavailable, install the
|
|
43
43
|
distribution's `python3.12-venv` package first (for example, `sudo apt install python3.12-venv`).
|
|
44
|
-
Software version 1.
|
|
44
|
+
Software version 1.31.0 uses CLI contract **61** and storage layout **5**.
|
|
45
45
|
|
|
46
46
|
## Try it in a disposable repository
|
|
47
47
|
|
|
@@ -50,7 +50,7 @@ With the environment above active:
|
|
|
50
50
|
```bash
|
|
51
51
|
git clone https://github.com/xuanheng-tech/worktree-controller.git wc
|
|
52
52
|
cd wc
|
|
53
|
-
git checkout v1.
|
|
53
|
+
git checkout v1.31.0
|
|
54
54
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -69,6 +69,13 @@ To actually change a file, publish to a local bare remote, integrate and recover
|
|
|
69
69
|
follow [your first change](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/first-change.md).
|
|
70
70
|
That complete example additionally uses public Git Finalizer and Snapshot Runner installations.
|
|
71
71
|
|
|
72
|
+
## Everyday tasks
|
|
73
|
+
|
|
74
|
+
Use `worktree-controller task list` to see live checkout tasks and `task show --task-key <task>`
|
|
75
|
+
for the recorded owner and next formal step. The [everyday task guide](docs/daily-workflow.md)
|
|
76
|
+
covers simpler start, original-task checkpoint/resume, guarded no-change release and exact
|
|
77
|
+
Finalizer handoff. These CLI 61 entrypoints retain the existing ownership, lease and policy checks.
|
|
78
|
+
|
|
72
79
|
## Use WC in a real project
|
|
73
80
|
|
|
74
81
|
A governed repository tracks `.agents/worktree-policy.toml` and registers its checkouts with WC.
|
|
@@ -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.
|
|
9
|
+
**Current release: 1.31.0 · Linux · Python 3.12 · Apache-2.0**
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -15,7 +15,7 @@ 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.
|
|
18
|
+
python -m pip install worktree-controller==1.31.0
|
|
19
19
|
worktree-controller --version
|
|
20
20
|
worktree-controller capabilities --json
|
|
21
21
|
```
|
|
@@ -24,7 +24,7 @@ WC requires Git and readable Linux `/proc`. Other operating systems and Python v
|
|
|
24
24
|
supported by this release. Its one runtime dependency is `websockets>=15,<16`.
|
|
25
25
|
On Ubuntu, if creating the environment reports that `ensurepip` is unavailable, install the
|
|
26
26
|
distribution's `python3.12-venv` package first (for example, `sudo apt install python3.12-venv`).
|
|
27
|
-
Software version 1.
|
|
27
|
+
Software version 1.31.0 uses CLI contract **61** and storage layout **5**.
|
|
28
28
|
|
|
29
29
|
## Try it in a disposable repository
|
|
30
30
|
|
|
@@ -33,7 +33,7 @@ With the environment above active:
|
|
|
33
33
|
```bash
|
|
34
34
|
git clone https://github.com/xuanheng-tech/worktree-controller.git wc
|
|
35
35
|
cd wc
|
|
36
|
-
git checkout v1.
|
|
36
|
+
git checkout v1.31.0
|
|
37
37
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -52,6 +52,13 @@ To actually change a file, publish to a local bare remote, integrate and recover
|
|
|
52
52
|
follow [your first change](https://github.com/xuanheng-tech/worktree-controller/blob/main/docs/first-change.md).
|
|
53
53
|
That complete example additionally uses public Git Finalizer and Snapshot Runner installations.
|
|
54
54
|
|
|
55
|
+
## Everyday tasks
|
|
56
|
+
|
|
57
|
+
Use `worktree-controller task list` to see live checkout tasks and `task show --task-key <task>`
|
|
58
|
+
for the recorded owner and next formal step. The [everyday task guide](docs/daily-workflow.md)
|
|
59
|
+
covers simpler start, original-task checkpoint/resume, guarded no-change release and exact
|
|
60
|
+
Finalizer handoff. These CLI 61 entrypoints retain the existing ownership, lease and policy checks.
|
|
61
|
+
|
|
55
62
|
## Use WC in a real project
|
|
56
63
|
|
|
57
64
|
A governed repository tracks `.agents/worktree-policy.toml` and registers its checkouts with WC.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# Everyday task operations
|
|
2
|
+
|
|
3
|
+
Use `worktree-controller task` for named feature/audit checkout tasks. It resolves the recorded
|
|
4
|
+
path, owner and record version, then calls the existing guarded lifecycle operation. The existing
|
|
5
|
+
low-level commands remain available. No separate task database or lifecycle is introduced.
|
|
6
|
+
|
|
7
|
+
These entrypoints require **WC 1.31.0, CLI 61, task_workflow_version 1**. Storage layout remains 5.
|
|
8
|
+
Check the installed `--version`, `capabilities --json` and `task --help` before using them. A newer
|
|
9
|
+
source checkout does not update a separately installed Controller. Governed installations still
|
|
10
|
+
need their normal compatibility, deployment and activation checks.
|
|
11
|
+
|
|
12
|
+
Start in an already governed repository: inspect `status`, `doctor` and its selected policy;
|
|
13
|
+
adopt existing checkouts explicitly. Examples below use `/project` as that repository's canonical
|
|
14
|
+
checkout. These commands operate locally and do not grant permission to publish or deploy.
|
|
15
|
+
|
|
16
|
+
## See the current work
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
worktree-controller task list --repo /project
|
|
20
|
+
worktree-controller task show --repo /project --task-key report-fix
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`list` shows live linked checkouts, their recorded owner, lifecycle, writer lease observation,
|
|
24
|
+
cleanliness and any checkpoint dependency. It is not a list of all business Tasks or queued
|
|
25
|
+
integrations. `show` explains the named checkout and its next formal step. After release, the
|
|
26
|
+
task key queries the original lifecycle history instead of inventing a new task.
|
|
27
|
+
|
|
28
|
+
Task keys can be ambiguous in imported or historical work. Mutations never pick a checkout by
|
|
29
|
+
owner, age or directory order. If multiple live checkouts share a key, select its exact `--path`
|
|
30
|
+
instead. Historical results retain their proof status and pagination; absence is not completion.
|
|
31
|
+
|
|
32
|
+
## 1. Start and finish a task with no changes
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
worktree-controller task start --repo /project --task-key report-fix \
|
|
36
|
+
--owner alice --scope src/reports
|
|
37
|
+
|
|
38
|
+
# An inspection that made no file changes and produced no task commit:
|
|
39
|
+
worktree-controller task finish --repo /project --task-key report-fix \
|
|
40
|
+
--owner alice --outcome no-change
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Start requires an explicit task key, owner and file scope. It defaults to feature role,
|
|
44
|
+
`authority_key=task_key`, module `task`, branch `task/<task-key>`, and the repository policy's
|
|
45
|
+
publication remote/branch as base. Override `--authority-key`, `--module`, `--branch`, `--base-ref`
|
|
46
|
+
or `--path` when your existing task requires different values. It does not fetch or substitute
|
|
47
|
+
another base when the policy base is unavailable locally. An existing live task is not duplicated.
|
|
48
|
+
|
|
49
|
+
Admission creates a checkout, **not write entitlement**. Before editing, bind the real writer
|
|
50
|
+
with the existing writer-session/lease flow and require `write-guard` to allow the operation.
|
|
51
|
+
Use the admitted path and declared task/authority. End only that writer's exact lease before
|
|
52
|
+
finishing. See [start or resume development](operations.md#start-or-resume-development).
|
|
53
|
+
|
|
54
|
+
The no-change finish first records the existing `complete-no-change` result, then attempts normal
|
|
55
|
+
guarded release. It retains the branch. A task commit or dirty worktree refuses no-change completion.
|
|
56
|
+
Unknown ignored files or registered resources can block the separate release step. In that case,
|
|
57
|
+
exit status is **2**, `TASK_RELEASE_BLOCKED` retains the completed prefix and provides a release
|
|
58
|
+
dry-run command. Preserve/classify those files through their existing owner flow. Repeating this
|
|
59
|
+
finish while `NO_CHANGE_COMPLETED` retries release without creating another completion receipt.
|
|
60
|
+
After successful release, use `task show`; another finish refuses the absent live allocation.
|
|
61
|
+
|
|
62
|
+
## 2. Pause and continue the original task
|
|
63
|
+
|
|
64
|
+
First save a checkpoint in your existing Task system. Obtain its content-addressed ID and the
|
|
65
|
+
exact HEAD it records. WC records that owner's attestation; it does not create or verify the
|
|
66
|
+
checkpoint payload and does not copy uncommitted files into it. Then, after the writer ends:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
worktree-controller task finish --repo /project --task-key report-fix --owner alice \
|
|
70
|
+
--outcome checkpoint --checkpoint-id "$checkpoint_id" --checkpoint-head "$checkpoint_head" \
|
|
71
|
+
--waiting-on 'Awaiting source data'
|
|
72
|
+
|
|
73
|
+
worktree-controller task resume --repo /project --task-key report-fix --owner alice
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The checkpoint ID must use the existing `sha256:<64 hex>` or `art_<64 hex>` format. Its HEAD
|
|
77
|
+
must still match the checkout. Parking preserves the same checkout, branch, uncommitted files
|
|
78
|
+
and unknown files; work remains unfinished. Resume uses the same allocation and owner, without
|
|
79
|
+
creating a replacement task or writer lease. Obtain fresh write entitlement before editing again.
|
|
80
|
+
This entry does not release an unfinished checkout; that remains a separate reviewed owner flow.
|
|
81
|
+
|
|
82
|
+
## 3. Hand off a completed change
|
|
83
|
+
|
|
84
|
+
Run appropriate tests/review and publish the task branch through your authorized Git Finalizer
|
|
85
|
+
flow. Use the actual returned record and the exact commit it binds; do not invent evidence.
|
|
86
|
+
After ending the exact writer:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
worktree-controller task finish --repo /project --task-key report-fix --owner alice \
|
|
90
|
+
--outcome handoff --finalizer-record-id "$finalizer_record_id" --commit "$commit"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
`TASK_HANDED_OFF` freezes the original scope and commit. It does not integrate or publish main,
|
|
94
|
+
release the checkout, declare the business Task complete or authorize a production deployment.
|
|
95
|
+
Continue the existing [handoff and integration](operations.md#handoff-and-integrate) flow, or use
|
|
96
|
+
the complete [first change example](first-change.md). Scope/commit drift and unknown files keep
|
|
97
|
+
their existing refusals. Active **or expired but still recorded** writer leases remain blocked;
|
|
98
|
+
these entrypoints never steal, renew or remove them.
|
|
99
|
+
|
|
100
|
+
## Inspect a blocked integration
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
worktree-controller task recover --repo /project --task-key original-integration-task
|
|
104
|
+
# Also works after its candidate checkout has been released:
|
|
105
|
+
worktree-controller task recover --repo /project --intent-id "$original_intent_id" --json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`recover` is a read-only entry to the existing `recovery-guide`. It shows the recorded owner,
|
|
109
|
+
blockers and exact next formal command, retaining the original intent and evidence. For an
|
|
110
|
+
unchanged BLOCKED integration, the supported path starts with quarantine, then formal abort,
|
|
111
|
+
guarded release and retry of the original intent. Execute only the current owner-authorized
|
|
112
|
+
step, then run `recover` again: every transition can change the next command and record version.
|
|
113
|
+
Source/scope drift, dirty files, active publication and missing evidence still block recovery.
|
|
114
|
+
The entry does not run commands, edit Controller metadata or make a recovery plan executable.
|
|
115
|
+
|
|
116
|
+
## Read results accurately
|
|
117
|
+
|
|
118
|
+
All actions accept `--json`. `list`, `show` and `recover` return 0 when observation succeeds,
|
|
119
|
+
including when that observation describes a blocker. This is not recovery or task completion.
|
|
120
|
+
Mutating actions return 0 only when the requested supported operation finishes; refusals,
|
|
121
|
+
`RESOURCE_WAIT` and incomplete release return 2. A waiting admission keeps its existing request;
|
|
122
|
+
inspect `admission-queue` instead of creating another task or branch.
|
|
123
|
+
|
|
124
|
+
Task selectors do not replace exact session closeout. At normal session exit, use the real
|
|
125
|
+
client/session/holder/run and every known expected allocation with `worktree-session-end --check
|
|
126
|
+
--require-complete`; read all rows and `summary.terminal_complete`. Business Task completion,
|
|
127
|
+
checkout release and published integration each require their own evidence.
|
|
@@ -6,6 +6,10 @@ pause after handoff and continue in a new process with the original task and int
|
|
|
6
6
|
It creates a **local bare Git remote**, never contacts a hosting service, and needs no credentials.
|
|
7
7
|
Use a disposable workspace; do not point it at an existing project.
|
|
8
8
|
|
|
9
|
+
This release uses the CLI 61 [task entrypoints](daily-workflow.md) for start, handoff and
|
|
10
|
+
recovery inspection; use WC 1.31.0 or newer with `task_workflow_version` 1. The underlying
|
|
11
|
+
writer, review, integration and closeout guards are the same as the low-level flow.
|
|
12
|
+
|
|
9
13
|
## Install the example's tools
|
|
10
14
|
|
|
11
15
|
WC itself needs Linux, Python 3.12, Git and readable `/proc`. Install the released version using
|
|
@@ -7,6 +7,10 @@ the [getting started guide](getting-started.md) provides an isolated first run.
|
|
|
7
7
|
|
|
8
8
|
## Start or resume development
|
|
9
9
|
|
|
10
|
+
For everyday feature/audit work, the [task entrypoints](daily-workflow.md) resolve the original
|
|
11
|
+
checkout and record identity by task key, covering start, checkpoint/resume and finish. The
|
|
12
|
+
steps below remain the underlying authority and the route for advanced integration/recovery.
|
|
13
|
+
|
|
10
14
|
For a worked local task, use [your first change](first-change.md). Core inspection/allocation needs
|
|
11
15
|
WC and Git. The integration flow below additionally uses Git Finalizer and separate test and
|
|
12
16
|
Snapshot evidence; policy schema 4+ requires both receipt identities explicitly. The worked example
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Technical reference
|
|
2
2
|
|
|
3
|
+
For the task-oriented CLI 61 entrypoints, see [everyday task operations](daily-workflow.md).
|
|
4
|
+
They delegate to the existing lifecycle guards and introduce no additional stored state.
|
|
5
|
+
|
|
3
6
|
For a first run, use the [getting started guide](getting-started.md). This reference retains
|
|
4
7
|
the detailed contracts, historical compatibility notes and benchmark evidence previously in the README.
|
|
5
8
|
The [CLI contract](../tool_cli_contract.json) is authoritative for exact interfaces.
|
|
@@ -99,7 +99,11 @@ def main():
|
|
|
99
99
|
(workspace / name).write_text(json.dumps(value, indent=2) + "\n")
|
|
100
100
|
|
|
101
101
|
def wc(command, *arguments):
|
|
102
|
-
|
|
102
|
+
if command == "task":
|
|
103
|
+
action, *arguments = arguments
|
|
104
|
+
value = json.loads(run(controller, command, action, "--repo", repo, "--json", *arguments))
|
|
105
|
+
else:
|
|
106
|
+
value = json.loads(run(controller, command, "--repo", repo, "--json", *arguments))
|
|
103
107
|
require(not value.get("errors"), f"Controller refused {command}; see commands.jsonl")
|
|
104
108
|
print(f"{command}: {(value.get('decision') or {}).get('code', 'observed')}", flush=True)
|
|
105
109
|
return value
|
|
@@ -146,11 +150,10 @@ def main():
|
|
|
146
150
|
"--message", "Initialize local demo", "--", "README.md", ".agents/worktree-policy.toml")
|
|
147
151
|
wc("adopt", "--path", repo, "--task-key", "demo-control", "--authority-key", "demo-control",
|
|
148
152
|
"--module", "demo", "--role", "control-plane", "--owner", OWNER, "--lifecycle", "ACTIVE")
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
allocation = acquired["decision"]["allocation_id"]
|
|
153
|
+
acquired = wc("task", "start", "--task-key", "demo-change", "--module", "demo",
|
|
154
|
+
"--owner", OWNER, "--branch", "demo/change", "--base-ref", "main",
|
|
155
|
+
"--path", source, "--scope", "README.md")
|
|
156
|
+
allocation = acquired["decision"]["operation"]["allocation_id"]
|
|
154
157
|
lease = wc("writer-lease-acquire", "--path", source, "--owner", OWNER,
|
|
155
158
|
"--holder-pid", os.getpid(), *identity)["decision"]
|
|
156
159
|
guard = wc("write-guard", "--path", source, "--task-key", "demo-change",
|
|
@@ -168,8 +171,8 @@ def main():
|
|
|
168
171
|
wc("writer-lease-release", "--path", source, "--lease-id", lease["lease_id"],
|
|
169
172
|
"--holder-pid", holder["pid"], "--holder-boot-id", holder["boot_id"],
|
|
170
173
|
"--holder-start-ticks", holder["start_ticks"], *identity)
|
|
171
|
-
wc("
|
|
172
|
-
"--
|
|
174
|
+
wc("task", "finish", "--task-key", "demo-change", "--owner", OWNER,
|
|
175
|
+
"--outcome", "handoff", "--commit", commit,
|
|
173
176
|
"--finalizer-record-id", workspace / "source-finalizer.json")
|
|
174
177
|
queued = wc("integration-enqueue", "--task-key", "demo-integration", "--authority-key",
|
|
175
178
|
"demo-integration", "--module", "demo", "--owner", OWNER,
|
|
@@ -197,7 +200,7 @@ def main():
|
|
|
197
200
|
require(not run("git", "-C", path, "status", "--porcelain"),
|
|
198
201
|
f"uncommitted files remain in {path}; preserve them and follow recovery-guide")
|
|
199
202
|
|
|
200
|
-
guide = wc("
|
|
203
|
+
guide = wc("task", "recover", "--intent-id", checkpoint["intent_id"])["decision"]["guidance"]
|
|
201
204
|
if guide.get("terminal_status") != "PUBLISHED_RELEASED":
|
|
202
205
|
require(guide["intent"]["state"] in {"QUEUED", "PREPARING_INTEGRATION"},
|
|
203
206
|
"this demo resumes only queued or unchanged claimed work; follow recovery-guide")
|
|
@@ -104,6 +104,10 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
104
104
|
commands = parser.add_subparsers(dest="command", required=True)
|
|
105
105
|
common = _common_parser()
|
|
106
106
|
|
|
107
|
+
from .task_workflow import add_parser as add_task_parser
|
|
108
|
+
|
|
109
|
+
add_task_parser(commands, common)
|
|
110
|
+
|
|
107
111
|
status_parser = commands.add_parser("status", parents=[common], help="show Git and controller state")
|
|
108
112
|
status_parser.add_argument("--namespace-pid", type=int,
|
|
109
113
|
help="compare mounts with an existing writer process")
|
|
@@ -1020,7 +1024,8 @@ def _main(argv: list[str] | None) -> int:
|
|
|
1020
1024
|
payload = envelope("capabilities")
|
|
1021
1025
|
payload["decision"] = {
|
|
1022
1026
|
"code": "CAPABILITIES",
|
|
1023
|
-
"contract_version":
|
|
1027
|
+
"contract_version": 61,
|
|
1028
|
+
"task_workflow_version": 1,
|
|
1024
1029
|
"exited_holder_release_version": 1,
|
|
1025
1030
|
"canonical_refresh_version": 1,
|
|
1026
1031
|
"host_handoff_continuation_version": HOST_HANDOFF_CONTINUATION_VERSION,
|
|
@@ -1119,6 +1124,10 @@ def _main(argv: list[str] | None) -> int:
|
|
|
1119
1124
|
release_receipt_id=parsed.release_receipt_id,
|
|
1120
1125
|
limit=parsed.limit, after=parsed.after,
|
|
1121
1126
|
)
|
|
1127
|
+
elif parsed.command == "task":
|
|
1128
|
+
from .task_workflow import dispatch as dispatch_task
|
|
1129
|
+
|
|
1130
|
+
payload = dispatch_task(parsed)
|
|
1122
1131
|
elif parsed.command == "recovery-guide":
|
|
1123
1132
|
from .recovery_guidance import recovery_guide
|
|
1124
1133
|
|
|
@@ -1888,6 +1897,10 @@ def _main(argv: list[str] | None) -> int:
|
|
|
1888
1897
|
_print(payload, parsed.json)
|
|
1889
1898
|
return 2
|
|
1890
1899
|
_print(payload, parsed.json)
|
|
1900
|
+
if parsed.command == "task" and (
|
|
1901
|
+
payload.get("errors") or payload.get("decision", {}).get("operation_complete") is False
|
|
1902
|
+
):
|
|
1903
|
+
return 2
|
|
1891
1904
|
if (payload.get("decision") or {}).get("code") in {
|
|
1892
1905
|
"RESOURCE_CLEANUP_BLOCKED",
|
|
1893
1906
|
"RECOVERY_ATTEMPT_INCOMPLETE",
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/lifecycle.py
RENAMED
|
@@ -2305,6 +2305,8 @@ def complete_no_change(
|
|
|
2305
2305
|
expected_record_version: int,
|
|
2306
2306
|
owner: str,
|
|
2307
2307
|
preserve_only: bool = False,
|
|
2308
|
+
expected_allocation_id: str | None = None,
|
|
2309
|
+
require_no_writer_lease: bool = False,
|
|
2308
2310
|
) -> dict[str, Any]:
|
|
2309
2311
|
"""Record a genuine no-change completion for a task that produced no commit.
|
|
2310
2312
|
|
|
@@ -2320,7 +2322,9 @@ def complete_no_change(
|
|
|
2320
2322
|
repository = inventory(repository_path)
|
|
2321
2323
|
snapshot = store._load_unlocked()
|
|
2322
2324
|
item, record = _managed_record(repository, snapshot, target)
|
|
2323
|
-
_cas(record, expected_record_version)
|
|
2325
|
+
_cas(record, expected_record_version, expected_allocation_id)
|
|
2326
|
+
if require_no_writer_lease and record.allocation_id in snapshot.writer_leases:
|
|
2327
|
+
raise ControllerError("WRITER_LEASE_PRESENT", "end the exact writer before task completion")
|
|
2324
2328
|
policy = load_policy(repository.canonical_path)
|
|
2325
2329
|
if policy.retained_slots < 1:
|
|
2326
2330
|
raise ControllerError(
|
|
@@ -2683,6 +2687,8 @@ def resume(
|
|
|
2683
2687
|
target: Path,
|
|
2684
2688
|
expected_record_version: int,
|
|
2685
2689
|
owner: str,
|
|
2690
|
+
expected_allocation_id: str | None = None,
|
|
2691
|
+
require_no_writer_lease: bool = False,
|
|
2686
2692
|
) -> dict[str, Any]:
|
|
2687
2693
|
"""Return a retained checkout to writer status under normal admission."""
|
|
2688
2694
|
|
|
@@ -2692,7 +2698,9 @@ def resume(
|
|
|
2692
2698
|
repository = inventory(repository_path)
|
|
2693
2699
|
snapshot = store._load_unlocked()
|
|
2694
2700
|
item, record = _managed_record(repository, snapshot, target)
|
|
2695
|
-
_cas(record, expected_record_version)
|
|
2701
|
+
_cas(record, expected_record_version, expected_allocation_id)
|
|
2702
|
+
if require_no_writer_lease and record.allocation_id in snapshot.writer_leases:
|
|
2703
|
+
raise ControllerError("WRITER_LEASE_PRESENT", "an existing writer blocks task resume")
|
|
2696
2704
|
if record.lifecycle != "RETAINED":
|
|
2697
2705
|
raise ControllerError(
|
|
2698
2706
|
"INVALID_LIFECYCLE", f"lifecycle {record.lifecycle} is not retained"
|
|
@@ -3091,6 +3099,9 @@ def handoff(
|
|
|
3091
3099
|
scope_paths: tuple[str, ...] = (),
|
|
3092
3100
|
mainline_commit: str | None = None,
|
|
3093
3101
|
scope_changed: bool = False,
|
|
3102
|
+
expected_allocation_id: str | None = None,
|
|
3103
|
+
expected_owner: str | None = None,
|
|
3104
|
+
require_no_writer_lease: bool = False,
|
|
3094
3105
|
) -> dict[str, Any]:
|
|
3095
3106
|
if kind not in {"integration-ready", "preserved"}:
|
|
3096
3107
|
raise ControllerError("INVALID_HANDOFF", "handoff kind is invalid")
|
|
@@ -3102,7 +3113,11 @@ def handoff(
|
|
|
3102
3113
|
repository = inventory(repository_path)
|
|
3103
3114
|
snapshot = store._load_unlocked()
|
|
3104
3115
|
item, record = _managed_record(repository, snapshot, target)
|
|
3105
|
-
_cas(record, expected_record_version)
|
|
3116
|
+
_cas(record, expected_record_version, expected_allocation_id)
|
|
3117
|
+
if expected_owner is not None and record.owner != expected_owner:
|
|
3118
|
+
raise ControllerError("OWNER_MISMATCH", "handoff owner does not match")
|
|
3119
|
+
if require_no_writer_lease and record.allocation_id in snapshot.writer_leases:
|
|
3120
|
+
raise ControllerError("WRITER_LEASE_PRESENT", "end the exact writer before task handoff")
|
|
3106
3121
|
if record.lifecycle in {"QUARANTINED", "INTEGRATING", "PUBLISHED", "RELEASED"}:
|
|
3107
3122
|
raise ControllerError(
|
|
3108
3123
|
"INVALID_LIFECYCLE", f"lifecycle {record.lifecycle} cannot be handed off"
|
|
@@ -8219,6 +8234,7 @@ def _release_registered_target(
|
|
|
8219
8234
|
publication_receipt_id: str | None = None,
|
|
8220
8235
|
release_intent_id: str | None = None,
|
|
8221
8236
|
expected_governance_plan_id: str | None = None,
|
|
8237
|
+
expected_allocation_id: str | None = None,
|
|
8222
8238
|
) -> dict[str, Any]:
|
|
8223
8239
|
repository_initial = inventory(repository_path)
|
|
8224
8240
|
store = MetadataStore(repository_initial.common_dir)
|
|
@@ -8231,6 +8247,8 @@ def _release_registered_target(
|
|
|
8231
8247
|
repository = inventory(repository_path)
|
|
8232
8248
|
snapshot = store._load_unlocked()
|
|
8233
8249
|
item, record = _managed_record(repository, snapshot, target)
|
|
8250
|
+
if expected_allocation_id is not None and record.allocation_id != expected_allocation_id:
|
|
8251
|
+
raise ControllerError("IDENTITY_MISMATCH", "release allocation differs from the selected task")
|
|
8234
8252
|
target_path = str(item.path)
|
|
8235
8253
|
publication_receipt = (
|
|
8236
8254
|
snapshot.publication_receipts.get(publication_receipt_id)
|
|
@@ -8927,6 +8945,7 @@ def release(
|
|
|
8927
8945
|
publication_receipt_id: str | None = None,
|
|
8928
8946
|
release_intent_id: str | None = None,
|
|
8929
8947
|
expected_governance_plan_id: str | None = None,
|
|
8948
|
+
expected_allocation_id: str | None = None,
|
|
8930
8949
|
) -> dict[str, Any]:
|
|
8931
8950
|
target_path = str(target.expanduser().resolve(strict=False))
|
|
8932
8951
|
repository = inventory(repository_path)
|
|
@@ -8947,6 +8966,7 @@ def release(
|
|
|
8947
8966
|
publication_receipt_id=publication_receipt_id,
|
|
8948
8967
|
release_intent_id=release_intent_id,
|
|
8949
8968
|
expected_governance_plan_id=expected_governance_plan_id,
|
|
8969
|
+
expected_allocation_id=expected_allocation_id,
|
|
8950
8970
|
)
|
|
8951
8971
|
except ControllerError as error:
|
|
8952
8972
|
if error.code not in {"INVALID_TARGET", "UNREGISTERED_WORKTREE"}:
|
|
@@ -8965,6 +8985,8 @@ def release(
|
|
|
8965
8985
|
}
|
|
8966
8986
|
return refreshed
|
|
8967
8987
|
return payload
|
|
8988
|
+
if expected_allocation_id is not None:
|
|
8989
|
+
raise ControllerError("TASK_CHECKOUT_MISSING", "selected task checkout is absent; inspect its original lifecycle history")
|
|
8968
8990
|
if expected_governance_plan_id is not None:
|
|
8969
8991
|
raise ControllerError("GC_PLAN_DRIFT", "GC target is no longer registered")
|
|
8970
8992
|
payload = _recover_or_replay_removed_release(
|
|
@@ -194,6 +194,13 @@ def recovery_guide(repository_path: Path, *, target: Path | None = None,
|
|
|
194
194
|
str(item["record_version"]), "--dry-run", "--json"])
|
|
195
195
|
step("retry_original_intent", "After complete release, select the original intent with "
|
|
196
196
|
"recovery-guide --intent-id; retry requires its public plan and fresh admission")
|
|
197
|
+
elif lifecycle == "NO_CHANGE_COMPLETED":
|
|
198
|
+
decision["reason"] = "No-change completion is recorded; checkout release still has its own guards"
|
|
199
|
+
step("inspect_release", "Do not create another completion receipt; inspect release guards "
|
|
200
|
+
"and preserve unclassified content before cleanup",
|
|
201
|
+
["worktree-controller", "release", "--repo", canonical, "--path", item["path"],
|
|
202
|
+
"--owner-release", item["owner"], "--expected-record-version",
|
|
203
|
+
str(item["record_version"]), "--dry-run", "--json"])
|
|
197
204
|
elif lifecycle == "HANDOFF_READY":
|
|
198
205
|
decision["reason"] = "A frozen handoff needs exact Finalizer identity before session closeout"
|
|
199
206
|
step("check_session", "Run worktree-session-end --check with the real client/session/holder/run "
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/rendering.py
RENAMED
|
@@ -8,6 +8,8 @@ from typing import Any
|
|
|
8
8
|
|
|
9
9
|
def render(payload: dict[str, Any]) -> str:
|
|
10
10
|
command = payload.get("command")
|
|
11
|
+
if command == "task":
|
|
12
|
+
return _render_task(payload)
|
|
11
13
|
repository = payload.get("repository") or {}
|
|
12
14
|
lines: list[str] = []
|
|
13
15
|
if repository:
|
|
@@ -177,3 +179,43 @@ def render(payload: dict[str, Any]) -> str:
|
|
|
177
179
|
for error in errors:
|
|
178
180
|
lines.append(f" {error.get('code')}: {error.get('message')}")
|
|
179
181
|
return "\n".join(lines)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _render_task(payload: dict[str, Any]) -> str:
|
|
185
|
+
decision = payload.get("decision") or {}
|
|
186
|
+
lines = [f"Repository: {(payload.get('repository') or {}).get('canonical_path', 'unknown')}",
|
|
187
|
+
f"{decision.get('code', 'ERROR')}: {decision.get('reason', '')}"]
|
|
188
|
+
for row in decision.get("tasks", []):
|
|
189
|
+
lines.append(f" {row['task_key'] or 'unmanaged'} | owner={row['owner'] or 'unknown'} | "
|
|
190
|
+
f"{row['lifecycle'] or 'unknown'} | writer={row['writer']} | "
|
|
191
|
+
f"{'clean' if row['clean'] else 'dirty'} | {row['path']}")
|
|
192
|
+
if row.get("waiting_on"):
|
|
193
|
+
lines.append(f" Waiting on: {row['waiting_on']}")
|
|
194
|
+
guidance = decision.get("guidance") or {}
|
|
195
|
+
task = guidance.get("identity") or decision.get("task")
|
|
196
|
+
if task:
|
|
197
|
+
lines.append(f"Task: {task['task_key']} | owner={task['owner']} | {task['lifecycle']}")
|
|
198
|
+
lines.append(f"Checkout: {task['path']}")
|
|
199
|
+
if operation := decision.get("operation"):
|
|
200
|
+
lines.append(f"Operation: {operation['code']}")
|
|
201
|
+
if operation.get("path"):
|
|
202
|
+
lines.append(f"Checkout: {operation['path']}")
|
|
203
|
+
if guidance.get("intent"):
|
|
204
|
+
lines.append(f"Integration: {guidance['intent']['intent_id']} | {guidance['intent']['state']}")
|
|
205
|
+
for blocker in guidance.get("blockers", []):
|
|
206
|
+
lines.append(f"Blocker: {blocker}")
|
|
207
|
+
for step in (*guidance.get("next_steps", []), *decision.get("next_steps", [])):
|
|
208
|
+
lines.append(f"Next: {step['message']}")
|
|
209
|
+
if step.get("argv"):
|
|
210
|
+
lines.append(f" {shlex.join(step['argv'])}")
|
|
211
|
+
if history := decision.get("history"):
|
|
212
|
+
for row in history["closed_releases"]:
|
|
213
|
+
lines.append(f"Released: owner={row['owner']} | proof={row['proof_status']} | {row['branch']} | {row['worktree_path']}")
|
|
214
|
+
for row in history["incomplete_history"]:
|
|
215
|
+
lines.append(f"Incomplete history: {row['reason']}")
|
|
216
|
+
if history["next_after"]:
|
|
217
|
+
lines.append(f"More history: lifecycle-history --task-key (cursor {history['next_after']})")
|
|
218
|
+
for group in ("errors", "warnings"):
|
|
219
|
+
for item in payload.get(group, []):
|
|
220
|
+
lines.append(f"{item['code']}: {item['message']}")
|
|
221
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""Small task-oriented entrypoints over the existing Controller authorities.
|
|
2
|
+
|
|
3
|
+
No task database, inferred owner, writer entitlement or new lifecycle is introduced.
|
|
4
|
+
Mutations resolve one live allocation and pass its identity into the locked verb.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .controller import envelope, status
|
|
14
|
+
from .git_backend import resolve_commit
|
|
15
|
+
from .lifecycle import acquire, complete_no_change, handoff, park, release, resume
|
|
16
|
+
from .lifecycle_history import lifecycle_history
|
|
17
|
+
from .models import ControllerError
|
|
18
|
+
from .policy import load_policy
|
|
19
|
+
from .recovery_guidance import recovery_guide
|
|
20
|
+
|
|
21
|
+
CONTRACT = "task-workflow/v1"
|
|
22
|
+
ACTIONS = ("list", "show", "start", "resume", "finish", "recover")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def add_parser(commands: Any, common: argparse.ArgumentParser) -> None:
|
|
26
|
+
task = commands.add_parser("task", help="start, inspect, resume and settle a named task")
|
|
27
|
+
actions = task.add_subparsers(dest="task_action", required=True)
|
|
28
|
+
for action in ACTIONS:
|
|
29
|
+
parser = actions.add_parser(action, parents=[common])
|
|
30
|
+
if action == "list":
|
|
31
|
+
parser.add_argument("--owner", help="show only this recorded owner")
|
|
32
|
+
continue
|
|
33
|
+
if action == "start":
|
|
34
|
+
parser.add_argument("--task-key", required=True)
|
|
35
|
+
parser.add_argument("--owner", required=True)
|
|
36
|
+
parser.add_argument("--scope", action="append", required=True)
|
|
37
|
+
parser.add_argument("--authority-key", help="defaults to the explicit task key")
|
|
38
|
+
parser.add_argument("--module", default="task")
|
|
39
|
+
parser.add_argument("--branch", help="defaults to task/<task-key>")
|
|
40
|
+
parser.add_argument("--base-ref", help="defaults to policy publication remote/branch")
|
|
41
|
+
parser.add_argument("--path", type=Path)
|
|
42
|
+
continue
|
|
43
|
+
selector = parser.add_mutually_exclusive_group(required=True)
|
|
44
|
+
selector.add_argument("--task-key")
|
|
45
|
+
selector.add_argument("--path", type=Path)
|
|
46
|
+
if action in {"show", "recover"}:
|
|
47
|
+
selector.add_argument("--intent-id", help="original integration identity, also after release")
|
|
48
|
+
else:
|
|
49
|
+
parser.add_argument("--owner", required=True)
|
|
50
|
+
if action == "finish":
|
|
51
|
+
parser.add_argument("--outcome", choices=("no-change", "checkpoint", "handoff"), required=True)
|
|
52
|
+
parser.add_argument("--checkpoint-id")
|
|
53
|
+
parser.add_argument("--checkpoint-head", help="HEAD bound by the existing Task checkpoint")
|
|
54
|
+
parser.add_argument("--waiting-on")
|
|
55
|
+
parser.add_argument("--finalizer-record-id")
|
|
56
|
+
parser.add_argument("--commit", help="exact commit bound by the Finalizer record")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _row(observed: dict[str, Any], *, task_key: str | None, target: Path | None) -> dict[str, Any]:
|
|
60
|
+
if (task_key is None) == (target is None):
|
|
61
|
+
raise ControllerError("INVALID_SELECTOR", "select exactly one task key or checkout path")
|
|
62
|
+
resolved = str(target.expanduser().resolve(strict=False)) if target is not None else None
|
|
63
|
+
matches = [row for row in observed["worktrees"]
|
|
64
|
+
if (row["task_key"] == task_key if task_key is not None else row["path"] == resolved)]
|
|
65
|
+
if not matches:
|
|
66
|
+
raise ControllerError("UNKNOWN_TASK", "no live allocation matches; use task show for retained history")
|
|
67
|
+
if len(matches) != 1:
|
|
68
|
+
raise ControllerError("AMBIGUOUS_TASK", "multiple live allocations share this task key; select --path explicitly")
|
|
69
|
+
row = matches[0]
|
|
70
|
+
if row["metadata_missing"]:
|
|
71
|
+
raise ControllerError("MISSING_METADATA", "checkout has no recorded task authority")
|
|
72
|
+
return row
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _identity(row: dict[str, Any]) -> dict[str, Any]:
|
|
76
|
+
return {field: row[field] for field in (
|
|
77
|
+
"allocation_id", "task_key", "authority_key", "owner", "path", "branch", "head",
|
|
78
|
+
"record_version", "lifecycle", "role", "scope_paths", "wait_checkpoint",
|
|
79
|
+
)}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _response(observed: dict[str, Any], action: str, code: str, reason: str, **extra: Any) -> dict[str, Any]:
|
|
83
|
+
payload = envelope("task")
|
|
84
|
+
payload["repository"] = observed["repository"]
|
|
85
|
+
payload["warnings"] = observed["warnings"]
|
|
86
|
+
payload["decision"] = {
|
|
87
|
+
"code": code, "contract": CONTRACT, "action": action, "reason": reason,
|
|
88
|
+
"business_task_completed": False, **extra,
|
|
89
|
+
}
|
|
90
|
+
return payload
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def inspect_task(repository_path: Path, *, task_key: str | None = None,
|
|
94
|
+
target: Path | None = None, intent_id: str | None = None) -> dict[str, Any]:
|
|
95
|
+
"""Keep original recovery commands/evidence; never turn observation into success."""
|
|
96
|
+
if sum(value is not None for value in (task_key, target, intent_id)) != 1:
|
|
97
|
+
raise ControllerError("INVALID_SELECTOR", "select exactly one task, path or original intent")
|
|
98
|
+
observed = status(repository_path)
|
|
99
|
+
if intent_id is not None:
|
|
100
|
+
guide = recovery_guide(repository_path, intent_id=intent_id)
|
|
101
|
+
else:
|
|
102
|
+
try:
|
|
103
|
+
row = _row(observed, task_key=task_key, target=target)
|
|
104
|
+
except ControllerError as error:
|
|
105
|
+
if error.code != "UNKNOWN_TASK" or task_key is None:
|
|
106
|
+
raise
|
|
107
|
+
history = lifecycle_history(repository_path, task_key=task_key)
|
|
108
|
+
decision = history["decision"]
|
|
109
|
+
if not (decision["closed_releases"] or decision["incomplete_history"]):
|
|
110
|
+
raise error
|
|
111
|
+
return _response(observed, "show", "TASK_HISTORY", "No live checkout; inspect the original release evidence",
|
|
112
|
+
mutation_performed=False, history=decision)
|
|
113
|
+
guide = recovery_guide(repository_path, target=Path(row["path"]))
|
|
114
|
+
return _response(observed, "show", "TASK_GUIDANCE", guide["decision"]["reason"],
|
|
115
|
+
mutation_performed=False, guidance=guide["decision"])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def start_task(repository_path: Path, *, task_key: str, owner: str, scope_paths: tuple[str, ...],
|
|
119
|
+
authority_key: str | None = None, module: str = "task", branch: str | None = None,
|
|
120
|
+
base_ref: str | None = None, target: Path | None = None) -> dict[str, Any]:
|
|
121
|
+
if not scope_paths or any(not value.strip() for value in scope_paths):
|
|
122
|
+
raise ControllerError("MISSING_TASK_SCOPE", "declare the task's repository-relative file scope")
|
|
123
|
+
observed = status(repository_path)
|
|
124
|
+
if any(row["task_key"] == task_key for row in observed["worktrees"]):
|
|
125
|
+
raise ControllerError("TASK_ALREADY_EXISTS", "inspect or resume the existing task; no second checkout was requested")
|
|
126
|
+
policy = load_policy(Path(observed["repository"]["canonical_path"]))
|
|
127
|
+
base = f"{policy.publication_remote}/{policy.publication_branch}" if base_ref is None else base_ref
|
|
128
|
+
if base_ref is None:
|
|
129
|
+
try:
|
|
130
|
+
resolve_commit(Path(observed["repository"]["canonical_path"]), base)
|
|
131
|
+
except ControllerError as error:
|
|
132
|
+
if error.code != "INVALID_COMMIT":
|
|
133
|
+
raise
|
|
134
|
+
raise ControllerError("TASK_BASE_UNAVAILABLE", f"policy base {base} is unavailable locally; "
|
|
135
|
+
"fetch through your Git workflow or select --base-ref explicitly") from error
|
|
136
|
+
operation = acquire(repository_path, task_key=task_key,
|
|
137
|
+
authority_key=task_key if authority_key is None else authority_key,
|
|
138
|
+
module=module, role="feature", owner=owner,
|
|
139
|
+
branch=f"task/{task_key}" if branch is None else branch,
|
|
140
|
+
base_ref=base,
|
|
141
|
+
target=target, scope_paths=scope_paths)
|
|
142
|
+
complete = operation["decision"]["code"] == "ACQUIRED"
|
|
143
|
+
return _response(operation, "start", "TASK_STARTED" if complete else "TASK_WAITING",
|
|
144
|
+
"Checkout admitted; obtain a live writer lease before editing" if complete else
|
|
145
|
+
"Admission did not create a checkout; keep the original request and inspect its decision",
|
|
146
|
+
operation_complete=complete, operation=operation["decision"],
|
|
147
|
+
writer_entitlement_granted=False)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _owned(repository_path: Path, task_key: str | None, target: Path | None,
|
|
151
|
+
owner: str) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
152
|
+
observed = status(repository_path)
|
|
153
|
+
row = _row(observed, task_key=task_key, target=target)
|
|
154
|
+
if row["owner"] != owner:
|
|
155
|
+
raise ControllerError("OWNER_MISMATCH", "task owner does not match the recorded owner")
|
|
156
|
+
if row["canonical"] or row["role"] not in {"feature", "audit"}:
|
|
157
|
+
raise ControllerError("INVALID_ROLE", "this entry settles only feature/audit tasks; inspect recovery guidance for other roles")
|
|
158
|
+
return observed, row
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def resume_task(repository_path: Path, *, owner: str, task_key: str | None = None,
|
|
162
|
+
target: Path | None = None) -> dict[str, Any]:
|
|
163
|
+
_observed, row = _owned(repository_path, task_key, target, owner)
|
|
164
|
+
operation = resume(repository_path, target=Path(row["path"]), owner=owner,
|
|
165
|
+
expected_record_version=row["record_version"],
|
|
166
|
+
expected_allocation_id=row["allocation_id"], require_no_writer_lease=True)
|
|
167
|
+
return _response(operation, "resume", "TASK_RESUMED", "Original checkout resumed; obtain a live writer lease before editing",
|
|
168
|
+
task=_identity(_row(operation, task_key=None, target=Path(row["path"]))),
|
|
169
|
+
operation_complete=True, operation=operation["decision"],
|
|
170
|
+
writer_entitlement_granted=False)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def finish_task(repository_path: Path, *, owner: str, outcome: str, task_key: str | None = None,
|
|
174
|
+
target: Path | None = None, checkpoint_id: str | None = None,
|
|
175
|
+
checkpoint_head: str | None = None, waiting_on: str | None = None,
|
|
176
|
+
finalizer_record_id: str | None = None, commit: str | None = None) -> dict[str, Any]:
|
|
177
|
+
if outcome not in {"no-change", "checkpoint", "handoff"}:
|
|
178
|
+
raise ControllerError("INVALID_OUTCOME", "select no-change, checkpoint or handoff explicitly")
|
|
179
|
+
if (outcome != "checkpoint" and any(value is not None for value in (checkpoint_id, checkpoint_head, waiting_on))
|
|
180
|
+
or outcome != "handoff" and any(value is not None for value in (finalizer_record_id, commit))):
|
|
181
|
+
raise ControllerError("INVALID_OUTCOME", "evidence options must belong to the selected outcome")
|
|
182
|
+
if outcome == "checkpoint" and (not checkpoint_id or not checkpoint_head):
|
|
183
|
+
raise ControllerError("INVALID_CHECKPOINT", "save the existing Task checkpoint first and provide its ID and bound HEAD")
|
|
184
|
+
if outcome == "handoff" and (not finalizer_record_id or not commit):
|
|
185
|
+
raise ControllerError("MISSING_FINALIZER_RECORD", "provide the actual Finalizer record and its exact commit")
|
|
186
|
+
observed, row = _owned(repository_path, task_key, target, owner)
|
|
187
|
+
arguments = dict(target=Path(row["path"]), expected_record_version=row["record_version"],
|
|
188
|
+
expected_allocation_id=row["allocation_id"], require_no_writer_lease=True)
|
|
189
|
+
if outcome == "checkpoint":
|
|
190
|
+
operation = park(repository_path, owner=owner, checkpoint_id=checkpoint_id,
|
|
191
|
+
checkpoint_head=checkpoint_head, waiting_on=waiting_on, **arguments)
|
|
192
|
+
return _response(operation, "finish", "TASK_CHECKPOINT_RETAINED",
|
|
193
|
+
"Checkpoint retained with its original checkout and branch; work remains unfinished",
|
|
194
|
+
operation_complete=True, operation=operation["decision"],
|
|
195
|
+
task=_identity(_row(operation, task_key=None, target=Path(row["path"]))), checkout_released=False)
|
|
196
|
+
if outcome == "handoff":
|
|
197
|
+
operation = handoff(repository_path, kind="integration-ready", commit=commit,
|
|
198
|
+
finalizer_commit=commit, finalizer_record_id=finalizer_record_id,
|
|
199
|
+
expected_owner=owner, **arguments)
|
|
200
|
+
return _response(operation, "finish", "TASK_HANDED_OFF",
|
|
201
|
+
"Exact commit handed off; integration and publication are still separate steps",
|
|
202
|
+
operation_complete=True, operation=operation["decision"],
|
|
203
|
+
task=_identity(_row(operation, task_key=None, target=Path(row["path"]))), checkout_released=False)
|
|
204
|
+
operations = []
|
|
205
|
+
if row["lifecycle"] != "NO_CHANGE_COMPLETED":
|
|
206
|
+
operation = complete_no_change(repository_path, owner=owner, **arguments)
|
|
207
|
+
operations.append(operation["decision"])
|
|
208
|
+
row = {**row, "record_version": operation["decision"]["record_version"],
|
|
209
|
+
"lifecycle": "NO_CHANGE_COMPLETED"}
|
|
210
|
+
try:
|
|
211
|
+
operation = release(repository_path, target=Path(row["path"]), owner_release=owner,
|
|
212
|
+
expected_record_version=row["record_version"],
|
|
213
|
+
expected_allocation_id=row["allocation_id"])
|
|
214
|
+
except (ControllerError, OSError) as error:
|
|
215
|
+
# Completion and release have distinct durable boundaries. Preserve and
|
|
216
|
+
# report the completed prefix; a cleanup refusal must never read as PASS.
|
|
217
|
+
code = getattr(error, "code", "FILESYSTEM_ERROR")
|
|
218
|
+
next_step = {
|
|
219
|
+
"message": "Inspect release guards; preserve unknown content before cleanup",
|
|
220
|
+
"argv": ["worktree-controller", "release", "--repo",
|
|
221
|
+
observed["repository"]["canonical_path"], "--path", row["path"],
|
|
222
|
+
"--owner-release", owner, "--expected-record-version",
|
|
223
|
+
str(row["record_version"]), "--dry-run", "--json"],
|
|
224
|
+
}
|
|
225
|
+
if code in {"IDENTITY_MISMATCH", "TASK_CHECKOUT_MISSING", "CAS_MISMATCH", "BINDING_IDENTITY_DRIFT"}:
|
|
226
|
+
next_step = {
|
|
227
|
+
"message": "Inspect the original allocation; do not act on a replacement checkout",
|
|
228
|
+
"argv": ["worktree-controller", "lifecycle-history", "--repo",
|
|
229
|
+
observed["repository"]["canonical_path"], "--allocation-id",
|
|
230
|
+
row["allocation_id"], "--json"],
|
|
231
|
+
}
|
|
232
|
+
payload = _response(observed, "finish", "TASK_RELEASE_BLOCKED",
|
|
233
|
+
"No-change completion recorded; checkout release still needs owner action",
|
|
234
|
+
operation_complete=False, operations=operations, task=_identity(row),
|
|
235
|
+
checkout_released=False, next_steps=[next_step])
|
|
236
|
+
payload["errors"] = [{"code": code, "message": str(error)}]
|
|
237
|
+
return payload
|
|
238
|
+
operations.append(operation["decision"])
|
|
239
|
+
return _response(operation, "finish", "TASK_CHECKOUT_RELEASED", "No-change completion recorded and checkout released; branch retained",
|
|
240
|
+
operation_complete=True, operations=operations, task=_identity(row),
|
|
241
|
+
checkout_released=True)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def dispatch(parsed: argparse.Namespace) -> dict[str, Any]:
|
|
245
|
+
action = parsed.task_action
|
|
246
|
+
if action == "list":
|
|
247
|
+
observed = status(parsed.repo)
|
|
248
|
+
rows = [row for row in observed["worktrees"] if not row["canonical"]
|
|
249
|
+
and (parsed.owner is None or row["owner"] == parsed.owner)]
|
|
250
|
+
return _response(observed, action, "TASK_LIST", "Live checkouts only; clean or idle does not mean completed",
|
|
251
|
+
mutation_performed=False, tasks=[{
|
|
252
|
+
**{key: row[key] for key in ("task_key", "owner", "path", "lifecycle", "clean", "metadata_missing")},
|
|
253
|
+
"writer": (row.get("lifecycle_observation") or {}).get("lease", {}).get("status", "UNKNOWN"),
|
|
254
|
+
"waiting_on": (row.get("wait_checkpoint") or {}).get("waiting_on"),
|
|
255
|
+
} for row in rows])
|
|
256
|
+
if action == "start":
|
|
257
|
+
return start_task(parsed.repo, task_key=parsed.task_key, owner=parsed.owner,
|
|
258
|
+
scope_paths=tuple(parsed.scope), authority_key=parsed.authority_key,
|
|
259
|
+
module=parsed.module, branch=parsed.branch, base_ref=parsed.base_ref, target=parsed.path)
|
|
260
|
+
selector = {"task_key": parsed.task_key, "target": parsed.path}
|
|
261
|
+
if action in {"show", "recover"}:
|
|
262
|
+
result = inspect_task(parsed.repo, **selector, intent_id=parsed.intent_id)
|
|
263
|
+
result["decision"]["action"] = action
|
|
264
|
+
return result
|
|
265
|
+
if action == "resume":
|
|
266
|
+
return resume_task(parsed.repo, owner=parsed.owner, **selector)
|
|
267
|
+
return finish_task(parsed.repo, owner=parsed.owner, outcome=parsed.outcome, **selector,
|
|
268
|
+
checkpoint_id=parsed.checkpoint_id, checkpoint_head=parsed.checkpoint_head,
|
|
269
|
+
waiting_on=parsed.waiting_on, finalizer_record_id=parsed.finalizer_record_id, commit=parsed.commit)
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"commands": [
|
|
3
|
+
{
|
|
4
|
+
"name": "task",
|
|
5
|
+
"operation_class": "git_worktree_mutation",
|
|
6
|
+
"result_contract": "worktree-controller/v1"
|
|
7
|
+
},
|
|
3
8
|
{
|
|
4
9
|
"name": "acquire",
|
|
5
10
|
"operation_class": "git_worktree_mutation",
|
|
@@ -404,7 +409,8 @@
|
|
|
404
409
|
"branch_retirement_version": 1,
|
|
405
410
|
"branch_retirement_verify_version": 1,
|
|
406
411
|
"cleanup_hardlink_topology_version": 2,
|
|
407
|
-
"contract_version":
|
|
412
|
+
"contract_version": 61,
|
|
413
|
+
"task_workflow_version": 1,
|
|
408
414
|
"release_recovery_inspection_version": 1,
|
|
409
415
|
"release_recovery_inspection_contract": {
|
|
410
416
|
"contract": "release-recovery-inspection/v1",
|
|
@@ -1065,7 +1071,7 @@
|
|
|
1065
1071
|
"session_launcher_entrypoint": "worktree-session",
|
|
1066
1072
|
"session_entrypoint": "worktree-session-end",
|
|
1067
1073
|
"tool_name": "worktree-controller",
|
|
1068
|
-
"tool_version": "1.
|
|
1074
|
+
"tool_version": "1.31.0",
|
|
1069
1075
|
"delivered_closeout_version": 1,
|
|
1070
1076
|
"retained_archive_version": 1,
|
|
1071
1077
|
"retention_convergence_version": 2,
|
|
@@ -1787,5 +1793,34 @@
|
|
|
1787
1793
|
"retention": "history records the checkpoint reference; status exposes wait_checkpoint while RETAINED",
|
|
1788
1794
|
"release": "explicit reviewed archive-retained plan then unchanged guarded release; branches kept, unknown files block",
|
|
1789
1795
|
"resume": "the Task restores the exact retained HEAD through controlled admission and revalidates context before a new writer"
|
|
1796
|
+
},
|
|
1797
|
+
"task_workflow_contract": {
|
|
1798
|
+
"contract": "task-workflow/v1",
|
|
1799
|
+
"version": 1,
|
|
1800
|
+
"actions": [
|
|
1801
|
+
"list",
|
|
1802
|
+
"show",
|
|
1803
|
+
"start",
|
|
1804
|
+
"resume",
|
|
1805
|
+
"finish",
|
|
1806
|
+
"recover"
|
|
1807
|
+
],
|
|
1808
|
+
"read_only": [
|
|
1809
|
+
"list",
|
|
1810
|
+
"show",
|
|
1811
|
+
"recover"
|
|
1812
|
+
],
|
|
1813
|
+
"selectors": "One exact task key or checkout path; show/recover also accept the original integration intent. Ambiguous live task keys fail closed.",
|
|
1814
|
+
"start": "Feature allocation only; explicit owner and scope. Defaults: authority=task key, module=task, branch=task/<task-key>, base=policy publication remote/branch. No writer lease is granted.",
|
|
1815
|
+
"resume": "Original retained allocation only, exact owner/allocation/record CAS and no writer lease; does not grant write entitlement.",
|
|
1816
|
+
"finish": {
|
|
1817
|
+
"no-change": "Complete no-change then guarded release; retain branch. Partial release failures preserve completion evidence and exit 2.",
|
|
1818
|
+
"checkpoint": "Require existing Task checkpoint ID and explicitly bound HEAD; park with no writer lease, preserve files and checkout.",
|
|
1819
|
+
"handoff": "Require actual Finalizer record and exact commit; freeze handoff with exact owner/allocation/CAS and no writer lease. Integration/publication remain separate."
|
|
1820
|
+
},
|
|
1821
|
+
"recovery": "Read-only forwarding of existing recovery-guidance/v1; execute its formal steps separately and reobserve after every transition.",
|
|
1822
|
+
"exit": "0 for successful observation or completed requested operation; 2 for refusal, RESOURCE_WAIT or incomplete checkout release. Observation exit 0 never proves task completion.",
|
|
1823
|
+
"storage": "No additional state, lifecycle states or database; storage layout remains 5.",
|
|
1824
|
+
"compatibility": "CLI 61 adds task-workflow/v1; existing low-level commands and contracts remain compatible."
|
|
1790
1825
|
}
|
|
1791
1826
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"commands": [
|
|
3
|
+
{
|
|
4
|
+
"name": "task",
|
|
5
|
+
"operation_class": "git_worktree_mutation",
|
|
6
|
+
"result_contract": "worktree-controller/v1"
|
|
7
|
+
},
|
|
3
8
|
{
|
|
4
9
|
"name": "acquire",
|
|
5
10
|
"operation_class": "git_worktree_mutation",
|
|
@@ -404,7 +409,8 @@
|
|
|
404
409
|
"branch_retirement_version": 1,
|
|
405
410
|
"branch_retirement_verify_version": 1,
|
|
406
411
|
"cleanup_hardlink_topology_version": 2,
|
|
407
|
-
"contract_version":
|
|
412
|
+
"contract_version": 61,
|
|
413
|
+
"task_workflow_version": 1,
|
|
408
414
|
"release_recovery_inspection_version": 1,
|
|
409
415
|
"release_recovery_inspection_contract": {
|
|
410
416
|
"contract": "release-recovery-inspection/v1",
|
|
@@ -1065,7 +1071,7 @@
|
|
|
1065
1071
|
"session_launcher_entrypoint": "worktree-session",
|
|
1066
1072
|
"session_entrypoint": "worktree-session-end",
|
|
1067
1073
|
"tool_name": "worktree-controller",
|
|
1068
|
-
"tool_version": "1.
|
|
1074
|
+
"tool_version": "1.31.0",
|
|
1069
1075
|
"delivered_closeout_version": 1,
|
|
1070
1076
|
"retained_archive_version": 1,
|
|
1071
1077
|
"retention_convergence_version": 2,
|
|
@@ -1787,5 +1793,34 @@
|
|
|
1787
1793
|
"retention": "history records the checkpoint reference; status exposes wait_checkpoint while RETAINED",
|
|
1788
1794
|
"release": "explicit reviewed archive-retained plan then unchanged guarded release; branches kept, unknown files block",
|
|
1789
1795
|
"resume": "the Task restores the exact retained HEAD through controlled admission and revalidates context before a new writer"
|
|
1796
|
+
},
|
|
1797
|
+
"task_workflow_contract": {
|
|
1798
|
+
"contract": "task-workflow/v1",
|
|
1799
|
+
"version": 1,
|
|
1800
|
+
"actions": [
|
|
1801
|
+
"list",
|
|
1802
|
+
"show",
|
|
1803
|
+
"start",
|
|
1804
|
+
"resume",
|
|
1805
|
+
"finish",
|
|
1806
|
+
"recover"
|
|
1807
|
+
],
|
|
1808
|
+
"read_only": [
|
|
1809
|
+
"list",
|
|
1810
|
+
"show",
|
|
1811
|
+
"recover"
|
|
1812
|
+
],
|
|
1813
|
+
"selectors": "One exact task key or checkout path; show/recover also accept the original integration intent. Ambiguous live task keys fail closed.",
|
|
1814
|
+
"start": "Feature allocation only; explicit owner and scope. Defaults: authority=task key, module=task, branch=task/<task-key>, base=policy publication remote/branch. No writer lease is granted.",
|
|
1815
|
+
"resume": "Original retained allocation only, exact owner/allocation/record CAS and no writer lease; does not grant write entitlement.",
|
|
1816
|
+
"finish": {
|
|
1817
|
+
"no-change": "Complete no-change then guarded release; retain branch. Partial release failures preserve completion evidence and exit 2.",
|
|
1818
|
+
"checkpoint": "Require existing Task checkpoint ID and explicitly bound HEAD; park with no writer lease, preserve files and checkout.",
|
|
1819
|
+
"handoff": "Require actual Finalizer record and exact commit; freeze handoff with exact owner/allocation/CAS and no writer lease. Integration/publication remain separate."
|
|
1820
|
+
},
|
|
1821
|
+
"recovery": "Read-only forwarding of existing recovery-guidance/v1; execute its formal steps separately and reobserve after every transition.",
|
|
1822
|
+
"exit": "0 for successful observation or completed requested operation; 2 for refusal, RESOURCE_WAIT or incomplete checkout release. Observation exit 0 never proves task completion.",
|
|
1823
|
+
"storage": "No additional state, lifecycle states or database; storage layout remains 5.",
|
|
1824
|
+
"compatibility": "CLI 61 adds task-workflow/v1; existing low-level commands and contracts remain compatible."
|
|
1790
1825
|
}
|
|
1791
1826
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/controller.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/convergence.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/disposable.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/environment.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/frozen_recovery.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/git_backend.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/governance.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/hardlinks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/metadata_codec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/owner_actions.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/preservation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/reconciler.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/semantics.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/session.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/source_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/source_recovery.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/writer_lease.py
RENAMED
|
File without changes
|
{worktree_controller-1.30.6 → worktree_controller-1.31.0}/src/worktree_controller/writer_session.py
RENAMED
|
File without changes
|