worktree-controller 1.30.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- worktree_controller/__init__.py +3 -0
- worktree_controller/activation_layout.py +483 -0
- worktree_controller/branch_retirement.py +1865 -0
- worktree_controller/canonical_refresh.py +150 -0
- worktree_controller/canonical_transition.py +324 -0
- worktree_controller/cleanup_attempts.py +442 -0
- worktree_controller/cleanup_evidence.py +75 -0
- worktree_controller/cleanup_recovery.py +912 -0
- worktree_controller/cli.py +1907 -0
- worktree_controller/controller.py +1974 -0
- worktree_controller/convergence.py +893 -0
- worktree_controller/delivered_closeout.py +928 -0
- worktree_controller/disposable.py +677 -0
- worktree_controller/environment.py +1038 -0
- worktree_controller/frozen_recovery.py +291 -0
- worktree_controller/git_backend.py +682 -0
- worktree_controller/governance.py +365 -0
- worktree_controller/hardlinks.py +294 -0
- worktree_controller/integration_cleanup.py +92 -0
- worktree_controller/integration_conflict.py +117 -0
- worktree_controller/integration_retry.py +526 -0
- worktree_controller/keyed_document_scope.py +162 -0
- worktree_controller/lifecycle.py +8992 -0
- worktree_controller/lifecycle_history.py +358 -0
- worktree_controller/lifecycle_observation.py +234 -0
- worktree_controller/metadata_codec.py +1694 -0
- worktree_controller/metadata_relocation.py +131 -0
- worktree_controller/models.py +546 -0
- worktree_controller/mount_diagnostics.py +324 -0
- worktree_controller/multiple_cleanup_recovery.py +730 -0
- worktree_controller/namespace_migration.py +1412 -0
- worktree_controller/native_session_recovery.py +171 -0
- worktree_controller/owner_actions.py +34 -0
- worktree_controller/policy.py +333 -0
- worktree_controller/preservation.py +983 -0
- worktree_controller/preserved_cleanup.py +849 -0
- worktree_controller/publication_execution.py +361 -0
- worktree_controller/reconciler.py +430 -0
- worktree_controller/recovery_guidance.py +205 -0
- worktree_controller/registration_backfill.py +166 -0
- worktree_controller/registration_prune.py +391 -0
- worktree_controller/rendering.py +179 -0
- worktree_controller/resource_admission.py +1736 -0
- worktree_controller/retirement_adapters.py +130 -0
- worktree_controller/retirement_execution.py +234 -0
- worktree_controller/semantics.py +459 -0
- worktree_controller/session.py +505 -0
- worktree_controller/session_admission.py +265 -0
- worktree_controller/source_cleanup.py +515 -0
- worktree_controller/source_preservation.py +692 -0
- worktree_controller/source_recovery.py +592 -0
- worktree_controller/store.py +1228 -0
- worktree_controller/tool_cli_contract.json +1791 -0
- worktree_controller/transaction_scope.py +876 -0
- worktree_controller/writer_lease.py +662 -0
- worktree_controller/writer_session.py +887 -0
- worktree_controller-1.30.4.dist-info/METADATA +3149 -0
- worktree_controller-1.30.4.dist-info/RECORD +61 -0
- worktree_controller-1.30.4.dist-info/WHEEL +4 -0
- worktree_controller-1.30.4.dist-info/entry_points.txt +5 -0
- worktree_controller-1.30.4.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Preserved cleanup authority for an explicitly aborted, unpublished integration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from . import cleanup_evidence
|
|
8
|
+
from .models import ControllerError
|
|
9
|
+
|
|
10
|
+
CONTRACT = "aborted-integration-preserved-cleanup/v1"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def abort_evidence(store, snapshot, item, record) -> dict[str, str]:
|
|
14
|
+
"""Bind cleanup to the producer's abort, never cleanliness or a stale source head.
|
|
15
|
+
|
|
16
|
+
Old abort receipts remain usable when exactly one matches. New aborts pin the
|
|
17
|
+
receipt's raw bytes in their history event, including repeated aborts after
|
|
18
|
+
quarantine. Neither path grants an admission cancellation or publication.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def invalid():
|
|
22
|
+
return ControllerError(
|
|
23
|
+
"INTEGRATION_ABORT_EVIDENCE_INVALID",
|
|
24
|
+
"one exact unpublished integration abort and retained candidate are required",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if record.role != "integration" or record.lifecycle not in {"INTEGRATION_ABORTED", "RELEASED"}:
|
|
28
|
+
raise invalid()
|
|
29
|
+
events = [
|
|
30
|
+
i for i, event in enumerate(record.history) if event.get("event") == "INTEGRATION_ABORTED"
|
|
31
|
+
]
|
|
32
|
+
if not events:
|
|
33
|
+
raise invalid()
|
|
34
|
+
position = events[-1]
|
|
35
|
+
event = record.history[position]
|
|
36
|
+
if (
|
|
37
|
+
event.get("owner") != record.owner
|
|
38
|
+
or event.get("to") != "INTEGRATION_ABORTED"
|
|
39
|
+
or any(row.get("event") != "RELEASED" for row in record.history[position + 1 :])
|
|
40
|
+
):
|
|
41
|
+
raise invalid()
|
|
42
|
+
if record.quarantine_observed_commit and record.quarantine_observed_commit != item.head:
|
|
43
|
+
raise invalid()
|
|
44
|
+
if any(
|
|
45
|
+
receipt.allocation_id == record.allocation_id
|
|
46
|
+
for receipt in snapshot.publication_receipts.values()
|
|
47
|
+
):
|
|
48
|
+
raise invalid()
|
|
49
|
+
if any(
|
|
50
|
+
intent.allocation_id == record.allocation_id
|
|
51
|
+
for intent in snapshot.integration_intents.values()
|
|
52
|
+
):
|
|
53
|
+
raise invalid()
|
|
54
|
+
expected = {
|
|
55
|
+
"kind": "integration_abort",
|
|
56
|
+
"schema_version": 1,
|
|
57
|
+
"repository_id": snapshot.repository_id,
|
|
58
|
+
"allocation_id": record.allocation_id,
|
|
59
|
+
"task_key": record.task_key,
|
|
60
|
+
"authority_key": record.authority_key,
|
|
61
|
+
"owner": record.owner,
|
|
62
|
+
"branch": item.branch,
|
|
63
|
+
"candidate_oid": item.head,
|
|
64
|
+
}
|
|
65
|
+
try:
|
|
66
|
+
bound = event.get("abort_receipt")
|
|
67
|
+
if bound is not None:
|
|
68
|
+
receipt_id = str(uuid.UUID(bound["receipt_id"]))
|
|
69
|
+
if receipt_id != bound["receipt_id"]:
|
|
70
|
+
raise invalid()
|
|
71
|
+
path = store.completion_receipts_dir / f"{receipt_id}.json"
|
|
72
|
+
value, digest = cleanup_evidence.read(path, bound["sha256"])
|
|
73
|
+
else:
|
|
74
|
+
matches = []
|
|
75
|
+
for path in sorted(store.completion_receipts_dir.glob("*.json")):
|
|
76
|
+
value, digest = cleanup_evidence.read(path)
|
|
77
|
+
if all(value.get(key) == wanted for key, wanted in expected.items()):
|
|
78
|
+
matches.append((path, value, digest))
|
|
79
|
+
if len(matches) != 1:
|
|
80
|
+
raise invalid()
|
|
81
|
+
path, value, digest = matches[0]
|
|
82
|
+
receipt_id = path.stem
|
|
83
|
+
if (
|
|
84
|
+
any(value.get(key) != wanted for key, wanted in expected.items())
|
|
85
|
+
or value.get("receipt_id") != receipt_id
|
|
86
|
+
or str(uuid.UUID(receipt_id)) != receipt_id
|
|
87
|
+
or not value.get("failure_reason")
|
|
88
|
+
):
|
|
89
|
+
raise invalid()
|
|
90
|
+
except (OSError, ValueError, KeyError, TypeError, ControllerError) as exc:
|
|
91
|
+
raise invalid() from exc
|
|
92
|
+
return {"contract": CONTRACT, "receipt_id": receipt_id, "path": str(path), "sha256": digest}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Accept one owner-resolved merge without changing the frozen integration contract."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import replace
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from .git_backend import commits_with_parents, inventory
|
|
10
|
+
from .integration_retry import _sources
|
|
11
|
+
from .models import ControllerError
|
|
12
|
+
from .semantics import integration_candidate_issues
|
|
13
|
+
from .store import MetadataStore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def resolve_integration(
|
|
17
|
+
repository_path: Path,
|
|
18
|
+
*,
|
|
19
|
+
target: Path,
|
|
20
|
+
expected_record_version: int,
|
|
21
|
+
owner: str,
|
|
22
|
+
expected_head: str,
|
|
23
|
+
finalizer_record_id: str,
|
|
24
|
+
) -> dict[str, Any]:
|
|
25
|
+
from .controller import status
|
|
26
|
+
from .lifecycle import _intent_for_allocation, _intent_payload, _updated_intent, _updated_record
|
|
27
|
+
from .semantics import assert_record_version as _cas
|
|
28
|
+
from .semantics import managed_record as _managed_record
|
|
29
|
+
|
|
30
|
+
evidence = finalizer_record_id.strip()
|
|
31
|
+
if not evidence or len(evidence) > 512 or "\x00" in evidence:
|
|
32
|
+
raise ControllerError(
|
|
33
|
+
"MISSING_FINALIZER_RECORD", "resolution requires exact Finalizer evidence"
|
|
34
|
+
)
|
|
35
|
+
repository = inventory(repository_path)
|
|
36
|
+
store = MetadataStore(repository.common_dir)
|
|
37
|
+
with store.lock(exclusive=True):
|
|
38
|
+
repository = inventory(repository_path)
|
|
39
|
+
snapshot = store._load_unlocked()
|
|
40
|
+
item, record = _managed_record(repository, snapshot, target)
|
|
41
|
+
_cas(record, expected_record_version)
|
|
42
|
+
intent = _intent_for_allocation(snapshot, record.allocation_id)
|
|
43
|
+
if record.owner != owner:
|
|
44
|
+
raise ControllerError("OWNER_MISMATCH", "resolution owner does not match")
|
|
45
|
+
if (
|
|
46
|
+
record.role != "integration"
|
|
47
|
+
or record.lifecycle != "ACTIVE"
|
|
48
|
+
or intent is None
|
|
49
|
+
or intent.state != "PREPARING_INTEGRATION"
|
|
50
|
+
):
|
|
51
|
+
raise ControllerError(
|
|
52
|
+
"INVALID_LIFECYCLE", "resolution requires an active claimed intent"
|
|
53
|
+
)
|
|
54
|
+
if (
|
|
55
|
+
snapshot.publication_lease
|
|
56
|
+
and snapshot.publication_lease.allocation_id == record.allocation_id
|
|
57
|
+
):
|
|
58
|
+
raise ControllerError(
|
|
59
|
+
"PUBLICATION_LEASE_HELD", "resolution cannot change publication authority"
|
|
60
|
+
)
|
|
61
|
+
event = intent.history[-1] if intent.history else {}
|
|
62
|
+
if (
|
|
63
|
+
event.get("event") != "CANDIDATE_CLAIMED"
|
|
64
|
+
or len(intent.source_handoffs) != 1
|
|
65
|
+
or event.get("pending_merge_source") != intent.source_handoffs[0]
|
|
66
|
+
or event.get("pending_merge_base") != intent.base_main_oid
|
|
67
|
+
or intent.candidate_oid != intent.base_main_oid
|
|
68
|
+
or record.task_commit != intent.base_main_oid
|
|
69
|
+
or record.base_commit != intent.base_main_oid
|
|
70
|
+
):
|
|
71
|
+
raise ControllerError("CONFLICT_RESOLUTION_NOT_PENDING", "no exact pending merge claim")
|
|
72
|
+
if item.detached or not item.clean or item.head != expected_head:
|
|
73
|
+
raise ControllerError(
|
|
74
|
+
"CANDIDATE_IDENTITY_MISMATCH", "resolution needs the exact clean attached HEAD"
|
|
75
|
+
)
|
|
76
|
+
_sources(repository, snapshot, store, intent)
|
|
77
|
+
topology = commits_with_parents(item.path, intent.base_main_oid, item.head)
|
|
78
|
+
parents = next((row for row in topology if row[0] == item.head), ())
|
|
79
|
+
if parents != (item.head, intent.base_main_oid, intent.source_handoffs[0]):
|
|
80
|
+
raise ControllerError(
|
|
81
|
+
"RESOLUTION_PARENT_MISMATCH",
|
|
82
|
+
"merge parents must be the pinned main and frozen source",
|
|
83
|
+
)
|
|
84
|
+
issues = integration_candidate_issues(item, record, snapshot, require_task_match=False)
|
|
85
|
+
if issues:
|
|
86
|
+
raise ControllerError(issues[0]["code"], issues[0]["message"])
|
|
87
|
+
updated = _updated_record(record, "INTEGRATION_CONFLICT_RESOLVED", task_commit=item.head)
|
|
88
|
+
resolved = _updated_intent(
|
|
89
|
+
intent,
|
|
90
|
+
"INTEGRATION_CONFLICT_RESOLVED",
|
|
91
|
+
candidate_oid=item.head,
|
|
92
|
+
revision=intent.revision + 1,
|
|
93
|
+
)
|
|
94
|
+
resolved = replace(
|
|
95
|
+
resolved,
|
|
96
|
+
history=(
|
|
97
|
+
*resolved.history[:-1],
|
|
98
|
+
{
|
|
99
|
+
**resolved.history[-1],
|
|
100
|
+
"resolved_oid": item.head,
|
|
101
|
+
"base_oid": intent.base_main_oid,
|
|
102
|
+
"source_oid": intent.source_handoffs[0],
|
|
103
|
+
"finalizer_record_id": evidence,
|
|
104
|
+
},
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
store.replace_record(updated)
|
|
108
|
+
store.replace_integration_intent(resolved)
|
|
109
|
+
payload = status(repository_path)
|
|
110
|
+
payload["command"] = "integration-resolve"
|
|
111
|
+
payload["decision"] = {
|
|
112
|
+
"code": "INTEGRATION_CONFLICT_RESOLVED",
|
|
113
|
+
"reason": "resolved merge accepted; fresh tests, Snapshot and validation are still required",
|
|
114
|
+
"record_version": updated.record_version,
|
|
115
|
+
**_intent_payload(resolved),
|
|
116
|
+
}
|
|
117
|
+
return payload
|