worktree-controller 1.32.2__tar.gz → 1.32.5__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.32.2 → worktree_controller-1.32.5}/PKG-INFO +15 -5
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/README.md +14 -4
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/architecture.md +11 -0
- worktree_controller-1.32.5/docs/integration-boundaries.md +68 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/publishing.md +13 -0
- worktree_controller-1.32.5/docs/roadmap.md +51 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/scope.md +16 -10
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/pyproject.toml +1 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/pyproject.toml.orig +1 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/__init__.py +1 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/cli.py +1 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/convergence.py +3 -3
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/delivered_closeout.py +120 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/integration_retry.py +5 -4
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/lifecycle.py +23 -97
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/preserved_cleanup.py +5 -0
- worktree_controller-1.32.5/src/worktree_controller/release_intents.py +57 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/semantics.py +49 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/tool_cli_contract.json +9 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/tool_cli_contract.json +9 -1
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/LICENSE +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/client-integration.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/daily-workflow.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/first-change.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/getting-started.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/operations.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/docs/reference.md +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/examples/change_task.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/examples/client_continuation.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/examples/quickstart.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/activation_layout.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/branch_retirement.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/canonical_refresh.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/canonical_transition.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/cleanup_attempts.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/cleanup_evidence.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/cleanup_recovery.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/controller.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/disposable.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/environment.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/frozen_recovery.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/git_backend.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/governance.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/hardlinks.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/integration_cleanup.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/integration_conflict.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/keyed_document_scope.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/lifecycle_history.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/lifecycle_observation.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/metadata_codec.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/metadata_relocation.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/models.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/mount_diagnostics.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/multiple_cleanup_recovery.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/namespace_migration.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/native_session_recovery.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/owner_actions.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/policy.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/preservation.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/publication_execution.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/reconciler.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/record_updates.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/recovery_guidance.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/registration_backfill.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/registration_prune.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/rendering.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/resource_admission.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/retirement_adapters.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/retirement_execution.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/session.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/session_admission.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/source_cleanup.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/source_preservation.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/source_recovery.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/store.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/task_workflow.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/transaction_scope.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/writer_lease.py +0 -0
- {worktree_controller-1.32.2 → worktree_controller-1.32.5}/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.32.
|
|
3
|
+
Version: 1.32.5
|
|
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.32.
|
|
26
|
+
**Current release: 1.32.5 · 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.32.
|
|
35
|
+
python -m pip install worktree-controller==1.32.5
|
|
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.32.
|
|
44
|
+
Software version 1.32.5 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.32.
|
|
53
|
+
git checkout v1.32.5
|
|
54
54
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -114,3 +114,13 @@ a successful process exit alone does not prove that all worktrees are settled.
|
|
|
114
114
|
- [Report a security issue](https://github.com/xuanheng-tech/worktree-controller/blob/main/SECURITY.md)
|
|
115
115
|
|
|
116
116
|
Licensed under the [Apache License 2.0](https://github.com/xuanheng-tech/worktree-controller/blob/main/LICENSE).
|
|
117
|
+
|
|
118
|
+
With `terminal_preserved_cleanup_version` 1, published integrations and completed
|
|
119
|
+
no-change feature/audit tasks may use the existing preservation and `resource-clean --typed`
|
|
120
|
+
flow. Require one exact remote-verified, lease-cleared publication or one verified original
|
|
121
|
+
no-change completion. Every removed tree needs immutable preservation coverage.
|
|
122
|
+
Plans pin the current verified proof baseline; cleanup and release recheck it without
|
|
123
|
+
rewriting history, reopening lifecycle or relaxing owner/HEAD/CAS, runtime, lease, retained-ref
|
|
124
|
+
and transaction guards. A newly observed legacy digest is not historical hash evidence.
|
|
125
|
+
New-format no-change completions may clean afterward only when their frozen prior cleanup
|
|
126
|
+
set is empty; existing pre-completion proofs retain their original CAS.
|
|
@@ -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.32.
|
|
9
|
+
**Current release: 1.32.5 · 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.32.
|
|
18
|
+
python -m pip install worktree-controller==1.32.5
|
|
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.32.
|
|
27
|
+
Software version 1.32.5 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.32.
|
|
36
|
+
git checkout v1.32.5
|
|
37
37
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -97,3 +97,13 @@ a successful process exit alone does not prove that all worktrees are settled.
|
|
|
97
97
|
- [Report a security issue](https://github.com/xuanheng-tech/worktree-controller/blob/main/SECURITY.md)
|
|
98
98
|
|
|
99
99
|
Licensed under the [Apache License 2.0](https://github.com/xuanheng-tech/worktree-controller/blob/main/LICENSE).
|
|
100
|
+
|
|
101
|
+
With `terminal_preserved_cleanup_version` 1, published integrations and completed
|
|
102
|
+
no-change feature/audit tasks may use the existing preservation and `resource-clean --typed`
|
|
103
|
+
flow. Require one exact remote-verified, lease-cleared publication or one verified original
|
|
104
|
+
no-change completion. Every removed tree needs immutable preservation coverage.
|
|
105
|
+
Plans pin the current verified proof baseline; cleanup and release recheck it without
|
|
106
|
+
rewriting history, reopening lifecycle or relaxing owner/HEAD/CAS, runtime, lease, retained-ref
|
|
107
|
+
and transaction guards. A newly observed legacy digest is not historical hash evidence.
|
|
108
|
+
New-format no-change completions may clean afterward only when their frozen prior cleanup
|
|
109
|
+
set is empty; existing pre-completion proofs retain their original CAS.
|
|
@@ -12,6 +12,7 @@ development and recovery paths; exact public interfaces live in the [contract](.
|
|
|
12
12
|
| Git observations and mutations | `git_backend.py` | Git owns registrations, refs, paths, HEAD and cleanliness |
|
|
13
13
|
| State and durable evidence | `models.py`, `store.py`, `metadata_codec.py`, `record_updates.py` | Validate stored identities; serialize mutations under the repository lock; record updates bump their version, and both update helpers append history |
|
|
14
14
|
| Shared identity and semantic checks | `semantics.py` | Consume the caller's snapshot, including effective intent state and terminal reconciliation; do not acquire authority or write metadata |
|
|
15
|
+
| Release-intent queries | `release_intents.py` | Select from the caller's snapshot and calculate renewal backoff using its supplied time; no metadata writes or fresh observation |
|
|
15
16
|
| Budget admission | `resource_admission.py` | Preserve durable request identity and host reservations across retries |
|
|
16
17
|
| Writer entitlement | `writer_lease.py`, `writer_session.py`, `governance.py` | Bind the real process identity and exact operation/target |
|
|
17
18
|
| Lifecycle orchestration | `lifecycle.py` and focused recovery modules | Compose existing guards and append durable transition evidence |
|
|
@@ -76,3 +77,13 @@ Further lifecycle extraction should follow one cohesive transition at a time, ke
|
|
|
76
77
|
scope and immutable evidence together. Avoid moving mutation helpers that depend on lifecycle's
|
|
77
78
|
clock, transaction state or caller-specific guards merely to reduce a line count. Public CLI,
|
|
78
79
|
capability and storage changes require their own version and compatibility review.
|
|
80
|
+
|
|
81
|
+
Release-intent selection and renewal backoff share a pure query module. Lifecycle keeps the
|
|
82
|
+
mutation helpers, clock, lock scope, cancellation and history append unchanged. Convergence
|
|
83
|
+
uses the same query directly; lifecycle retains its existing internal aliases. A changed HEAD
|
|
84
|
+
or record version still renews immediately, and repeated unchanged expiries retain the existing
|
|
85
|
+
one-day backoff cap. This extraction changes no CLI, capability or storage contract.
|
|
86
|
+
|
|
87
|
+
Scope digest and overlap queries live in `semantics` alongside scope normalization. Integration
|
|
88
|
+
retry calls these shared pure helpers directly; lifecycle retains compatibility aliases. Their
|
|
89
|
+
serialization, empty-scope interpretation and parent/child path rules remain unchanged.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Consumer and protocol boundaries
|
|
2
|
+
|
|
3
|
+
Inventory verified on 2026-10-23 against WC 1.32.3 (CLI 61, storage 5) and GF 1.9.7.
|
|
4
|
+
This is a source inventory, not an observation of every installed host or external user.
|
|
5
|
+
The public [CLI contract](../tool_cli_contract.json) remains authoritative.
|
|
6
|
+
[Scope](scope.md) records the conservative decision to keep existing commands supported.
|
|
7
|
+
|
|
8
|
+
## Existing delivery protocols
|
|
9
|
+
|
|
10
|
+
| Flow | Producer authority | Consumer responsibility | Required evidence and refusal |
|
|
11
|
+
| --- | --- | --- | --- |
|
|
12
|
+
| Integration publication | WC `integration-publication-session/v1`; repository lock spans verify, authorize, push observation and completion | GF publishes the exact authorized candidate with a non-force push and independently observes the remote | Repository/allocation, lease/run, candidate, expected main, record version and validation identity must match. Missing capability, drift or incomplete completion blocks. After uncertain transport, inspect remote facts and re-enter the same identity. |
|
|
13
|
+
| Branch retirement | WC `branch-retirement-execution/v1` and `retirement-execution-result/v1` | GF performs only the plan-authorized exact-OID ref operation | The public plan, verify, authorize and complete results bind the operation and ancestry proof. Missing refs alone do not establish a completed retirement; retain recovery identity. |
|
|
14
|
+
| Sensitive source review | GF owns its review-file schema | Treat the four WC linkage fields as opaque identities | No derivation of private authority digests or parsing WC metadata to construct authority. |
|
|
15
|
+
| Review evidence | SR owns `snapshot-runner read`, evidence-read schema 1 and stored-era validation | GF's explicit initial-publish mode consumes public fields | Unknown schema, partial output, gaps or identity mismatch blocks; GF does not parse SR storage or hard-code its security epoch. |
|
|
16
|
+
| Deployment health | The published toolchain catalog, currently owned by GF | `tool-skill-sync doctor` verifies installed capabilities and the frozen runtime contract | Explicit CLI/storage allowlists and required capability versions all apply. Source availability or equal CLI numbers alone do not prove compatibility. |
|
|
17
|
+
|
|
18
|
+
These protocols already exist. The next step is preserving and testing them, not designing a
|
|
19
|
+
replacement message bus or accepting unknown versions. Protocol/capability versioning does not
|
|
20
|
+
relax the deployment catalog's explicit CLI/storage compatibility checks.
|
|
21
|
+
|
|
22
|
+
GF is independently usable for its core Git operations. WC/SR adapters run only when their
|
|
23
|
+
corresponding operation is requested. GF's
|
|
24
|
+
[adapter inventory and regression matrix](https://github.com/xuanheng-tech/git-finalizer/blob/828c35a3bc3d60c5ef25f1fb5221a086e749c0b5/docs/integration-boundaries.md)
|
|
25
|
+
is the consumer-owned authority. WC owns lifecycle; a GF execution receipt still needs the
|
|
26
|
+
formal `lease-complete` flow before lifecycle completion.
|
|
27
|
+
|
|
28
|
+
Two bounded legacy couplings remain: GF checks WC namespace directory **existence** to report
|
|
29
|
+
a migration blocker, and recognizes exact public contract descriptions in its scanner. Neither
|
|
30
|
+
is general private-state parsing. Replace the existence probe only after a producer-owned
|
|
31
|
+
public unmanaged/legacy/migrated verdict exists and all negative controls pass. Keep the narrow
|
|
32
|
+
scanner recognition until an agreed producer/consumer contract change replaces it; never widen
|
|
33
|
+
the scanner or silently fall back to private files.
|
|
34
|
+
|
|
35
|
+
## Host consumers
|
|
36
|
+
|
|
37
|
+
A fresh inspection of AW's current controller client confirms the following source call paths:
|
|
38
|
+
|
|
39
|
+
| Surface | Evidence classification | Ownership decision |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `host-execution`, `execution-admit/cancel`, `admission-queue` | Direct calls | WC governs admission and resource claims; AW keeps business scheduling and host policy. |
|
|
42
|
+
| `session-admit` | Direct `_invoke` call after capability negotiation | Supported. The earlier audit's absence claim is obsolete. |
|
|
43
|
+
| `session-bind` | Argument tuple passed through `_run`, rather than a literal `_invoke` call | Supported. An `_invoke`-only search misses this consumer. |
|
|
44
|
+
| `binding-add/remove/transfer` | Transfer is direct; add/remove are dynamically composed by a bounded helper | Supported; explicit client/session identity and existing lease checks remain required. |
|
|
45
|
+
| `lifecycle-events`, `lifecycle-event-ack` | Direct calls | WC owns event identity; AW acknowledges after its own durable update. |
|
|
46
|
+
| `reconcile-session` | No direct external AW CLI call found in this inspection; WC's shipped session-end adapter calls the lifecycle function | Supported. No proof of exclusive use or safe deprecation. |
|
|
47
|
+
|
|
48
|
+
Also observed: plan/acquire/resume/status/capabilities, writer lease acquire/release and write
|
|
49
|
+
guard; integration queue/refresh/validate, publication lease acquire/complete, reconciliation,
|
|
50
|
+
release-intent and lifecycle-history. Static literals are evidence of a call path, not runtime
|
|
51
|
+
frequency or a complete list of dynamically constructed commands.
|
|
52
|
+
|
|
53
|
+
No AW identity default, resource limit, QP policy or business approval changes in this inventory.
|
|
54
|
+
Any future host-policy relocation needs consumer-owned changes, staged compatibility, migration
|
|
55
|
+
and rollback. Recovery commands remain supported until all consumers have an evidence-backed
|
|
56
|
+
migration and a published deprecation window.
|
|
57
|
+
|
|
58
|
+
## Regression and change gates
|
|
59
|
+
|
|
60
|
+
- WC: public CLI/API contract tests plus the affected lifecycle/recovery suites, and the complete
|
|
61
|
+
integration gate for shared changes. A pure helper extraction preserves clocks, locks, request
|
|
62
|
+
identity, immutable history, refusal codes, retry timing and aliases.
|
|
63
|
+
- GF: its existing boundary tests, public-protocol negative controls and required real WC/SR
|
|
64
|
+
release gate. A consumer stub is not a substitute for real producer acceptance.
|
|
65
|
+
- Host: installed capability checks and the consumer's existing acceptance command before
|
|
66
|
+
activation. Source inspection is not installed-runtime or live-business acceptance.
|
|
67
|
+
- Preserve old receipts and explicit version allowlists. Do not count an expected nonzero refusal
|
|
68
|
+
as functional success, and do not infer business completion from a clean checkout.
|
|
@@ -102,3 +102,16 @@ Receipts distinguish a read-only audit, `PUBLISHED_VERIFIED`, and `FRESH_INSTALL
|
|
|
102
102
|
Skipped tests remain skipped. Local installation or activation of a maintainer's WC runtime is a
|
|
103
103
|
separate operation; publishing a package does not update that runtime or authorize a production
|
|
104
104
|
release in any repository governed by WC.
|
|
105
|
+
|
|
106
|
+
## Publication visibility
|
|
107
|
+
|
|
108
|
+
Successful writes can precede their visibility through GitHub or PyPI queries. After a successful
|
|
109
|
+
create, upload or publish command, the publisher polls only the missing observation. All phases
|
|
110
|
+
share one five-minute monotonic deadline, including draft, asset, PyPI and public-release visibility.
|
|
111
|
+
Each network command retains its own timeout; an in-flight request can finish after that deadline.
|
|
112
|
+
Metadata requests ask caches to revalidate. This does not guarantee immediate upstream visibility.
|
|
113
|
+
|
|
114
|
+
Every poll rechecks the immutable tag and main containment; normal inventory and byte/hash checks
|
|
115
|
+
remain active. Command errors, unknown assets, yanked files and identity/checksum drift stop
|
|
116
|
+
immediately. Polls never repeat writes. If visibility still times out, the job fails and keeps the
|
|
117
|
+
original bundle and current release. Audit destinations and rerun failed jobs in the same run as above.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Maintenance roadmap
|
|
2
|
+
|
|
3
|
+
Execution baseline: 2026-10-23. This is the WC portion of the approved four-tool maintenance
|
|
4
|
+
direction. Each stage needs its own tested deliverable; later ideas are not current behavior.
|
|
5
|
+
|
|
6
|
+
## Current baseline
|
|
7
|
+
|
|
8
|
+
WC 1.32.5 retains CLI contract 61 and storage layout 5. The scope and architecture documents
|
|
9
|
+
are already on main. Previous extractions reduced private cross-module imports from 93 to
|
|
10
|
+
82 and then 80. This batch moves pure scope hashing/overlap queries into `semantics`, reducing
|
|
11
|
+
the baseline to 78. It retains lifecycle mutation helpers, clocks and internal aliases.
|
|
12
|
+
|
|
13
|
+
GF 1.9.8 already consumes SR's public read API and WC's versioned publication/retirement
|
|
14
|
+
protocols. CL 1.3.10 and SR 2.5.12 already use main on both hosting platforms. These completed
|
|
15
|
+
items are not prerequisites still waiting to happen.
|
|
16
|
+
|
|
17
|
+
[Consumer boundaries](integration-boundaries.md) correct the earlier AW session-call inventory.
|
|
18
|
+
Remaining evidence gaps are external consumer usage, installed-host behavior beyond the existing
|
|
19
|
+
acceptance gates, and historical temporary-file impact. No historical evidence is cleaned up.
|
|
20
|
+
|
|
21
|
+
## Sequence and acceptance
|
|
22
|
+
|
|
23
|
+
| Stage | Deliverable | Exit evidence | Stop or rollback boundary |
|
|
24
|
+
| --- | --- | --- | --- |
|
|
25
|
+
| Now: boundaries | Current protocol and consumer inventory, including legacy adapters | Named owner per interface, source references, explicit unknowns; no changes to supported commands | Conflicting evidence keeps the interface supported |
|
|
26
|
+
| Now: WC structure | One cohesive pure release-intent query extraction | Full integration gate; existing renewal/cancellation/recovery behavior; CLI 61/storage 5 unchanged | Revert the isolated extraction if behavior differs |
|
|
27
|
+
| Shared quality checks: delivered | CL/SR adopt release-tools with a reviewed full commit pin | Both hosted parallel pilots and final three-version checks passed; SR retains its package check | Restore the pinned original consumer workflow; PyPI/Gitea flows remain owned by consumers |
|
|
28
|
+
| Next 2–6 weeks | Further WC extraction only where a shared responsibility is clear; measure maintenance burden and failures | One behavior-preserving batch at a time, consumer gates, shrinking private-import baseline | Keep injected clocks, transaction and crash hooks with their owner until an explicit migration is tested |
|
|
29
|
+
| Following 6–12 weeks | Evaluate moving manifests, compatibility catalog and shared Skills from GF to a dedicated toolchain owner | Full ownership inventory, staged installer/migration, exactly one active writer, historical pin and rollback tests | No second simultaneous Skill authority; no ownership switch on file-copy evidence |
|
|
30
|
+
| Following 3–6 months | Evaluate a staged GF Python core migration | Characterization of exit/status/ref effects, isolated old/new comparisons, interruption recovery and real consumer gates | No dual production writes; preserve scanner refusals and existing installation until replacement passes |
|
|
31
|
+
|
|
32
|
+
The time ranges express ordering, not release deadlines. Batch internal maintenance with a
|
|
33
|
+
planned software release instead of increasing the CLI contract for every refactor. A public
|
|
34
|
+
behavior change still requires explicit contract/capability review and compatibility updates.
|
|
35
|
+
|
|
36
|
+
## Shared workflow pilot boundary
|
|
37
|
+
|
|
38
|
+
Start with CL and retain its Python matrix and `just check`; only after hosted equivalence is
|
|
39
|
+
proved adopt SR with its additional package check. Pin all workflow dependencies to reviewed
|
|
40
|
+
commits. Keep upload jobs and their Trusted Publisher/environment in each repository's top-level
|
|
41
|
+
release workflow. First publication of a new public repository requires its own authorization.
|
|
42
|
+
|
|
43
|
+
Do not introduce a shared runtime Python dependency into CL/SR, merge the tools, relax SR's
|
|
44
|
+
Python floor, move host-specific defaults without migration, or replace existing WC/GF protocols.
|
|
45
|
+
GitHub-first release consolidation and a future toolchain repository remain design decisions,
|
|
46
|
+
not permission to disable existing Gitea delivery or platform controls.
|
|
47
|
+
|
|
48
|
+
This maintenance batch moves pure scope hashing and overlap queries to `semantics`, lowering
|
|
49
|
+
the private-import baseline from 80 to 78. Publication metadata readback now shares a bounded
|
|
50
|
+
visibility window; exact bytes, identities, failed-command outcomes and same-bundle recovery stay
|
|
51
|
+
mandatory. CL/SR shared quality checks have passed their hosted parallel and final matrices.
|
|
@@ -4,16 +4,18 @@ This document records which parts of the public CLI are generic worktree governa
|
|
|
4
4
|
exist mainly for one consumer. It is an inventory plus proposed boundaries; nothing here changes
|
|
5
5
|
behavior, and every "proposed" line needs an owner decision before code moves.
|
|
6
6
|
|
|
7
|
-
Snapshot: CLI contract 61, 81 commands,
|
|
8
|
-
counts were rechecked on 2026-10-23
|
|
9
|
-
|
|
7
|
+
Snapshot: WC 1.32.3, CLI contract 61, 81 commands, 66 `*_version` capability keys. The public contract
|
|
8
|
+
counts were rechecked on 2026-10-23. Host call paths were rechecked against current
|
|
9
|
+
consumer source; this remains static evidence, not a runtime trace or proof of exclusive use.
|
|
10
|
+
See [consumer boundaries](integration-boundaries.md) for the updated protocol inventory.
|
|
10
11
|
|
|
11
12
|
## How the inventory was made
|
|
12
13
|
|
|
13
14
|
- **Agent Workspace (AW):** every `_invoke(...)` call in AW's controller client
|
|
14
|
-
(`backend/src/agent_workspace/infrastructure/worktree_controller.py`) plus
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
(`backend/src/agent_workspace/infrastructure/worktree_controller.py`), plus argument tuples
|
|
16
|
+
passed through `_run`, bounded dynamic binding helpers, session-retirement and external-execution
|
|
17
|
+
modules. The earlier `_invoke`-only inventory missed `session-bind`. This is not proof that
|
|
18
|
+
dynamic or other consumers do not exist.
|
|
17
19
|
- **Git Finalizer (GF):** word match over `git-finalize*` and `tooling/*.py`. Common words
|
|
18
20
|
(`review`, `task`, `release`, `resume`, `status`, `doctor`) can be false positives; the
|
|
19
21
|
retirement and publication entries are confirmed by GF's adapter code.
|
|
@@ -34,7 +36,8 @@ Checkout budget, ownership and safe release for any person or agent sharing a re
|
|
|
34
36
|
|
|
35
37
|
`branch-retirement-plan`, `-verify`, `-authorize`, `-complete`, `-record`, `-status`,
|
|
36
38
|
`-executions`, and `publication-execution-session`. These are the public protocols GF consumes
|
|
37
|
-
through capabilities
|
|
39
|
+
through versioned capabilities and protocols. This does not relax explicit CLI/storage version
|
|
40
|
+
allowlists in the deployment catalog or permit unknown versions. See [boundaries](integration-boundaries.md).
|
|
38
41
|
|
|
39
42
|
## 3. Host-runtime integration (used by AW today)
|
|
40
43
|
|
|
@@ -45,7 +48,8 @@ through capabilities; they should evolve by capability version, not by CLI contr
|
|
|
45
48
|
| `admission-queue` | yes | Periodic queue reconciliation |
|
|
46
49
|
| `binding-add`, `binding-remove`, `binding-transfer` | yes | Runtime bindings owned by a host |
|
|
47
50
|
| `lifecycle-events`, `lifecycle-event-ack` | yes | Event feed with consumer acknowledgement |
|
|
48
|
-
| `session-admit`, `session-bind
|
|
51
|
+
| `session-admit`, `session-bind` | yes, confirmed in current source | Capability-negotiated direct/tuple call paths; retain support |
|
|
52
|
+
| `reconcile-session` | no direct external AW CLI call found | WC session-end calls its lifecycle function; other external consumers remain unverified |
|
|
49
53
|
|
|
50
54
|
**Proposed:** keep these in WC only if they are documented as a generic *host runtime protocol*
|
|
51
55
|
(any host, explicit `--client-id`, no AW-specific defaults or wording). Otherwise move the
|
|
@@ -61,7 +65,8 @@ and binding records.
|
|
|
61
65
|
`reconcile-writers`, `acquire-across`, `acquire-when-free`, `canonical-refresh`,
|
|
62
66
|
`canonical-transition`, `namespace-migration-plan`, `-verify`, `-receipts`, `namespace-migrate`.
|
|
63
67
|
|
|
64
|
-
|
|
68
|
+
A static AW/GF inventory cannot establish usage by operators, other hosts or indirect adapters.
|
|
69
|
+
These commands remain supported; lack of a literal call is not deprecation evidence.
|
|
65
70
|
|
|
66
71
|
**Proposed:** classify each as *supported*, *operator-only* or *deprecated* in the CLI contract,
|
|
67
72
|
publish a deprecation window for the last group, and stop adding a new top-level command per
|
|
@@ -70,7 +75,8 @@ recovery case — prefer one `recover` entry with typed subcases.
|
|
|
70
75
|
## Open questions for the owner
|
|
71
76
|
|
|
72
77
|
1. Is `host-execution` meant for hosts other than AW? If not, should its policy move to AW?
|
|
73
|
-
2.
|
|
78
|
+
2. Which additional consumers use the session interfaces? AW use of `session-admit` and
|
|
79
|
+
`session-bind` is confirmed; direct external `reconcile-session` usage remains unverified.
|
|
74
80
|
3. Which section-4 commands may be deprecated, and on what timeline?
|
|
75
81
|
|
|
76
82
|
## Current compatibility decision
|
|
@@ -1047,6 +1047,7 @@ def _main(argv: list[str] | None) -> int:
|
|
|
1047
1047
|
"preservation_supersession_version": 1,
|
|
1048
1048
|
"retained_typed_cleanup_version": 1,
|
|
1049
1049
|
"aborted_integration_preserved_cleanup_version": 1,
|
|
1050
|
+
"terminal_preserved_cleanup_version": 1,
|
|
1050
1051
|
"preservation_leaf_version": 1,
|
|
1051
1052
|
"integration_publication_execution_version": 1,
|
|
1052
1053
|
"integration_retry_version": 1,
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/convergence.py
RENAMED
|
@@ -316,11 +316,11 @@ def _require_intent_renewal_due(
|
|
|
316
316
|
stays blocked. The same rule as stale-intent renewal applies: a changed record or
|
|
317
317
|
HEAD, or a first expiry, proceeds at once.
|
|
318
318
|
"""
|
|
319
|
-
from .
|
|
319
|
+
from .release_intents import release_intent_renewal_due, release_intents_for_allocation
|
|
320
320
|
|
|
321
321
|
canonical = inventory(repository_path)
|
|
322
322
|
snapshot = MetadataStore(canonical.common_dir).load()
|
|
323
|
-
intents =
|
|
323
|
+
intents = release_intents_for_allocation(snapshot, record.allocation_id)
|
|
324
324
|
if any(intent.status in {"PENDING", "BLOCKED_RETRYABLE"} for intent in intents):
|
|
325
325
|
return # create_release_intent reuses the live intent
|
|
326
326
|
stale = [intent for intent in intents if intent.status == "STALE"]
|
|
@@ -329,7 +329,7 @@ def _require_intent_renewal_due(
|
|
|
329
329
|
# The backoff runs from the most recent time an intent of this allocation went stale.
|
|
330
330
|
latest = max(stale, key=lambda intent: (intent.updated_at, intent.created_at, intent.intent_id))
|
|
331
331
|
ttl = load_policy(canonical.canonical_path).release_intent_ttl_seconds
|
|
332
|
-
if not
|
|
332
|
+
if not release_intent_renewal_due(snapshot, latest, record, item.head, datetime.now(UTC), ttl):
|
|
333
333
|
raise ControllerError(
|
|
334
334
|
"RENEWAL_BACKOFF",
|
|
335
335
|
f"unchanged handoff release intent is backing off after {latest.blocker or 'expiry'}",
|
|
@@ -136,8 +136,119 @@ def _legacy_no_change(store: MetadataStore, item: WorktreeGitState, record: Meta
|
|
|
136
136
|
return original
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def _has_pre_completion_cleanup(store: MetadataStore, record: MetadataRecord) -> bool:
|
|
140
|
+
from .cleanup_evidence import read
|
|
141
|
+
|
|
142
|
+
for path in sorted(store.completion_receipts_dir.glob("*.json")):
|
|
143
|
+
value, _ = read(path)
|
|
144
|
+
plan = value.get("plan", {})
|
|
145
|
+
if plan.get("allocation_id") == record.allocation_id and plan.get("terminal_evidence") is None:
|
|
146
|
+
return True
|
|
147
|
+
return False
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def terminal_cleanup_evidence(store: MetadataStore, snapshot: MetadataSnapshot,
|
|
151
|
+
item: WorktreeGitState, record: MetadataRecord) -> dict[str, Any]:
|
|
152
|
+
"""Pin existing terminal proof without reopening a frozen allocation.
|
|
153
|
+
|
|
154
|
+
Legacy no-change receipts acquire a verified baseline now, never a claim of
|
|
155
|
+
historical digest observation. New completions can clean at their terminal
|
|
156
|
+
CAS only when they froze an empty prior cleanup set.
|
|
157
|
+
"""
|
|
158
|
+
from .cleanup_evidence import read
|
|
159
|
+
from .semantics import publication_receipt_lease_released
|
|
160
|
+
|
|
161
|
+
def invalid():
|
|
162
|
+
return ControllerError("TERMINAL_CLEANUP_EVIDENCE_INVALID",
|
|
163
|
+
"exact lease-cleared publication or no-change completion required")
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
if record.lifecycle == "PUBLISHED" and record.role == "integration":
|
|
167
|
+
matches = [receipt for receipt in snapshot.publication_receipts.values()
|
|
168
|
+
if receipt.allocation_id == record.allocation_id]
|
|
169
|
+
if len(matches) != 1:
|
|
170
|
+
raise invalid()
|
|
171
|
+
receipt = matches[0]
|
|
172
|
+
if (receipt.repository_id != snapshot.repository_id
|
|
173
|
+
or receipt.status not in {"REMOTE_VERIFIED", "RELEASED"}
|
|
174
|
+
or not receipt.remote_verified_at
|
|
175
|
+
or not publication_receipt_lease_released(snapshot, receipt)
|
|
176
|
+
or receipt.candidate_oid != item.head
|
|
177
|
+
or receipt.finalizer_record_id != record.finalizer_record_id
|
|
178
|
+
or not receipt.validation_evidence_id):
|
|
179
|
+
raise invalid()
|
|
180
|
+
intents = [intent for intent in snapshot.integration_intents.values()
|
|
181
|
+
if intent.allocation_id == record.allocation_id]
|
|
182
|
+
if receipt.intent_id is not None or intents:
|
|
183
|
+
if len(intents) != 1:
|
|
184
|
+
raise invalid()
|
|
185
|
+
intent = intents[0]
|
|
186
|
+
if (receipt.intent_id != intent.intent_id
|
|
187
|
+
or intent.allocation_id != record.allocation_id
|
|
188
|
+
or intent.publication_identity != receipt.receipt_id
|
|
189
|
+
or intent.candidate_oid != item.head
|
|
190
|
+
or intent.validation_evidence_id != receipt.validation_evidence_id):
|
|
191
|
+
raise invalid()
|
|
192
|
+
from .store import publication_receipt_to_dict
|
|
193
|
+
|
|
194
|
+
receipt_path = store.publication_receipts_dir / f"{receipt.receipt_id}.json"
|
|
195
|
+
receipt_value, receipt_digest = read(receipt_path)
|
|
196
|
+
if receipt_value != publication_receipt_to_dict(receipt):
|
|
197
|
+
raise invalid()
|
|
198
|
+
# The producer receipt is immutable; later closeout has separate evidence.
|
|
199
|
+
return {"contract": "terminal-preserved-cleanup/v1", "kind": "publication",
|
|
200
|
+
"receipt_id": receipt.receipt_id,
|
|
201
|
+
"receipt_ref": {"path": str(receipt_path), "sha256": receipt_digest},
|
|
202
|
+
"repository_id": receipt.repository_id,
|
|
203
|
+
"allocation_id": receipt.allocation_id, "intent_id": receipt.intent_id,
|
|
204
|
+
"candidate_oid": receipt.candidate_oid,
|
|
205
|
+
"finalizer_record_id": receipt.finalizer_record_id,
|
|
206
|
+
"validation_evidence_id": receipt.validation_evidence_id,
|
|
207
|
+
"remote_verified_at": receipt.remote_verified_at}
|
|
208
|
+
if (record.lifecycle != "NO_CHANGE_COMPLETED" or record.role not in {"feature", "audit"}
|
|
209
|
+
or not record.history):
|
|
210
|
+
raise invalid()
|
|
211
|
+
completion_ref = record.history[-1].get("no_change_completion_ref")
|
|
212
|
+
if completion_ref:
|
|
213
|
+
receipt = _completion_document(store, completion_ref)
|
|
214
|
+
original = record_from_dict(receipt["before_record"])
|
|
215
|
+
event = record.history[-1]
|
|
216
|
+
if (receipt.get("contract") != NO_CHANGE_CONTRACT
|
|
217
|
+
or receipt.get("kind") != "no_change_completion"
|
|
218
|
+
or receipt.get("preservation_cleanup_receipts") != []
|
|
219
|
+
or _no_change_transition(original, event) != record
|
|
220
|
+
or {key: value for key, value in event.items() if key != "no_change_completion_ref"}
|
|
221
|
+
!= receipt["completion_event"]):
|
|
222
|
+
raise invalid()
|
|
223
|
+
_no_change_identity(receipt, original, item, snapshot.repository_id)
|
|
224
|
+
if not (_no_change_time(original.last_reviewed_at)
|
|
225
|
+
<= _no_change_time(receipt["completed_at"]) <= _no_change_time(event["at"])):
|
|
226
|
+
raise invalid()
|
|
227
|
+
if _has_pre_completion_cleanup(store, record):
|
|
228
|
+
raise invalid()
|
|
229
|
+
return {"contract": "terminal-preserved-cleanup/v1", "kind": "no_change",
|
|
230
|
+
"completion_ref": completion_ref}
|
|
231
|
+
matches = []
|
|
232
|
+
for path in sorted(store.completion_receipts_dir.glob("*.json")):
|
|
233
|
+
value, digest = read(path)
|
|
234
|
+
if (value.get("kind") == "no_change_completion"
|
|
235
|
+
and value.get("allocation_id") == record.allocation_id):
|
|
236
|
+
matches.append({"receipt_id": path.stem, "path": str(path), "sha256": digest})
|
|
237
|
+
if len(matches) != 1:
|
|
238
|
+
raise invalid()
|
|
239
|
+
ref = matches[0]
|
|
240
|
+
_legacy_no_change(store, item, record, snapshot.repository_id, ref)
|
|
241
|
+
return {"contract": "terminal-preserved-cleanup/v1", "kind": "legacy_no_change",
|
|
242
|
+
"completion_ref": ref}
|
|
243
|
+
except (OSError, KeyError, TypeError, ValueError, IndexError, ControllerError) as exc:
|
|
244
|
+
raise invalid() from exc
|
|
245
|
+
|
|
246
|
+
|
|
139
247
|
def verify_no_change_cleanup(store: MetadataStore, item: WorktreeGitState,
|
|
140
|
-
record: MetadataRecord, repository_id: str | None
|
|
248
|
+
record: MetadataRecord, repository_id: str | None, *,
|
|
249
|
+
cleanup_recovery: dict[str, Any] | None = None,
|
|
250
|
+
cleanup_transaction_id: str | None = None,
|
|
251
|
+
allow_pending_typed_no_action: bool = False) -> list[dict[str, str]]:
|
|
141
252
|
"""Bridge only frozen normal transitions; cleanup keeps its original exact CAS."""
|
|
142
253
|
from .preserved_cleanup import cleanup_receipts
|
|
143
254
|
|
|
@@ -157,6 +268,14 @@ def verify_no_change_cleanup(store: MetadataStore, item: WorktreeGitState,
|
|
|
157
268
|
<= _no_change_time(receipt["completed_at"]) <= _no_change_time(event["at"])):
|
|
158
269
|
raise _no_change_fail("completion timestamp differs from its transition")
|
|
159
270
|
refs = receipt["preservation_cleanup_receipts"]
|
|
271
|
+
if refs == [] and not _has_pre_completion_cleanup(store, record):
|
|
272
|
+
terminal_cleanup_evidence(store, store._load_unlocked(), item, record)
|
|
273
|
+
return cleanup_receipts(
|
|
274
|
+
store, item, record, repository_id, cleanup_recovery=cleanup_recovery,
|
|
275
|
+
cleanup_transaction_id=cleanup_transaction_id,
|
|
276
|
+
allow_pending_typed_no_action=allow_pending_typed_no_action,
|
|
277
|
+
include_no_action_refs=True,
|
|
278
|
+
)
|
|
160
279
|
elif receipt.get("kind") == "no_change_cleanup_recovery":
|
|
161
280
|
plan = receipt["plan"]
|
|
162
281
|
legacy = record_from_dict(plan["record"])
|
|
@@ -17,6 +17,8 @@ from .semantics import (
|
|
|
17
17
|
integration_intent_abandoned,
|
|
18
18
|
integration_prerequisite,
|
|
19
19
|
integration_queue_order,
|
|
20
|
+
scope_digest,
|
|
21
|
+
scopes_overlap,
|
|
20
22
|
unallocated_integration_conflict,
|
|
21
23
|
)
|
|
22
24
|
from .store import MetadataStore
|
|
@@ -57,7 +59,7 @@ def _sources(
|
|
|
57
59
|
*,
|
|
58
60
|
require_idle: bool = True,
|
|
59
61
|
) -> dict[str, Any]:
|
|
60
|
-
from .lifecycle import _resolve_integration_sources
|
|
62
|
+
from .lifecycle import _resolve_integration_sources
|
|
61
63
|
|
|
62
64
|
observed = _resolve_integration_sources(
|
|
63
65
|
repository,
|
|
@@ -74,7 +76,7 @@ def _sources(
|
|
|
74
76
|
intent.source_finalizer_record_ids,
|
|
75
77
|
intent.source_snapshot_ids,
|
|
76
78
|
)
|
|
77
|
-
if observed != expected or
|
|
79
|
+
if observed != expected or scope_digest(intent.scope_paths) != intent.scope_digest:
|
|
78
80
|
raise ControllerError("SOURCE_EVIDENCE_MISMATCH", "retry cannot replace source or scope")
|
|
79
81
|
evidence = {}
|
|
80
82
|
for allocation, branch, head in zip(
|
|
@@ -227,7 +229,6 @@ def _prerequisite(
|
|
|
227
229
|
parent: IntegrationIntent,
|
|
228
230
|
child: IntegrationIntent,
|
|
229
231
|
) -> dict[str, Any]:
|
|
230
|
-
from .lifecycle import _scopes_overlap
|
|
231
232
|
from .semantics import effective_intent_state
|
|
232
233
|
|
|
233
234
|
if (
|
|
@@ -258,7 +259,7 @@ def _prerequisite(
|
|
|
258
259
|
and other.allocation_id is not None
|
|
259
260
|
and effective_intent_state(snapshot, other) not in {"RELEASED", "ABANDONED"}
|
|
260
261
|
and other.queue_sequence > parent.queue_sequence
|
|
261
|
-
and
|
|
262
|
+
and scopes_overlap(other.scope_paths, child.scope_paths)
|
|
262
263
|
for other in snapshot.integration_intents.values()
|
|
263
264
|
):
|
|
264
265
|
raise ControllerError(
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/lifecycle.py
RENAMED
|
@@ -79,6 +79,9 @@ from .preserved_cleanup import CONTRACT as PRESERVED_CLEANUP_CONTRACT
|
|
|
79
79
|
from .preserved_cleanup import coverage, delete_preserved_state
|
|
80
80
|
from .record_updates import updated_intent as _updated_intent
|
|
81
81
|
from .record_updates import updated_record as _updated_record
|
|
82
|
+
from .release_intents import RENEWAL_BACKOFF_CAP_SECONDS as RENEWAL_BACKOFF_CAP_SECONDS
|
|
83
|
+
from .release_intents import release_intent_renewal_due as _renewal_due
|
|
84
|
+
from .release_intents import release_intents_for_allocation as _release_intents_for_allocation
|
|
82
85
|
from .semantics import assert_record_version as _cas
|
|
83
86
|
from .semantics import (
|
|
84
87
|
attribution_base,
|
|
@@ -95,7 +98,10 @@ from .semantics import (
|
|
|
95
98
|
from .semantics import effective_intent_state as _effective_intent_state
|
|
96
99
|
from .semantics import integration_reconciliation as _integration_reconciliation
|
|
97
100
|
from .semantics import managed_record as _managed_record
|
|
101
|
+
from .semantics import publication_receipt_lease_released as _publication_receipt_lease_released
|
|
98
102
|
from .semantics import record_for as _record_for
|
|
103
|
+
from .semantics import scope_digest as _scope_digest
|
|
104
|
+
from .semantics import scopes_overlap as _scopes_overlap
|
|
99
105
|
from .store import (
|
|
100
106
|
INTEGRATION_INTENT_CONTRACT_VERSION,
|
|
101
107
|
INTEGRATION_INTENT_SCHEMA_VERSION,
|
|
@@ -148,31 +154,12 @@ def _crash_point(name: str) -> None:
|
|
|
148
154
|
os._exit(97)
|
|
149
155
|
|
|
150
156
|
|
|
151
|
-
def _scope_digest(scope_paths: tuple[str, ...]) -> str:
|
|
152
|
-
encoded = json.dumps(list(scope_paths), ensure_ascii=True, separators=(",", ":"))
|
|
153
|
-
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
|
|
154
|
-
|
|
155
|
-
|
|
156
157
|
def _intent_order(intent: IntegrationIntent) -> tuple[int, int | str, str]:
|
|
157
158
|
if intent.queue_sequence > 0:
|
|
158
159
|
return (1, intent.queue_sequence, intent.intent_id)
|
|
159
160
|
return (0, intent.enqueued_at, intent.intent_id)
|
|
160
161
|
|
|
161
162
|
|
|
162
|
-
def _scopes_overlap(left: tuple[str, ...], right: tuple[str, ...]) -> bool:
|
|
163
|
-
if not left or not right:
|
|
164
|
-
return True
|
|
165
|
-
for left_path in left:
|
|
166
|
-
for right_path in right:
|
|
167
|
-
if (
|
|
168
|
-
left_path == right_path
|
|
169
|
-
or left_path.startswith(f"{right_path}/")
|
|
170
|
-
or right_path.startswith(f"{left_path}/")
|
|
171
|
-
):
|
|
172
|
-
return True
|
|
173
|
-
return False
|
|
174
|
-
|
|
175
|
-
|
|
176
163
|
def _released_intent_evidence(
|
|
177
164
|
snapshot: MetadataSnapshot,
|
|
178
165
|
intent: IntegrationIntent,
|
|
@@ -348,54 +335,6 @@ def _updated_release_intent(intent: ReleaseIntent, event: str, **changes: Any) -
|
|
|
348
335
|
return release_intent_from_dict(payload)
|
|
349
336
|
|
|
350
337
|
|
|
351
|
-
RENEWAL_BACKOFF_CAP_SECONDS = 24 * 3600
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
def _renewal_due(
|
|
355
|
-
snapshot: MetadataSnapshot,
|
|
356
|
-
previous: ReleaseIntent,
|
|
357
|
-
record: MetadataRecord,
|
|
358
|
-
head: str,
|
|
359
|
-
now: datetime,
|
|
360
|
-
ttl_seconds: int,
|
|
361
|
-
) -> bool:
|
|
362
|
-
"""Whether a stale release intent should be renewed on this pass.
|
|
363
|
-
|
|
364
|
-
The first expiry, or a changed record or HEAD, is renewed at once. Each further
|
|
365
|
-
stale intent of an unchanged identity failed the same way, so the next renewal
|
|
366
|
-
waits the TTL doubled per repeat, capped at a day. Renewal stays eventual (a stuck
|
|
367
|
-
checkout is still retried) while one blocked allocation no longer adds an intent
|
|
368
|
-
on every reconciliation pass.
|
|
369
|
-
"""
|
|
370
|
-
if (previous.expected_record_version, previous.expected_head) != (record.record_version, head):
|
|
371
|
-
return True
|
|
372
|
-
streak = sum(
|
|
373
|
-
1
|
|
374
|
-
for intent in _release_intents_for_allocation(snapshot, record.allocation_id)
|
|
375
|
-
if intent.status == "STALE"
|
|
376
|
-
and (intent.expected_record_version, intent.expected_head) == (record.record_version, head)
|
|
377
|
-
)
|
|
378
|
-
if streak <= 1:
|
|
379
|
-
return True
|
|
380
|
-
delay = min(ttl_seconds * 2 ** (streak - 2), RENEWAL_BACKOFF_CAP_SECONDS)
|
|
381
|
-
return (now - datetime.fromisoformat(previous.updated_at)).total_seconds() >= delay
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
def _release_intents_for_allocation(
|
|
385
|
-
snapshot: MetadataSnapshot, allocation_id: str
|
|
386
|
-
) -> tuple[ReleaseIntent, ...]:
|
|
387
|
-
return tuple(
|
|
388
|
-
sorted(
|
|
389
|
-
(
|
|
390
|
-
intent
|
|
391
|
-
for intent in snapshot.release_intents.values()
|
|
392
|
-
if intent.allocation_id == allocation_id
|
|
393
|
-
),
|
|
394
|
-
key=lambda intent: (intent.created_at, intent.intent_id),
|
|
395
|
-
)
|
|
396
|
-
)
|
|
397
|
-
|
|
398
|
-
|
|
399
338
|
def _cancel_release_intents(
|
|
400
339
|
store: MetadataStore,
|
|
401
340
|
snapshot: MetadataSnapshot,
|
|
@@ -4760,6 +4699,7 @@ def _typed_cleanup_plan_locked(
|
|
|
4760
4699
|
blockers: list[dict[str, str]] = []
|
|
4761
4700
|
aborted_integration = record.role == "integration" and record.lifecycle == "INTEGRATION_ABORTED"
|
|
4762
4701
|
integration_abort_evidence = None
|
|
4702
|
+
terminal_evidence = None
|
|
4763
4703
|
preserved_evidence = None
|
|
4764
4704
|
preserved_evidences: list[dict[str, Any]] = []
|
|
4765
4705
|
multiple = bool(additional_preservation_receipts or additional_preservation_receipt_sha256)
|
|
@@ -4907,6 +4847,12 @@ def _typed_cleanup_plan_locked(
|
|
|
4907
4847
|
from .integration_cleanup import abort_evidence
|
|
4908
4848
|
|
|
4909
4849
|
integration_abort_evidence = abort_evidence(store, snapshot, item, record)
|
|
4850
|
+
elif ((record.lifecycle == "PUBLISHED" and record.role == "integration")
|
|
4851
|
+
or (record.lifecycle == "NO_CHANGE_COMPLETED" and not archived_no_commit
|
|
4852
|
+
and record.history)):
|
|
4853
|
+
from .delivered_closeout import terminal_cleanup_evidence
|
|
4854
|
+
|
|
4855
|
+
terminal_evidence = terminal_cleanup_evidence(store, snapshot, item, record)
|
|
4910
4856
|
elif (record.lifecycle != "HANDOFF_READY" and not archived_no_commit and not retained_cleanup) or record.role not in {
|
|
4911
4857
|
"feature",
|
|
4912
4858
|
"audit",
|
|
@@ -4963,13 +4909,13 @@ def _typed_cleanup_plan_locked(
|
|
|
4963
4909
|
runtime_evidence["scan_error"] = {"code": exc.code, "message": str(exc)}
|
|
4964
4910
|
blockers.append({"code": exc.code, "message": str(exc)})
|
|
4965
4911
|
for resource in resources:
|
|
4966
|
-
if aborted_integration and resource["cleanup_action"] == "remove-tree":
|
|
4912
|
+
if (aborted_integration or terminal_evidence is not None) and resource["cleanup_action"] == "remove-tree":
|
|
4967
4913
|
# This additional lifecycle has no project-name/path allowlist. Even
|
|
4968
4914
|
# otherwise recognized build output requires an exact preserved copy.
|
|
4969
4915
|
resource.update(
|
|
4970
4916
|
classification="blocked", cleanup_action="none",
|
|
4971
|
-
blocker="INTEGRATION_PRESERVATION_REQUIRED",
|
|
4972
|
-
reason="
|
|
4917
|
+
blocker="INTEGRATION_PRESERVATION_REQUIRED" if aborted_integration else "TERMINAL_PRESERVATION_REQUIRED",
|
|
4918
|
+
reason="terminal integration/no-change cleanup requires exact preservation for every tree",
|
|
4973
4919
|
)
|
|
4974
4920
|
if resource["blocker"]:
|
|
4975
4921
|
blockers.append(
|
|
@@ -5039,6 +4985,8 @@ def _typed_cleanup_plan_locked(
|
|
|
5039
4985
|
identity["preservation_evidence"] = preserved_evidence
|
|
5040
4986
|
if multiple:
|
|
5041
4987
|
identity["preservation_evidences"] = preserved_evidences
|
|
4988
|
+
if terminal_evidence is not None:
|
|
4989
|
+
identity["terminal_evidence"] = terminal_evidence
|
|
5042
4990
|
if integration_abort_evidence is not None:
|
|
5043
4991
|
identity["integration_abort_evidence"] = integration_abort_evidence
|
|
5044
4992
|
if recovery is not None:
|
|
@@ -6215,32 +6163,6 @@ def _reconciliation_for_publication_receipt(
|
|
|
6215
6163
|
return matches[0] if matches else None
|
|
6216
6164
|
|
|
6217
6165
|
|
|
6218
|
-
def _lease_release_reconciliation_for_publication_receipt(
|
|
6219
|
-
snapshot: MetadataSnapshot, receipt_id: str
|
|
6220
|
-
) -> ReconciliationReceipt | None:
|
|
6221
|
-
matches = [
|
|
6222
|
-
receipt
|
|
6223
|
-
for receipt in snapshot.reconciliation_receipts.values()
|
|
6224
|
-
if receipt.source_receipt_id == receipt_id
|
|
6225
|
-
and receipt.result == "LEASE_RELEASE_ACKNOWLEDGED"
|
|
6226
|
-
]
|
|
6227
|
-
if len(matches) > 1:
|
|
6228
|
-
raise ControllerError(
|
|
6229
|
-
"RECONCILIATION_DRIFT", "publication receipt has multiple lease reconciliations"
|
|
6230
|
-
)
|
|
6231
|
-
return matches[0] if matches else None
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
def _publication_receipt_lease_released(
|
|
6235
|
-
snapshot: MetadataSnapshot, receipt: PublicationReceipt
|
|
6236
|
-
) -> bool:
|
|
6237
|
-
return bool(
|
|
6238
|
-
receipt.lease_released_at is not None
|
|
6239
|
-
or _lease_release_reconciliation_for_publication_receipt(snapshot, receipt.receipt_id)
|
|
6240
|
-
is not None
|
|
6241
|
-
)
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
6166
|
def _publication_receipt_effectively_released(
|
|
6245
6167
|
snapshot: MetadataSnapshot, receipt: PublicationReceipt
|
|
6246
6168
|
) -> bool:
|
|
@@ -7518,7 +7440,11 @@ def _validate_release_core(
|
|
|
7518
7440
|
elif record.lifecycle == "NO_CHANGE_COMPLETED" and record.history[-1].get("no_change_completion_ref"):
|
|
7519
7441
|
from .delivered_closeout import verify_no_change_cleanup
|
|
7520
7442
|
|
|
7521
|
-
cleanup_refs = verify_no_change_cleanup(
|
|
7443
|
+
cleanup_refs = verify_no_change_cleanup(
|
|
7444
|
+
store, item, record, snapshot.repository_id, cleanup_recovery=cleanup_recovery,
|
|
7445
|
+
cleanup_transaction_id=cleanup_transaction_id,
|
|
7446
|
+
allow_pending_typed_no_action=allow_pending_typed_no_action,
|
|
7447
|
+
)
|
|
7522
7448
|
else:
|
|
7523
7449
|
cleanup_refs = cleanup_receipts(
|
|
7524
7450
|
store, item, record, snapshot.repository_id,
|
|
@@ -640,6 +640,11 @@ def cleanup_receipts(
|
|
|
640
640
|
plan = receipt.get("plan", {})
|
|
641
641
|
if plan.get("allocation_id") != record.allocation_id:
|
|
642
642
|
continue
|
|
643
|
+
if plan.get("terminal_evidence") is not None:
|
|
644
|
+
from .delivered_closeout import terminal_cleanup_evidence
|
|
645
|
+
|
|
646
|
+
if terminal_cleanup_evidence(store, store._load_unlocked(), item, record) != plan["terminal_evidence"]:
|
|
647
|
+
raise _blocked("terminal completion evidence changed after cleanup")
|
|
643
648
|
if plan.get("integration_abort_evidence") is not None:
|
|
644
649
|
from .integration_cleanup import abort_evidence
|
|
645
650
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Pure queries over the caller's release-intent snapshot.
|
|
2
|
+
|
|
3
|
+
Callers retain ownership of clocks, locks and all release-intent mutations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
|
|
10
|
+
from .models import MetadataRecord, MetadataSnapshot, ReleaseIntent
|
|
11
|
+
|
|
12
|
+
RENEWAL_BACKOFF_CAP_SECONDS = 24 * 3600
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def release_intent_renewal_due(
|
|
16
|
+
snapshot: MetadataSnapshot,
|
|
17
|
+
previous: ReleaseIntent,
|
|
18
|
+
record: MetadataRecord,
|
|
19
|
+
head: str,
|
|
20
|
+
now: datetime,
|
|
21
|
+
ttl_seconds: int,
|
|
22
|
+
) -> bool:
|
|
23
|
+
"""Whether a stale release intent should be renewed on this pass.
|
|
24
|
+
|
|
25
|
+
The first expiry, or a changed record or HEAD, is renewed at once. Each further
|
|
26
|
+
stale intent of an unchanged identity failed the same way, so the next renewal
|
|
27
|
+
waits the TTL doubled per repeat, capped at a day. Renewal stays eventual (a stuck
|
|
28
|
+
checkout is still retried) while one blocked allocation no longer adds an intent
|
|
29
|
+
on every reconciliation pass.
|
|
30
|
+
"""
|
|
31
|
+
if (previous.expected_record_version, previous.expected_head) != (record.record_version, head):
|
|
32
|
+
return True
|
|
33
|
+
streak = sum(
|
|
34
|
+
1
|
|
35
|
+
for intent in release_intents_for_allocation(snapshot, record.allocation_id)
|
|
36
|
+
if intent.status == "STALE"
|
|
37
|
+
and (intent.expected_record_version, intent.expected_head) == (record.record_version, head)
|
|
38
|
+
)
|
|
39
|
+
if streak <= 1:
|
|
40
|
+
return True
|
|
41
|
+
delay = min(ttl_seconds * 2 ** (streak - 2), RENEWAL_BACKOFF_CAP_SECONDS)
|
|
42
|
+
return (now - datetime.fromisoformat(previous.updated_at)).total_seconds() >= delay
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def release_intents_for_allocation(
|
|
46
|
+
snapshot: MetadataSnapshot, allocation_id: str
|
|
47
|
+
) -> tuple[ReleaseIntent, ...]:
|
|
48
|
+
return tuple(
|
|
49
|
+
sorted(
|
|
50
|
+
(
|
|
51
|
+
intent
|
|
52
|
+
for intent in snapshot.release_intents.values()
|
|
53
|
+
if intent.allocation_id == allocation_id
|
|
54
|
+
),
|
|
55
|
+
key=lambda intent: (intent.created_at, intent.intent_id),
|
|
56
|
+
)
|
|
57
|
+
)
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/semantics.py
RENAMED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
5
7
|
from dataclasses import replace
|
|
6
8
|
from pathlib import Path, PurePosixPath
|
|
7
9
|
from typing import Any
|
|
@@ -20,6 +22,8 @@ from .models import (
|
|
|
20
22
|
IntegrationReconciliationReceipt,
|
|
21
23
|
MetadataRecord,
|
|
22
24
|
MetadataSnapshot,
|
|
25
|
+
PublicationReceipt,
|
|
26
|
+
ReconciliationReceipt,
|
|
23
27
|
RepositoryInventory,
|
|
24
28
|
WorktreeGitState,
|
|
25
29
|
WorktreePolicy,
|
|
@@ -29,6 +33,32 @@ from .policy import load_policy
|
|
|
29
33
|
EVIDENCE_LIFECYCLES = frozenset({"HANDOFF_READY", "PRESERVED", "INTEGRATING", "PUBLISHED"})
|
|
30
34
|
|
|
31
35
|
|
|
36
|
+
def _lease_release_reconciliation_for_publication_receipt(
|
|
37
|
+
snapshot: MetadataSnapshot, receipt_id: str
|
|
38
|
+
) -> ReconciliationReceipt | None:
|
|
39
|
+
matches = [
|
|
40
|
+
receipt
|
|
41
|
+
for receipt in snapshot.reconciliation_receipts.values()
|
|
42
|
+
if receipt.source_receipt_id == receipt_id
|
|
43
|
+
and receipt.result == "LEASE_RELEASE_ACKNOWLEDGED"
|
|
44
|
+
]
|
|
45
|
+
if len(matches) > 1:
|
|
46
|
+
raise ControllerError(
|
|
47
|
+
"RECONCILIATION_DRIFT", "publication receipt has multiple lease reconciliations"
|
|
48
|
+
)
|
|
49
|
+
return matches[0] if matches else None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def publication_receipt_lease_released(
|
|
53
|
+
snapshot: MetadataSnapshot, receipt: PublicationReceipt
|
|
54
|
+
) -> bool:
|
|
55
|
+
return bool(
|
|
56
|
+
receipt.lease_released_at is not None
|
|
57
|
+
or _lease_release_reconciliation_for_publication_receipt(snapshot, receipt.receipt_id)
|
|
58
|
+
is not None
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
32
62
|
def record_for(item: WorktreeGitState, snapshot: MetadataSnapshot) -> MetadataRecord | None:
|
|
33
63
|
allocation_id = snapshot.bindings.get(item.key)
|
|
34
64
|
if allocation_id is None:
|
|
@@ -176,6 +206,25 @@ def integration_queue_order(
|
|
|
176
206
|
return (0, anchor.enqueued_at, phase, intent.intent_id)
|
|
177
207
|
|
|
178
208
|
|
|
209
|
+
def scope_digest(scope_paths: tuple[str, ...]) -> str:
|
|
210
|
+
encoded = json.dumps(list(scope_paths), ensure_ascii=True, separators=(",", ":"))
|
|
211
|
+
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def scopes_overlap(left: tuple[str, ...], right: tuple[str, ...]) -> bool:
|
|
215
|
+
if not left or not right:
|
|
216
|
+
return True
|
|
217
|
+
for left_path in left:
|
|
218
|
+
for right_path in right:
|
|
219
|
+
if (
|
|
220
|
+
left_path == right_path
|
|
221
|
+
or left_path.startswith(f"{right_path}/")
|
|
222
|
+
or right_path.startswith(f"{left_path}/")
|
|
223
|
+
):
|
|
224
|
+
return True
|
|
225
|
+
return False
|
|
226
|
+
|
|
227
|
+
|
|
179
228
|
def normalize_scope_paths(values: tuple[str, ...]) -> tuple[str, ...]:
|
|
180
229
|
normalized: set[str] = set()
|
|
181
230
|
for raw in values:
|
|
@@ -1071,7 +1071,7 @@
|
|
|
1071
1071
|
"session_launcher_entrypoint": "worktree-session",
|
|
1072
1072
|
"session_entrypoint": "worktree-session-end",
|
|
1073
1073
|
"tool_name": "worktree-controller",
|
|
1074
|
-
"tool_version": "1.32.
|
|
1074
|
+
"tool_version": "1.32.5",
|
|
1075
1075
|
"delivered_closeout_version": 1,
|
|
1076
1076
|
"retained_archive_version": 1,
|
|
1077
1077
|
"retention_convergence_version": 2,
|
|
@@ -1512,6 +1512,14 @@
|
|
|
1512
1512
|
"single_input_compatibility": "existing single receipt contract, plan and receipt shape remain unchanged",
|
|
1513
1513
|
"storage_layout_version": 4
|
|
1514
1514
|
},
|
|
1515
|
+
"terminal_preserved_cleanup_version": 1,
|
|
1516
|
+
"terminal_preserved_cleanup_contract": {
|
|
1517
|
+
"contract": "terminal-preserved-cleanup/v1",
|
|
1518
|
+
"eligible": "PUBLISHED integration with one matching remote-verified publication and cleared publication lease; NO_CHANGE_COMPLETED feature/audit with one verified original completion transition and receipt",
|
|
1519
|
+
"coverage": "every removed tree requires an exact immutable preservation generation; existing owner, HEAD, CAS, retained-ref, binding, runtime, writer and transaction guards remain mandatory",
|
|
1520
|
+
"evidence": "plan pins raw producer publication bytes or exact completion reference; legacy completion establishes a newly verified raw baseline; cleanup and release reverify it without rewriting historical evidence or reopening lifecycle",
|
|
1521
|
+
"new_no_change": "new-format completions with an empty frozen prior cleanup set may clean at the exact terminal CAS; nonempty prior proof sets retain their original pre-completion CAS"
|
|
1522
|
+
},
|
|
1515
1523
|
"retained_typed_cleanup_version": 1,
|
|
1516
1524
|
"aborted_integration_preserved_cleanup_version": 1,
|
|
1517
1525
|
"aborted_integration_preserved_cleanup_contract": {
|
|
@@ -1071,7 +1071,7 @@
|
|
|
1071
1071
|
"session_launcher_entrypoint": "worktree-session",
|
|
1072
1072
|
"session_entrypoint": "worktree-session-end",
|
|
1073
1073
|
"tool_name": "worktree-controller",
|
|
1074
|
-
"tool_version": "1.32.
|
|
1074
|
+
"tool_version": "1.32.5",
|
|
1075
1075
|
"delivered_closeout_version": 1,
|
|
1076
1076
|
"retained_archive_version": 1,
|
|
1077
1077
|
"retention_convergence_version": 2,
|
|
@@ -1512,6 +1512,14 @@
|
|
|
1512
1512
|
"single_input_compatibility": "existing single receipt contract, plan and receipt shape remain unchanged",
|
|
1513
1513
|
"storage_layout_version": 4
|
|
1514
1514
|
},
|
|
1515
|
+
"terminal_preserved_cleanup_version": 1,
|
|
1516
|
+
"terminal_preserved_cleanup_contract": {
|
|
1517
|
+
"contract": "terminal-preserved-cleanup/v1",
|
|
1518
|
+
"eligible": "PUBLISHED integration with one matching remote-verified publication and cleared publication lease; NO_CHANGE_COMPLETED feature/audit with one verified original completion transition and receipt",
|
|
1519
|
+
"coverage": "every removed tree requires an exact immutable preservation generation; existing owner, HEAD, CAS, retained-ref, binding, runtime, writer and transaction guards remain mandatory",
|
|
1520
|
+
"evidence": "plan pins raw producer publication bytes or exact completion reference; legacy completion establishes a newly verified raw baseline; cleanup and release reverify it without rewriting historical evidence or reopening lifecycle",
|
|
1521
|
+
"new_no_change": "new-format completions with an empty frozen prior cleanup set may clean at the exact terminal CAS; nonempty prior proof sets retain their original pre-completion CAS"
|
|
1522
|
+
},
|
|
1515
1523
|
"retained_typed_cleanup_version": 1,
|
|
1516
1524
|
"aborted_integration_preserved_cleanup_version": 1,
|
|
1517
1525
|
"aborted_integration_preserved_cleanup_contract": {
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/controller.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/disposable.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/environment.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/frozen_recovery.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/git_backend.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/governance.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/hardlinks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/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.32.2 → worktree_controller-1.32.5}/src/worktree_controller/owner_actions.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/preservation.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/reconciler.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/record_updates.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/rendering.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/session.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/source_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/source_recovery.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/task_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/writer_lease.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.2 → worktree_controller-1.32.5}/src/worktree_controller/writer_session.py
RENAMED
|
File without changes
|