worktree-controller 1.32.1__tar.gz → 1.32.2__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.1 → worktree_controller-1.32.2}/PKG-INFO +5 -5
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/README.md +4 -4
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/architecture.md +2 -2
- worktree_controller-1.32.2/docs/scope.md +86 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/pyproject.toml +1 -1
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/pyproject.toml.orig +1 -1
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/__init__.py +1 -1
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/canonical_transition.py +2 -2
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/controller.py +4 -20
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/convergence.py +2 -2
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/delivered_closeout.py +6 -6
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/frozen_recovery.py +3 -3
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/integration_conflict.py +4 -3
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/integration_retry.py +10 -8
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/lifecycle.py +4 -64
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/reconciler.py +3 -2
- worktree_controller-1.32.2/src/worktree_controller/record_updates.py +54 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/registration_prune.py +2 -2
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/semantics.py +24 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/tool_cli_contract.json +1 -1
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/tool_cli_contract.json +1 -1
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/LICENSE +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/client-integration.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/daily-workflow.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/first-change.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/getting-started.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/operations.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/publishing.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/docs/reference.md +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/examples/change_task.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/examples/client_continuation.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/examples/quickstart.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/activation_layout.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/branch_retirement.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/canonical_refresh.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/cleanup_attempts.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/cleanup_evidence.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/cleanup_recovery.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/cli.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/disposable.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/environment.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/git_backend.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/governance.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/hardlinks.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/integration_cleanup.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/keyed_document_scope.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/lifecycle_history.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/lifecycle_observation.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/metadata_codec.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/metadata_relocation.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/models.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/mount_diagnostics.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/multiple_cleanup_recovery.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/namespace_migration.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/native_session_recovery.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/owner_actions.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/policy.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/preservation.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/preserved_cleanup.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/publication_execution.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/recovery_guidance.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/registration_backfill.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/rendering.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/resource_admission.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/retirement_adapters.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/retirement_execution.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/session.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/session_admission.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/source_cleanup.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/source_preservation.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/source_recovery.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/store.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/task_workflow.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/transaction_scope.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/writer_lease.py +0 -0
- {worktree_controller-1.32.1 → worktree_controller-1.32.2}/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.2
|
|
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.2 · 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.2
|
|
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.2 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.2
|
|
54
54
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
55
55
|
```
|
|
56
56
|
|
|
@@ -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.2 · 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.2
|
|
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.2 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.2
|
|
37
37
|
python examples/quickstart.py --workspace "$HOME/wc-first-run"
|
|
38
38
|
```
|
|
39
39
|
|
|
@@ -10,8 +10,8 @@ development and recovery paths; exact public interfaces live in the [contract](.
|
|
|
10
10
|
| --- | --- | --- |
|
|
11
11
|
| CLI parsing and routing | `cli.py`, command modules | Dispatch explicit operations; do not duplicate authority decisions |
|
|
12
12
|
| Git observations and mutations | `git_backend.py` | Git owns registrations, refs, paths, HEAD and cleanliness |
|
|
13
|
-
| State and durable evidence | `models.py`, `store.py`, `metadata_codec.py` | Validate stored identities; serialize mutations under the repository lock |
|
|
14
|
-
| Shared identity and semantic checks | `semantics.py` | Consume the caller's snapshot; do not acquire authority or write metadata |
|
|
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
|
+
| 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
15
|
| Budget admission | `resource_admission.py` | Preserve durable request identity and host reservations across retries |
|
|
16
16
|
| Writer entitlement | `writer_lease.py`, `writer_session.py`, `governance.py` | Bind the real process identity and exact operation/target |
|
|
17
17
|
| Lifecycle orchestration | `lifecycle.py` and focused recovery modules | Compose existing guards and append durable transition evidence |
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Scope and consumer surface (draft for owner decision)
|
|
2
|
+
|
|
3
|
+
This document records which parts of the public CLI are generic worktree governance and which
|
|
4
|
+
exist mainly for one consumer. It is an inventory plus proposed boundaries; nothing here changes
|
|
5
|
+
behavior, and every "proposed" line needs an owner decision before code moves.
|
|
6
|
+
|
|
7
|
+
Snapshot: CLI contract 61, 81 commands, 65 `*_version` capability keys. The public contract
|
|
8
|
+
counts were rechecked on 2026-10-23; consumer mappings below are the cloud audit
|
|
9
|
+
reported in the maintainer handoff, not a runtime trace or proof of exclusive use.
|
|
10
|
+
|
|
11
|
+
## How the inventory was made
|
|
12
|
+
|
|
13
|
+
- **Agent Workspace (AW):** every `_invoke(...)` call in AW's controller client
|
|
14
|
+
(`backend/src/agent_workspace/infrastructure/worktree_controller.py`) plus its session-retirement
|
|
15
|
+
and external-execution modules. This was an exact static match in that audit, not a guarantee
|
|
16
|
+
that dynamic or other consumers do not exist.
|
|
17
|
+
- **Git Finalizer (GF):** word match over `git-finalize*` and `tooling/*.py`. Common words
|
|
18
|
+
(`review`, `task`, `release`, `resume`, `status`, `doctor`) can be false positives; the
|
|
19
|
+
retirement and publication entries are confirmed by GF's adapter code.
|
|
20
|
+
- **Docs/examples:** word match over `README.md`, `docs/`, `examples/` and the shipped Skill.
|
|
21
|
+
|
|
22
|
+
## 1. Generic governance core (keep; any client)
|
|
23
|
+
|
|
24
|
+
Checkout budget, ownership and safe release for any person or agent sharing a repository:
|
|
25
|
+
|
|
26
|
+
`plan`, `status`, `doctor`, `capabilities`, `acquire`, `adopt`, `park`, `reopen`, `release`,
|
|
27
|
+
`release-intent`, `quarantine`, `review`, `handoff`, `resume`, `task`, `complete-no-change`,
|
|
28
|
+
`recovery-guide`, `lifecycle-history`, `write-guard`, `writer-lease-acquire`,
|
|
29
|
+
`writer-lease-release`, `writer-session`, `lease-acquire`, `lease-complete`, `lease-abort`,
|
|
30
|
+
`integration-enqueue`, `integration-claim`, `integration-validate`, `integration-queue`,
|
|
31
|
+
`integration-refresh`, `reconcile`.
|
|
32
|
+
|
|
33
|
+
## 2. Delivery-tool protocols (keep; versioned, consumed by GF)
|
|
34
|
+
|
|
35
|
+
`branch-retirement-plan`, `-verify`, `-authorize`, `-complete`, `-record`, `-status`,
|
|
36
|
+
`-executions`, and `publication-execution-session`. These are the public protocols GF consumes
|
|
37
|
+
through capabilities; they should evolve by capability version, not by CLI contract number.
|
|
38
|
+
|
|
39
|
+
## 3. Host-runtime integration (used by AW today)
|
|
40
|
+
|
|
41
|
+
| Command | Used by AW | Note |
|
|
42
|
+
| --- | --- | --- |
|
|
43
|
+
| `host-execution` | yes | Budgets a host process with no repository writer lease; default `--client-id` is `agent-workspace` |
|
|
44
|
+
| `execution-admit`, `execution-cancel` | yes | Durable admission of governed executions |
|
|
45
|
+
| `admission-queue` | yes | Periodic queue reconciliation |
|
|
46
|
+
| `binding-add`, `binding-remove`, `binding-transfer` | yes | Runtime bindings owned by a host |
|
|
47
|
+
| `lifecycle-events`, `lifecycle-event-ack` | yes | Event feed with consumer acknowledgement |
|
|
48
|
+
| `session-admit`, `session-bind`, `reconcile-session` | not found in AW's client | Need an owner answer: other host, legacy, or removable |
|
|
49
|
+
|
|
50
|
+
**Proposed:** keep these in WC only if they are documented as a generic *host runtime protocol*
|
|
51
|
+
(any host, explicit `--client-id`, no AW-specific defaults or wording). Otherwise move the
|
|
52
|
+
AW-specific policy (memory budgets, default identities) into AW and keep WC's part to admission
|
|
53
|
+
and binding records.
|
|
54
|
+
|
|
55
|
+
## 4. Recovery and maintenance tail
|
|
56
|
+
|
|
57
|
+
`recover-handoff`, `recover-published`, `recovery-adopt`, `release-recovery-inspect`,
|
|
58
|
+
`integration-abort`, `-abandon`, `-reopen`, `-resolve`, `-retry`, `-equivalent`,
|
|
59
|
+
`-recovery-matrix`, `close-delivered`, `close-preserved`, `adopt-delivered`, `archive-retained`,
|
|
60
|
+
`externalize`, `resource-register`, `resource-clean`, `resource-recover`, `gc-plan`, `gc-apply`,
|
|
61
|
+
`reconcile-writers`, `acquire-across`, `acquire-when-free`, `canonical-refresh`,
|
|
62
|
+
`canonical-transition`, `namespace-migration-plan`, `-verify`, `-receipts`, `namespace-migrate`.
|
|
63
|
+
|
|
64
|
+
Most of these have no consumer in AW or GF and appear only in reference docs, or not at all.
|
|
65
|
+
|
|
66
|
+
**Proposed:** classify each as *supported*, *operator-only* or *deprecated* in the CLI contract,
|
|
67
|
+
publish a deprecation window for the last group, and stop adding a new top-level command per
|
|
68
|
+
recovery case — prefer one `recover` entry with typed subcases.
|
|
69
|
+
|
|
70
|
+
## Open questions for the owner
|
|
71
|
+
|
|
72
|
+
1. Is `host-execution` meant for hosts other than AW? If not, should its policy move to AW?
|
|
73
|
+
2. Who uses `session-admit`, `session-bind` and `reconcile-session`?
|
|
74
|
+
3. Which section-4 commands may be deprecated, and on what timeline?
|
|
75
|
+
|
|
76
|
+
## Current compatibility decision
|
|
77
|
+
|
|
78
|
+
Keep every existing command supported while these ownership questions are unresolved. Do not
|
|
79
|
+
remove host commands, change default identities, relax recovery guards or deprecate commands
|
|
80
|
+
from an absence of static matches. New hosts use explicit client/session identities and the
|
|
81
|
+
versioned public contracts described in [client integration](client-integration.md). WC remains
|
|
82
|
+
the lifecycle and policy authority; consumers retain their business and release approvals.
|
|
83
|
+
|
|
84
|
+
Effective integration-state interpretation lives in `semantics.py`;
|
|
85
|
+
record and intent updates live in `record_updates.py`. These moves preserve CLI contract 61 and
|
|
86
|
+
storage layout 5. See [architecture](architecture.md) for module responsibilities.
|
|
@@ -19,7 +19,6 @@ from .git_backend import (
|
|
|
19
19
|
update_branch_ref,
|
|
20
20
|
validate_branch_name,
|
|
21
21
|
)
|
|
22
|
-
from .lifecycle import _updated_record
|
|
23
22
|
from .models import (
|
|
24
23
|
ControllerError,
|
|
25
24
|
MetadataRecord,
|
|
@@ -29,6 +28,7 @@ from .models import (
|
|
|
29
28
|
WorktreePolicy,
|
|
30
29
|
)
|
|
31
30
|
from .policy import default_policy, load_policy, parse_policy
|
|
31
|
+
from .record_updates import updated_record
|
|
32
32
|
from .semantics import managed_record as _managed_record
|
|
33
33
|
from .store import MetadataStore
|
|
34
34
|
from .transaction_scope import conflicts
|
|
@@ -274,7 +274,7 @@ def transition_canonical(
|
|
|
274
274
|
if not refresh_main and item.branch == expected_branch:
|
|
275
275
|
git_write(item.path, ("checkout", "--no-overwrite-ignore", expected_branch))
|
|
276
276
|
raise
|
|
277
|
-
updated =
|
|
277
|
+
updated = updated_record(
|
|
278
278
|
record,
|
|
279
279
|
"CANONICAL_MAIN_REFRESHED" if refresh_main else "CANONICAL_TRANSITIONED",
|
|
280
280
|
lifecycle="ACTIVE",
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/controller.py
RENAMED
|
@@ -33,9 +33,9 @@ from .mount_diagnostics import diagnose_mounts, mount_warnings
|
|
|
33
33
|
from .policy import load_policy
|
|
34
34
|
from .semantics import (
|
|
35
35
|
EVIDENCE_LIFECYCLES,
|
|
36
|
+
effective_intent_state,
|
|
36
37
|
evidence_head_issues,
|
|
37
38
|
integration_candidate_issues,
|
|
38
|
-
integration_intent_abandoned,
|
|
39
39
|
integration_queue_order,
|
|
40
40
|
normalize_scope_paths,
|
|
41
41
|
path_scope_issues,
|
|
@@ -248,22 +248,6 @@ def _intent_for_allocation(snapshot: MetadataSnapshot, allocation_id: str | None
|
|
|
248
248
|
return matches[0] if matches else None
|
|
249
249
|
|
|
250
250
|
|
|
251
|
-
def _effective_intent_state(snapshot: MetadataSnapshot, intent: Any) -> str:
|
|
252
|
-
reconciliations = [
|
|
253
|
-
receipt
|
|
254
|
-
for receipt in snapshot.integration_reconciliation_receipts.values()
|
|
255
|
-
if receipt.intent_id == intent.intent_id
|
|
256
|
-
and receipt.result == "STALE_INTENT_TERMINAL_ACKNOWLEDGED"
|
|
257
|
-
]
|
|
258
|
-
if len(reconciliations) > 1:
|
|
259
|
-
raise ControllerError(
|
|
260
|
-
"INVALID_METADATA", "integration intent has multiple terminal reconciliations"
|
|
261
|
-
)
|
|
262
|
-
if reconciliations:
|
|
263
|
-
return reconciliations[0].effective_terminal_state
|
|
264
|
-
return "ABANDONED" if integration_intent_abandoned(intent) else intent.state
|
|
265
|
-
|
|
266
|
-
|
|
267
251
|
def _integration_intent_order(intent: Any) -> tuple[int, int | str, str]:
|
|
268
252
|
if intent.queue_sequence > 0:
|
|
269
253
|
return (1, intent.queue_sequence, intent.intent_id)
|
|
@@ -400,7 +384,7 @@ def _repository_payload(
|
|
|
400
384
|
(
|
|
401
385
|
intent
|
|
402
386
|
for intent in snapshot.integration_intents.values()
|
|
403
|
-
if
|
|
387
|
+
if effective_intent_state(snapshot, intent) in {"QUEUED", "WAITING_SCOPE"}
|
|
404
388
|
),
|
|
405
389
|
key=lambda value: integration_queue_order(snapshot, value),
|
|
406
390
|
)
|
|
@@ -543,7 +527,7 @@ def _worktree_payload(
|
|
|
543
527
|
"source_allocations": list(record.source_allocations) if record else [],
|
|
544
528
|
"integration_intent_id": intent.intent_id if intent else None,
|
|
545
529
|
"integration_intent_state": (
|
|
546
|
-
|
|
530
|
+
effective_intent_state(snapshot, intent)
|
|
547
531
|
if intent is not None and snapshot is not None
|
|
548
532
|
else None
|
|
549
533
|
),
|
|
@@ -1317,7 +1301,7 @@ def _semantic_warnings(
|
|
|
1317
1301
|
"RELEASED": {"RELEASED"},
|
|
1318
1302
|
}
|
|
1319
1303
|
for intent in snapshot.integration_intents.values():
|
|
1320
|
-
effective_state =
|
|
1304
|
+
effective_state = effective_intent_state(snapshot, intent)
|
|
1321
1305
|
if effective_state in {"QUEUED", "WAITING_SCOPE", "ABANDONED"}:
|
|
1322
1306
|
continue
|
|
1323
1307
|
if unallocated_integration_conflict(intent):
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/convergence.py
RENAMED
|
@@ -199,13 +199,13 @@ def _is_candidate(record: MetadataRecord) -> bool:
|
|
|
199
199
|
|
|
200
200
|
|
|
201
201
|
def _integration_bound(snapshot: MetadataSnapshot, record: MetadataRecord) -> bool:
|
|
202
|
-
from .
|
|
202
|
+
from .semantics import effective_intent_state
|
|
203
203
|
|
|
204
204
|
return any(
|
|
205
205
|
intent.allocation_id == record.allocation_id
|
|
206
206
|
or record.allocation_id in intent.source_allocations
|
|
207
207
|
for intent in snapshot.integration_intents.values()
|
|
208
|
-
if
|
|
208
|
+
if effective_intent_state(snapshot, intent) not in TERMINAL_INTEGRATION_STATES
|
|
209
209
|
) or any(
|
|
210
210
|
other.lifecycle != "RELEASED" and record.allocation_id in other.source_allocations
|
|
211
211
|
for other in snapshot.records.values()
|
|
@@ -345,11 +345,11 @@ def _eligible(
|
|
|
345
345
|
for_typed_cleanup: bool = False,
|
|
346
346
|
) -> dict[str, str]:
|
|
347
347
|
from .lifecycle import (
|
|
348
|
-
_effective_intent_state,
|
|
349
348
|
_typed_runtime_blockers,
|
|
350
349
|
_typed_runtime_evidence,
|
|
351
350
|
_validate_release_environment,
|
|
352
351
|
)
|
|
352
|
+
from .semantics import effective_intent_state
|
|
353
353
|
|
|
354
354
|
if for_typed_cleanup and (state != "RETAINED" or require_publication):
|
|
355
355
|
raise ControllerError("INVALID_LIFECYCLE", "typed cleanup does not authorize publication")
|
|
@@ -410,7 +410,7 @@ def _eligible(
|
|
|
410
410
|
record.allocation_id in intent.source_allocations
|
|
411
411
|
and (
|
|
412
412
|
not (
|
|
413
|
-
(effective_state :=
|
|
413
|
+
(effective_state := effective_intent_state(snapshot, intent)) == "ABANDONED"
|
|
414
414
|
or (not require_publication and effective_state == "RELEASED")
|
|
415
415
|
)
|
|
416
416
|
or not is_ancestor(
|
|
@@ -662,7 +662,7 @@ def close_delivered(
|
|
|
662
662
|
plan_id: str | None = None,
|
|
663
663
|
) -> dict[str, Any]:
|
|
664
664
|
from .controller import envelope
|
|
665
|
-
from .
|
|
665
|
+
from .record_updates import updated_record
|
|
666
666
|
from .semantics import assert_record_version as _cas
|
|
667
667
|
from .semantics import managed_record as _managed_record
|
|
668
668
|
|
|
@@ -758,7 +758,7 @@ def close_delivered(
|
|
|
758
758
|
"plan_id": identity,
|
|
759
759
|
"plan": plan,
|
|
760
760
|
}
|
|
761
|
-
updated =
|
|
761
|
+
updated = updated_record(record, EVENT, lifecycle=STATE)
|
|
762
762
|
event = {
|
|
763
763
|
**updated.history[-1],
|
|
764
764
|
"closeout_receipt_id": receipt_id,
|
|
@@ -803,7 +803,7 @@ def archive_retained(
|
|
|
803
803
|
) -> dict[str, Any]:
|
|
804
804
|
"""Freeze a clean, unfinished retained task for guarded physical release."""
|
|
805
805
|
from .controller import envelope
|
|
806
|
-
from .
|
|
806
|
+
from .record_updates import updated_record
|
|
807
807
|
from .semantics import assert_record_version as _cas
|
|
808
808
|
from .semantics import managed_record as _managed_record
|
|
809
809
|
|
|
@@ -896,7 +896,7 @@ def archive_retained(
|
|
|
896
896
|
"plan_id": identity,
|
|
897
897
|
"plan": plan,
|
|
898
898
|
}
|
|
899
|
-
updated =
|
|
899
|
+
updated = updated_record(record, ARCHIVE_EVENT, lifecycle=ARCHIVE_STATE)
|
|
900
900
|
event = {
|
|
901
901
|
**updated.history[-1],
|
|
902
902
|
"archive_receipt_id": receipt_id,
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/frozen_recovery.py
RENAMED
|
@@ -20,10 +20,10 @@ from .lifecycle import (
|
|
|
20
20
|
_cancel_release_intents,
|
|
21
21
|
_typed_runtime_blockers,
|
|
22
22
|
_typed_runtime_evidence,
|
|
23
|
-
_updated_record,
|
|
24
23
|
)
|
|
25
24
|
from .models import ControllerError
|
|
26
25
|
from .policy import load_policy
|
|
26
|
+
from .record_updates import updated_record
|
|
27
27
|
from .semantics import assert_record_version as _cas
|
|
28
28
|
from .semantics import integration_intent_abandoned, publication_ref
|
|
29
29
|
from .semantics import managed_record as _managed_record
|
|
@@ -245,7 +245,7 @@ def recover_frozen_checkout(
|
|
|
245
245
|
git_write(item.path, ("symbolic-ref", "HEAD", f"refs/heads/{branch}"))
|
|
246
246
|
elif latest.branch != branch:
|
|
247
247
|
raise ControllerError("BLOCK_DRIFT", "checkout branch changed during recovery")
|
|
248
|
-
updated =
|
|
248
|
+
updated = updated_record(record, "PUBLISHED_CHECKOUT_REATTACHED")
|
|
249
249
|
else:
|
|
250
250
|
_cancel_release_intents(
|
|
251
251
|
store,
|
|
@@ -254,7 +254,7 @@ def recover_frozen_checkout(
|
|
|
254
254
|
event="FROZEN_SOURCE_RECOVERED",
|
|
255
255
|
blocker="REOPENED",
|
|
256
256
|
)
|
|
257
|
-
updated =
|
|
257
|
+
updated = updated_record(
|
|
258
258
|
record,
|
|
259
259
|
"PUBLISHED_SOURCE_RECOVERED",
|
|
260
260
|
lifecycle="ACTIVE",
|
|
@@ -23,7 +23,8 @@ def resolve_integration(
|
|
|
23
23
|
finalizer_record_id: str,
|
|
24
24
|
) -> dict[str, Any]:
|
|
25
25
|
from .controller import status
|
|
26
|
-
from .lifecycle import _intent_for_allocation, _intent_payload
|
|
26
|
+
from .lifecycle import _intent_for_allocation, _intent_payload
|
|
27
|
+
from .record_updates import updated_intent, updated_record
|
|
27
28
|
from .semantics import assert_record_version as _cas
|
|
28
29
|
from .semantics import managed_record as _managed_record
|
|
29
30
|
|
|
@@ -84,8 +85,8 @@ def resolve_integration(
|
|
|
84
85
|
issues = integration_candidate_issues(item, record, snapshot, require_task_match=False)
|
|
85
86
|
if issues:
|
|
86
87
|
raise ControllerError(issues[0]["code"], issues[0]["message"])
|
|
87
|
-
updated =
|
|
88
|
-
resolved =
|
|
88
|
+
updated = updated_record(record, "INTEGRATION_CONFLICT_RESOLVED", task_commit=item.head)
|
|
89
|
+
resolved = updated_intent(
|
|
89
90
|
intent,
|
|
90
91
|
"INTEGRATION_CONFLICT_RESOLVED",
|
|
91
92
|
candidate_oid=item.head,
|
|
@@ -227,7 +227,8 @@ def _prerequisite(
|
|
|
227
227
|
parent: IntegrationIntent,
|
|
228
228
|
child: IntegrationIntent,
|
|
229
229
|
) -> dict[str, Any]:
|
|
230
|
-
from .lifecycle import
|
|
230
|
+
from .lifecycle import _scopes_overlap
|
|
231
|
+
from .semantics import effective_intent_state
|
|
231
232
|
|
|
232
233
|
if (
|
|
233
234
|
child.owner != parent.owner
|
|
@@ -255,7 +256,7 @@ def _prerequisite(
|
|
|
255
256
|
if any(
|
|
256
257
|
other.intent_id != parent.intent_id
|
|
257
258
|
and other.allocation_id is not None
|
|
258
|
-
and
|
|
259
|
+
and effective_intent_state(snapshot, other) not in {"RELEASED", "ABANDONED"}
|
|
259
260
|
and other.queue_sequence > parent.queue_sequence
|
|
260
261
|
and _scopes_overlap(other.scope_paths, child.scope_paths)
|
|
261
262
|
for other in snapshot.integration_intents.values()
|
|
@@ -295,7 +296,7 @@ def _plan(
|
|
|
295
296
|
prerequisite_id: str | None,
|
|
296
297
|
reason: str,
|
|
297
298
|
) -> dict[str, Any]:
|
|
298
|
-
from .
|
|
299
|
+
from .semantics import effective_intent_state
|
|
299
300
|
|
|
300
301
|
failure = _failure(repository, snapshot, store, intent)
|
|
301
302
|
source_records = _sources(repository, snapshot, store, intent)
|
|
@@ -324,7 +325,7 @@ def _plan(
|
|
|
324
325
|
bound = integration_prerequisite(intent)
|
|
325
326
|
if bound:
|
|
326
327
|
child = snapshot.integration_intents.get(bound)
|
|
327
|
-
if child is None or
|
|
328
|
+
if child is None or effective_intent_state(snapshot, child) != "RELEASED":
|
|
328
329
|
raise ControllerError(
|
|
329
330
|
"RETRY_PREREQUISITE_PENDING",
|
|
330
331
|
"prerequisite needs formal publication and release",
|
|
@@ -379,7 +380,7 @@ def _plan(
|
|
|
379
380
|
"queue_evidence": {
|
|
380
381
|
other.intent_id: _digest(integration_intent_to_dict(other))
|
|
381
382
|
for other in snapshot.integration_intents.values()
|
|
382
|
-
if
|
|
383
|
+
if effective_intent_state(snapshot, other) not in {"RELEASED", "ABANDONED"}
|
|
383
384
|
},
|
|
384
385
|
}
|
|
385
386
|
return {**value, "plan_id": _digest(value)}
|
|
@@ -397,7 +398,8 @@ def retry_integration(
|
|
|
397
398
|
) -> dict[str, Any]:
|
|
398
399
|
"""Plan/CAS an append-only dependency decision or restart the original intent."""
|
|
399
400
|
from .controller import status
|
|
400
|
-
from .lifecycle import _blocking_scope_intent, _emit_lifecycle_event
|
|
401
|
+
from .lifecycle import _blocking_scope_intent, _emit_lifecycle_event
|
|
402
|
+
from .record_updates import updated_intent
|
|
401
403
|
|
|
402
404
|
detail = reason.strip()
|
|
403
405
|
if not detail or len(detail) > 512 or "\x00" in detail:
|
|
@@ -451,9 +453,9 @@ def retry_integration(
|
|
|
451
453
|
raise ControllerError("BLOCK_DRIFT", "retry plan evidence changed; plan again")
|
|
452
454
|
if not dry_run:
|
|
453
455
|
if prerequisite_intent_id:
|
|
454
|
-
updated =
|
|
456
|
+
updated = updated_intent(intent, "RETRY_PREREQUISITE_BOUND")
|
|
455
457
|
else:
|
|
456
|
-
updated =
|
|
458
|
+
updated = updated_intent(
|
|
457
459
|
intent,
|
|
458
460
|
"PREPARATION_RETRY_QUEUED",
|
|
459
461
|
state="QUEUED",
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/lifecycle.py
RENAMED
|
@@ -77,6 +77,8 @@ from .models import (
|
|
|
77
77
|
from .policy import load_policy
|
|
78
78
|
from .preserved_cleanup import CONTRACT as PRESERVED_CLEANUP_CONTRACT
|
|
79
79
|
from .preserved_cleanup import coverage, delete_preserved_state
|
|
80
|
+
from .record_updates import updated_intent as _updated_intent
|
|
81
|
+
from .record_updates import updated_record as _updated_record
|
|
80
82
|
from .semantics import assert_record_version as _cas
|
|
81
83
|
from .semantics import (
|
|
82
84
|
attribution_base,
|
|
@@ -90,6 +92,8 @@ from .semantics import (
|
|
|
90
92
|
path_scope_issues,
|
|
91
93
|
publication_ref,
|
|
92
94
|
)
|
|
95
|
+
from .semantics import effective_intent_state as _effective_intent_state
|
|
96
|
+
from .semantics import integration_reconciliation as _integration_reconciliation
|
|
93
97
|
from .semantics import managed_record as _managed_record
|
|
94
98
|
from .semantics import record_for as _record_for
|
|
95
99
|
from .store import (
|
|
@@ -106,11 +110,9 @@ from .store import (
|
|
|
106
110
|
RELEASE_RECEIPT_SCHEMA_VERSION,
|
|
107
111
|
MetadataStore,
|
|
108
112
|
integration_intent_from_dict,
|
|
109
|
-
integration_intent_to_dict,
|
|
110
113
|
lifecycle_event_from_dict,
|
|
111
114
|
new_record,
|
|
112
115
|
now_iso,
|
|
113
|
-
record_from_dict,
|
|
114
116
|
record_to_dict,
|
|
115
117
|
release_intent_from_dict,
|
|
116
118
|
release_intent_to_dict,
|
|
@@ -146,46 +148,6 @@ def _crash_point(name: str) -> None:
|
|
|
146
148
|
os._exit(97)
|
|
147
149
|
|
|
148
150
|
|
|
149
|
-
def _updated_record(record: MetadataRecord, event: str, **changes: Any) -> MetadataRecord:
|
|
150
|
-
payload = record_to_dict(record)
|
|
151
|
-
before = record.lifecycle
|
|
152
|
-
timestamp = now_iso()
|
|
153
|
-
payload.update(changes)
|
|
154
|
-
payload["record_version"] = record.record_version + 1
|
|
155
|
-
payload["last_reviewed_at"] = timestamp
|
|
156
|
-
history = list(record.history)
|
|
157
|
-
history.append(
|
|
158
|
-
{
|
|
159
|
-
"at": timestamp,
|
|
160
|
-
"event": event,
|
|
161
|
-
"from": before,
|
|
162
|
-
"to": payload.get("lifecycle", before),
|
|
163
|
-
"owner": payload.get("owner", record.owner),
|
|
164
|
-
}
|
|
165
|
-
)
|
|
166
|
-
payload["history"] = history
|
|
167
|
-
return record_from_dict(payload)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _updated_intent(intent: IntegrationIntent, event: str, **changes: Any) -> IntegrationIntent:
|
|
171
|
-
payload = integration_intent_to_dict(intent)
|
|
172
|
-
timestamp = now_iso()
|
|
173
|
-
payload.update(changes)
|
|
174
|
-
payload["updated_at"] = timestamp
|
|
175
|
-
history = list(intent.history)
|
|
176
|
-
history.append(
|
|
177
|
-
{
|
|
178
|
-
"at": timestamp,
|
|
179
|
-
"event": event,
|
|
180
|
-
"from": intent.state,
|
|
181
|
-
"to": payload.get("state", intent.state),
|
|
182
|
-
"candidate_oid": payload.get("candidate_oid"),
|
|
183
|
-
}
|
|
184
|
-
)
|
|
185
|
-
payload["history"] = history
|
|
186
|
-
return integration_intent_from_dict(payload)
|
|
187
|
-
|
|
188
|
-
|
|
189
151
|
def _scope_digest(scope_paths: tuple[str, ...]) -> str:
|
|
190
152
|
encoded = json.dumps(list(scope_paths), ensure_ascii=True, separators=(",", ":"))
|
|
191
153
|
return hashlib.sha256(encoded.encode("utf-8")).hexdigest()
|
|
@@ -211,28 +173,6 @@ def _scopes_overlap(left: tuple[str, ...], right: tuple[str, ...]) -> bool:
|
|
|
211
173
|
return False
|
|
212
174
|
|
|
213
175
|
|
|
214
|
-
def _integration_reconciliation(
|
|
215
|
-
snapshot: MetadataSnapshot, intent_id: str
|
|
216
|
-
) -> IntegrationReconciliationReceipt | None:
|
|
217
|
-
matches = [
|
|
218
|
-
receipt
|
|
219
|
-
for receipt in snapshot.integration_reconciliation_receipts.values()
|
|
220
|
-
if receipt.intent_id == intent_id and receipt.result == "STALE_INTENT_TERMINAL_ACKNOWLEDGED"
|
|
221
|
-
]
|
|
222
|
-
if len(matches) > 1:
|
|
223
|
-
raise ControllerError(
|
|
224
|
-
"INVALID_METADATA", "integration intent has multiple terminal reconciliations"
|
|
225
|
-
)
|
|
226
|
-
return matches[0] if matches else None
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def _effective_intent_state(snapshot: MetadataSnapshot, intent: IntegrationIntent) -> str:
|
|
230
|
-
receipt = _integration_reconciliation(snapshot, intent.intent_id)
|
|
231
|
-
if receipt is not None:
|
|
232
|
-
return receipt.effective_terminal_state
|
|
233
|
-
return "ABANDONED" if integration_intent_abandoned(intent) else intent.state
|
|
234
|
-
|
|
235
|
-
|
|
236
176
|
def _released_intent_evidence(
|
|
237
177
|
snapshot: MetadataSnapshot,
|
|
238
178
|
intent: IntegrationIntent,
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/reconciler.py
RENAMED
|
@@ -20,7 +20,7 @@ from typing import Any
|
|
|
20
20
|
|
|
21
21
|
from .controller import status
|
|
22
22
|
from .git_backend import inventory
|
|
23
|
-
from .lifecycle import _cancel_release_intents
|
|
23
|
+
from .lifecycle import _cancel_release_intents
|
|
24
24
|
from .models import (
|
|
25
25
|
ControllerError,
|
|
26
26
|
MetadataRecord,
|
|
@@ -28,6 +28,7 @@ from .models import (
|
|
|
28
28
|
WriterReconciliationReceipt,
|
|
29
29
|
)
|
|
30
30
|
from .policy import load_policy
|
|
31
|
+
from .record_updates import updated_record
|
|
31
32
|
from .semantics import record_for as _record_for
|
|
32
33
|
from .store import WRITER_RECEIPT_SCHEMA_VERSION, MetadataStore
|
|
33
34
|
from .writer_lease import (
|
|
@@ -278,7 +279,7 @@ def reconcile_writers(
|
|
|
278
279
|
event="RECORD_CHANGED",
|
|
279
280
|
blocker="PARKED",
|
|
280
281
|
)
|
|
281
|
-
updated =
|
|
282
|
+
updated = updated_record(record, "PARKED", lifecycle="RETAINED")
|
|
282
283
|
store.replace_record(updated)
|
|
283
284
|
store.remove_writer_lease(record.allocation_id)
|
|
284
285
|
receipt = WriterReconciliationReceipt(
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Versioned, history-preserving updates of allocation records and integration intents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from .metadata_codec import (
|
|
8
|
+
integration_intent_from_dict,
|
|
9
|
+
integration_intent_to_dict,
|
|
10
|
+
record_from_dict,
|
|
11
|
+
record_to_dict,
|
|
12
|
+
)
|
|
13
|
+
from .models import IntegrationIntent, MetadataRecord
|
|
14
|
+
from .store import now_iso
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def updated_record(record: MetadataRecord, event: str, **changes: Any) -> MetadataRecord:
|
|
18
|
+
payload = record_to_dict(record)
|
|
19
|
+
before = record.lifecycle
|
|
20
|
+
timestamp = now_iso()
|
|
21
|
+
payload.update(changes)
|
|
22
|
+
payload["record_version"] = record.record_version + 1
|
|
23
|
+
payload["last_reviewed_at"] = timestamp
|
|
24
|
+
history = list(record.history)
|
|
25
|
+
history.append(
|
|
26
|
+
{
|
|
27
|
+
"at": timestamp,
|
|
28
|
+
"event": event,
|
|
29
|
+
"from": before,
|
|
30
|
+
"to": payload.get("lifecycle", before),
|
|
31
|
+
"owner": payload.get("owner", record.owner),
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
payload["history"] = history
|
|
35
|
+
return record_from_dict(payload)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def updated_intent(intent: IntegrationIntent, event: str, **changes: Any) -> IntegrationIntent:
|
|
39
|
+
payload = integration_intent_to_dict(intent)
|
|
40
|
+
timestamp = now_iso()
|
|
41
|
+
payload.update(changes)
|
|
42
|
+
payload["updated_at"] = timestamp
|
|
43
|
+
history = list(intent.history)
|
|
44
|
+
history.append(
|
|
45
|
+
{
|
|
46
|
+
"at": timestamp,
|
|
47
|
+
"event": event,
|
|
48
|
+
"from": intent.state,
|
|
49
|
+
"to": payload.get("state", intent.state),
|
|
50
|
+
"candidate_oid": payload.get("candidate_oid"),
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
payload["history"] = history
|
|
54
|
+
return integration_intent_from_dict(payload)
|
|
@@ -20,7 +20,6 @@ from .git_backend import (
|
|
|
20
20
|
public_refs,
|
|
21
21
|
worktree_content_fingerprint,
|
|
22
22
|
)
|
|
23
|
-
from .lifecycle import _updated_record
|
|
24
23
|
from .models import (
|
|
25
24
|
ControllerError,
|
|
26
25
|
MetadataRecord,
|
|
@@ -29,6 +28,7 @@ from .models import (
|
|
|
29
28
|
WorktreeGitState,
|
|
30
29
|
WorktreePolicy,
|
|
31
30
|
)
|
|
31
|
+
from .record_updates import updated_record
|
|
32
32
|
from .store import MetadataStore, now_iso
|
|
33
33
|
|
|
34
34
|
CONTRACT = "registration-prune/v1"
|
|
@@ -343,7 +343,7 @@ def apply_prune(
|
|
|
343
343
|
snapshot = store._load_unlocked()
|
|
344
344
|
record = snapshot.records[journal["allocation_id"]]
|
|
345
345
|
if record.record_version == journal["expected_record_version"]:
|
|
346
|
-
updated =
|
|
346
|
+
updated = updated_record(
|
|
347
347
|
record, "REGISTRATION_PRUNED", lifecycle="RELEASED", owner="unassigned"
|
|
348
348
|
)
|
|
349
349
|
store.replace_record(updated)
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/semantics.py
RENAMED
|
@@ -17,6 +17,7 @@ from .git_backend import (
|
|
|
17
17
|
from .models import (
|
|
18
18
|
ControllerError,
|
|
19
19
|
IntegrationIntent,
|
|
20
|
+
IntegrationReconciliationReceipt,
|
|
20
21
|
MetadataRecord,
|
|
21
22
|
MetadataSnapshot,
|
|
22
23
|
RepositoryInventory,
|
|
@@ -79,6 +80,29 @@ def integration_intent_abandoned(intent: IntegrationIntent) -> bool:
|
|
|
79
80
|
)
|
|
80
81
|
|
|
81
82
|
|
|
83
|
+
def integration_reconciliation(
|
|
84
|
+
snapshot: MetadataSnapshot, intent_id: str
|
|
85
|
+
) -> IntegrationReconciliationReceipt | None:
|
|
86
|
+
matches = [
|
|
87
|
+
receipt
|
|
88
|
+
for receipt in snapshot.integration_reconciliation_receipts.values()
|
|
89
|
+
if receipt.intent_id == intent_id and receipt.result == "STALE_INTENT_TERMINAL_ACKNOWLEDGED"
|
|
90
|
+
]
|
|
91
|
+
if len(matches) > 1:
|
|
92
|
+
raise ControllerError(
|
|
93
|
+
"INVALID_METADATA", "integration intent has multiple terminal reconciliations"
|
|
94
|
+
)
|
|
95
|
+
return matches[0] if matches else None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def effective_intent_state(snapshot: MetadataSnapshot, intent: IntegrationIntent) -> str:
|
|
99
|
+
"""Interpret terminal evidence from the caller's snapshot without changing authority."""
|
|
100
|
+
receipt = integration_reconciliation(snapshot, intent.intent_id)
|
|
101
|
+
if receipt is not None:
|
|
102
|
+
return receipt.effective_terminal_state
|
|
103
|
+
return "ABANDONED" if integration_intent_abandoned(intent) else intent.state
|
|
104
|
+
|
|
105
|
+
|
|
82
106
|
def _current_attempt(intent: IntegrationIntent) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
|
|
83
107
|
"""Split history at the last formal retry: (earlier attempts, current attempt)."""
|
|
84
108
|
history = list(intent.history)
|
|
@@ -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.2",
|
|
1075
1075
|
"delivered_closeout_version": 1,
|
|
1076
1076
|
"retained_archive_version": 1,
|
|
1077
1077
|
"retention_convergence_version": 2,
|
|
@@ -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.2",
|
|
1075
1075
|
"delivered_closeout_version": 1,
|
|
1076
1076
|
"retained_archive_version": 1,
|
|
1077
1077
|
"retention_convergence_version": 2,
|
|
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
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/disposable.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/environment.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/git_backend.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/governance.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/hardlinks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/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.1 → worktree_controller-1.32.2}/src/worktree_controller/owner_actions.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/preservation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/rendering.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/session.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/source_cleanup.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/source_recovery.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/task_workflow.py
RENAMED
|
File without changes
|
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/writer_lease.py
RENAMED
|
File without changes
|
{worktree_controller-1.32.1 → worktree_controller-1.32.2}/src/worktree_controller/writer_session.py
RENAMED
|
File without changes
|