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.
Files changed (61) hide show
  1. worktree_controller/__init__.py +3 -0
  2. worktree_controller/activation_layout.py +483 -0
  3. worktree_controller/branch_retirement.py +1865 -0
  4. worktree_controller/canonical_refresh.py +150 -0
  5. worktree_controller/canonical_transition.py +324 -0
  6. worktree_controller/cleanup_attempts.py +442 -0
  7. worktree_controller/cleanup_evidence.py +75 -0
  8. worktree_controller/cleanup_recovery.py +912 -0
  9. worktree_controller/cli.py +1907 -0
  10. worktree_controller/controller.py +1974 -0
  11. worktree_controller/convergence.py +893 -0
  12. worktree_controller/delivered_closeout.py +928 -0
  13. worktree_controller/disposable.py +677 -0
  14. worktree_controller/environment.py +1038 -0
  15. worktree_controller/frozen_recovery.py +291 -0
  16. worktree_controller/git_backend.py +682 -0
  17. worktree_controller/governance.py +365 -0
  18. worktree_controller/hardlinks.py +294 -0
  19. worktree_controller/integration_cleanup.py +92 -0
  20. worktree_controller/integration_conflict.py +117 -0
  21. worktree_controller/integration_retry.py +526 -0
  22. worktree_controller/keyed_document_scope.py +162 -0
  23. worktree_controller/lifecycle.py +8992 -0
  24. worktree_controller/lifecycle_history.py +358 -0
  25. worktree_controller/lifecycle_observation.py +234 -0
  26. worktree_controller/metadata_codec.py +1694 -0
  27. worktree_controller/metadata_relocation.py +131 -0
  28. worktree_controller/models.py +546 -0
  29. worktree_controller/mount_diagnostics.py +324 -0
  30. worktree_controller/multiple_cleanup_recovery.py +730 -0
  31. worktree_controller/namespace_migration.py +1412 -0
  32. worktree_controller/native_session_recovery.py +171 -0
  33. worktree_controller/owner_actions.py +34 -0
  34. worktree_controller/policy.py +333 -0
  35. worktree_controller/preservation.py +983 -0
  36. worktree_controller/preserved_cleanup.py +849 -0
  37. worktree_controller/publication_execution.py +361 -0
  38. worktree_controller/reconciler.py +430 -0
  39. worktree_controller/recovery_guidance.py +205 -0
  40. worktree_controller/registration_backfill.py +166 -0
  41. worktree_controller/registration_prune.py +391 -0
  42. worktree_controller/rendering.py +179 -0
  43. worktree_controller/resource_admission.py +1736 -0
  44. worktree_controller/retirement_adapters.py +130 -0
  45. worktree_controller/retirement_execution.py +234 -0
  46. worktree_controller/semantics.py +459 -0
  47. worktree_controller/session.py +505 -0
  48. worktree_controller/session_admission.py +265 -0
  49. worktree_controller/source_cleanup.py +515 -0
  50. worktree_controller/source_preservation.py +692 -0
  51. worktree_controller/source_recovery.py +592 -0
  52. worktree_controller/store.py +1228 -0
  53. worktree_controller/tool_cli_contract.json +1791 -0
  54. worktree_controller/transaction_scope.py +876 -0
  55. worktree_controller/writer_lease.py +662 -0
  56. worktree_controller/writer_session.py +887 -0
  57. worktree_controller-1.30.4.dist-info/METADATA +3149 -0
  58. worktree_controller-1.30.4.dist-info/RECORD +61 -0
  59. worktree_controller-1.30.4.dist-info/WHEEL +4 -0
  60. worktree_controller-1.30.4.dist-info/entry_points.txt +5 -0
  61. 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