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,131 @@
1
+ """Exact immutable evidence references across the explicit one-time layout move.
2
+
3
+ This is not metadata discovery: only an explicit layout or namespace migration's
4
+ hash-bound inventory can relocate a historical reference. Current metadata authority
5
+ has one namespace.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import stat
12
+ from pathlib import Path
13
+
14
+ from . import preservation as p
15
+ from .disposable import _inode, _open_directory
16
+ from .models import ControllerError
17
+ from .preserved_cleanup import _document
18
+ from .store import ROOT_NAME, VERSION_NAME
19
+
20
+ CONTRACT = "metadata-reference-relocation/v1"
21
+ RECORD = "namespace-relocation.json"
22
+ DIRECTORIES = (
23
+ "transactions",
24
+ "completion-receipts",
25
+ "source-cleanup",
26
+ "cleanup-actions",
27
+ "cleanup-recoveries",
28
+ )
29
+
30
+
31
+ def _blocked() -> ControllerError:
32
+ return ControllerError("METADATA_RELOCATION_BLOCKED", "exact metadata relocation guard refused")
33
+
34
+
35
+ def inventory(root: Path) -> dict[str, str]:
36
+ """Only immutable cleanup JSON evidence; no provider files or mutable records."""
37
+ result = {}
38
+
39
+ def walk(directory: p._Directory, prefix: str) -> None:
40
+ directory.check()
41
+ names = sorted(os.listdir(directory.fd))
42
+ for name in names:
43
+ info = os.stat(name, dir_fd=directory.fd, follow_symlinks=False)
44
+ relative = f"{prefix}/{name}"
45
+ if stat.S_ISDIR(info.st_mode):
46
+ with _open_directory(directory.fd, name, _inode(info)) as child:
47
+ walk(child, relative)
48
+ elif stat.S_ISREG(info.st_mode) and name.endswith(".json"):
49
+ _, result[relative] = _document(directory, name)
50
+ else:
51
+ raise _blocked()
52
+ directory.check()
53
+ if sorted(os.listdir(directory.fd)) != names:
54
+ raise _blocked()
55
+
56
+ with p._absolute_directory(root) as chain:
57
+ parent = chain[-1]
58
+ for name in DIRECTORIES:
59
+ if name in os.listdir(parent.fd):
60
+ info = os.stat(name, dir_fd=parent.fd, follow_symlinks=False)
61
+ with _open_directory(parent.fd, name, _inode(info)) as child:
62
+ walk(child, name)
63
+ return result
64
+
65
+
66
+ def prepare(old: Path, new: Path) -> dict:
67
+ with p._absolute_directory(old) as chain:
68
+ info = os.fstat(chain[-1].fd)
69
+ value = {
70
+ "contract": CONTRACT,
71
+ "previous_root": str(old),
72
+ "current_root": str(new),
73
+ "root_identity": [info.st_dev, info.st_ino],
74
+ "files": inventory(old),
75
+ }
76
+ return {**value, "sha256": p._sha(value)}
77
+
78
+
79
+ def _record(root: Path) -> dict:
80
+ with p._absolute_directory(root) as chain:
81
+ value, _ = _document(chain[-1], RECORD)
82
+ info = os.fstat(chain[-1].fd)
83
+ old = Path(value["previous_root"])
84
+ if (
85
+ value.get("contract") != CONTRACT
86
+ or value.get("sha256") != p._sha({k: v for k, v in value.items() if k != "sha256"})
87
+ or value.get("current_root") != str(root)
88
+ or value.get("root_identity") != [info.st_dev, info.st_ino]
89
+ or not old.is_absolute()
90
+ or old.parent.parent != root.parent.parent
91
+ or old.name != VERSION_NAME
92
+ or old == root
93
+ or os.path.lexists(old)
94
+ ):
95
+ raise _blocked()
96
+ return value
97
+
98
+
99
+ def relocated(path: Path, digest: str | None) -> Path:
100
+ """Resolve a missing old reference only through an exact recorded file digest."""
101
+ if not digest or not path.is_absolute() or ".." in path.parts:
102
+ return path
103
+ for old in path.parents:
104
+ if old.name != VERSION_NAME:
105
+ continue
106
+ root = old.parent.parent / ROOT_NAME / VERSION_NAME
107
+ if root == old or not os.path.lexists(root / RECORD):
108
+ continue
109
+ value = _record(root)
110
+ if value["previous_root"] != str(old):
111
+ continue
112
+ relative = path.relative_to(old).as_posix()
113
+ if value["files"].get(relative) != digest:
114
+ raise _blocked()
115
+ return root / relative
116
+ return path
117
+
118
+
119
+ def historical_reference(path: Path, digest: str) -> dict[str, str]:
120
+ """Preserve reference identities used inside already immutable attempt chains."""
121
+ for root in path.parents:
122
+ if root.name != VERSION_NAME or root.parent.name != ROOT_NAME:
123
+ continue
124
+ if not os.path.lexists(root / RECORD):
125
+ break
126
+ value = _record(root)
127
+ relative = path.relative_to(root).as_posix()
128
+ if value["files"].get(relative) == digest:
129
+ path = Path(value["previous_root"]) / relative
130
+ break
131
+ return {"path": str(path), "sha256": digest}
@@ -0,0 +1,546 @@
1
+ """Public model constants and small immutable data structures."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any
8
+
9
+ SCHEMA_VERSION = 1
10
+ METADATA_SCHEMA_VERSION = 4
11
+ SUPPORTED_METADATA_SCHEMA_VERSIONS = frozenset({1, 2, 3, 4})
12
+ TERMINAL_INTEGRATION_STATES = frozenset({"REMOTE_VERIFIED", "RELEASED", "ABANDONED"})
13
+
14
+ LIFECYCLE_STATES = frozenset(
15
+ {
16
+ "PLANNED",
17
+ "ACTIVE",
18
+ "RETAINED",
19
+ "RELEASE_READY",
20
+ "ARCHIVED",
21
+ "NO_CHANGE_COMPLETED",
22
+ "INTEGRATION_ABORTED",
23
+ "HANDOFF_READY",
24
+ "PRESERVED",
25
+ "INTEGRATING",
26
+ "PUBLISHED",
27
+ "RELEASED",
28
+ "BLOCKED",
29
+ "QUARANTINED",
30
+ "RECOVERY_REQUIRED",
31
+ }
32
+ )
33
+ INTEGRATION_INTENT_STATES = frozenset(
34
+ {
35
+ "QUEUED",
36
+ "WAITING_SCOPE",
37
+ "PREPARING_INTEGRATION",
38
+ "READY_TO_PUBLISH",
39
+ "NEEDS_REPREPARE",
40
+ "PUBLISHING",
41
+ "RECOVERY_REQUIRED",
42
+ "ATTENTION_REQUIRED",
43
+ "REMOTE_VERIFIED",
44
+ "RELEASED",
45
+ # Accepted only while reading pre-0.6 intent records.
46
+ "CLAIMED",
47
+ "VALIDATED",
48
+ "BLOCKED",
49
+ "PUBLISHED",
50
+ }
51
+ )
52
+ INTEGRATION_EVENT_TYPES = frozenset(
53
+ {
54
+ "INTENT_QUEUED",
55
+ "PREPARE_SLOT_GRANTED",
56
+ "PREPARED",
57
+ "NEEDS_REPREPARE",
58
+ "ATTENTION_REQUIRED",
59
+ "PUBLICATION_LEASE_GRANTED",
60
+ "REMOTE_VERIFIED",
61
+ "RECOVERY_REQUIRED",
62
+ "RELEASED",
63
+ }
64
+ )
65
+ RELEASE_INTENT_STATES = frozenset(
66
+ {
67
+ "PENDING",
68
+ "BLOCKED_RETRYABLE",
69
+ "STALE",
70
+ "CANCELLED",
71
+ "RELEASED",
72
+ }
73
+ )
74
+ ACTIVE_AUTHORITY_STATES = frozenset(
75
+ {
76
+ "PLANNED",
77
+ "ACTIVE",
78
+ "ARCHIVED",
79
+ "HANDOFF_READY",
80
+ "INTEGRATING",
81
+ "BLOCKED",
82
+ "QUARANTINED",
83
+ "RECOVERY_REQUIRED",
84
+ }
85
+ )
86
+ CANONICAL_ROLES = frozenset(
87
+ {"canonical", "control-plane", "integration", "publication", "verification"}
88
+ )
89
+ #: Roles that share one bounded physical pool of ordinary worktrees.
90
+ ORDINARY_ROLES = frozenset({"feature", "audit"})
91
+ EMERGENCY_ROLES = frozenset({"hotfix", "emergency"})
92
+ RECOVERY_ROLES = frozenset({"preservation", "recovery"})
93
+ FEATURE_AUTHORITY_ROLES = frozenset({"feature", "audit", "hotfix", "emergency"})
94
+ WORKTREE_ROLES = frozenset(
95
+ {
96
+ "canonical",
97
+ "control-plane",
98
+ "feature",
99
+ "integration",
100
+ "audit",
101
+ "preservation",
102
+ "recovery",
103
+ "hotfix",
104
+ "emergency",
105
+ "publication",
106
+ "verification",
107
+ }
108
+ )
109
+
110
+ PLAN_DECISIONS = frozenset(
111
+ {
112
+ "REUSE",
113
+ "CREATE_ALLOWED",
114
+ "CREATE_EMERGENCY_ALLOWED",
115
+ "CREATE_RECOVERY_ALLOWED",
116
+ "BLOCK_BUDGET",
117
+ "BLOCK_CANONICAL",
118
+ "BLOCK_INTEGRATION_SLOT",
119
+ "BLOCK_FEATURE_SLOT",
120
+ "BLOCK_ORDINARY_POOL",
121
+ "BLOCK_EMERGENCY_SLOT",
122
+ "BLOCK_RECOVERY_RESERVE",
123
+ "BLOCK_DUPLICATE_AUTHORITY",
124
+ "NEEDS_ADOPTION",
125
+ "BLOCK_DRIFT",
126
+ "UNKNOWN",
127
+ }
128
+ )
129
+
130
+
131
+ class ControllerError(RuntimeError):
132
+ """Controlled failure with a stable machine-readable code."""
133
+
134
+ def __init__(self, code: str, message: str) -> None:
135
+ super().__init__(message)
136
+ self.code = code
137
+
138
+
139
+ @dataclass(frozen=True)
140
+ class WorktreeGitState:
141
+ path: Path
142
+ git_dir: Path
143
+ key: str
144
+ canonical: bool
145
+ head: str
146
+ branch: str | None
147
+ detached: bool
148
+ locked: bool
149
+ lock_reason: str | None
150
+ prunable: bool
151
+ prunable_reason: str | None
152
+ clean: bool
153
+ staged: int
154
+ modified: int
155
+ untracked: int
156
+
157
+
158
+ @dataclass(frozen=True)
159
+ class RepositoryInventory:
160
+ requested_path: Path
161
+ canonical_path: Path
162
+ common_dir: Path
163
+ worktrees: tuple[WorktreeGitState, ...]
164
+
165
+
166
+ @dataclass(frozen=True)
167
+ class ConvergencePolicy:
168
+ """Delegated retention convergence: how long an unused checkout may stay on disk.
169
+
170
+ ``standing_reserve`` is the number of idle checkouts tolerated beside the live
171
+ writers before idle thresholds start to shrink; it is a pressure reference, never
172
+ an admission limit.
173
+
174
+ ``release_unintegrated_handoffs`` lets the reconciler release the checkout of a
175
+ frozen, integration-ready handoff whose exact HEAD is retained by both the local
176
+ and the remote-tracking branch before it is integrated; integration then replays
177
+ from that retained branch.
178
+ """
179
+
180
+ enabled: bool
181
+ grace_minutes: int
182
+ idle_hours_max: int
183
+ idle_hours_min: int
184
+ standing_reserve: int
185
+ release_unintegrated_handoffs: bool = False
186
+
187
+
188
+ @dataclass(frozen=True)
189
+ class WorktreePolicy:
190
+ path: Path
191
+ present: bool
192
+ policy_schema_version: int
193
+ normal_limit: int
194
+ hard_limit: int
195
+ canonical_protected: bool
196
+ canonical_allowed_roles: frozenset[str]
197
+ integration_candidate_limit: int
198
+ integration_prepare_limit: int
199
+ feature_audit_slots: int
200
+ emergency_slots: int
201
+ recovery_reserve: int
202
+ retained_slots: int
203
+ reconciler_enabled: bool
204
+ reconciler_actor: str
205
+ reconciler_legacy_adoption: bool
206
+ main_publish_limit: int
207
+ publication_remote: str
208
+ publication_branch: str
209
+ automatic_feature_audit_release: bool
210
+ automatic_integration_continuation: bool
211
+ preserved_auto_release: bool
212
+ release_intent_ttl_seconds: int
213
+ stale_warning_days: int
214
+ require_adoption: bool
215
+ require_task_key: bool
216
+ require_authority_key: bool
217
+ unique_active_feature_authority: bool
218
+ resource_admission: bool = True
219
+ checkout_growth_bytes: int = 2 * 1024**3
220
+ checkout_growth_inodes: int = 100_000
221
+ convergence: ConvergencePolicy | None = None
222
+
223
+
224
+ @dataclass(frozen=True)
225
+ class MetadataRecord:
226
+ schema_version: int
227
+ record_version: int
228
+ allocation_id: str
229
+ task_key: str
230
+ authority_key: str
231
+ module: str
232
+ role: str
233
+ owner: str
234
+ lifecycle: str
235
+ created_at: str
236
+ last_reviewed_at: str
237
+ handoff_commit: str | None
238
+ finalizer_record_id: str | None
239
+ base_commit: str | None
240
+ task_commit: str | None
241
+ finalizer_commit: str | None
242
+ scope_paths: tuple[str, ...]
243
+ source_allocations: tuple[str, ...]
244
+ quarantine_expected_commit: str | None
245
+ quarantine_observed_commit: str | None
246
+ quarantine_reason: str | None
247
+ recovery_allocation_id: str | None
248
+ recovery_ref: str | None
249
+ disposable_resources: tuple[dict[str, Any], ...]
250
+ external_bindings: tuple[str, ...]
251
+ history: tuple[dict[str, Any], ...]
252
+
253
+
254
+ @dataclass(frozen=True)
255
+ class IntegrationIntent:
256
+ schema_version: int
257
+ contract_version: int
258
+ intent_id: str
259
+ task_key: str
260
+ authority_key: str
261
+ module: str
262
+ owner: str
263
+ source_allocations: tuple[str, ...]
264
+ source_handoffs: tuple[str, ...]
265
+ source_branches: tuple[str, ...]
266
+ source_finalizer_record_ids: tuple[str, ...]
267
+ source_snapshot_ids: tuple[str, ...]
268
+ scope_paths: tuple[str, ...]
269
+ scope_digest: str
270
+ queue_sequence: int
271
+ state: str
272
+ allocation_id: str | None
273
+ base_main_oid: str | None
274
+ candidate_oid: str | None
275
+ revision: int
276
+ attempt: int
277
+ prepare_identity: str | None
278
+ publication_identity: str | None
279
+ recovery_identity: str | None
280
+ prepared_receipt_id: str | None
281
+ prepared_base_origin_main_oid: str | None
282
+ prepared_commit_oid: str | None
283
+ prepared_tree_oid: str | None
284
+ prepared_scope_digest: str | None
285
+ tests_receipt_id: str | None
286
+ snapshot_receipt_id: str | None
287
+ validation_evidence_id: str | None
288
+ validated_oid: str | None
289
+ blocked_reason: str | None
290
+ enqueued_at: str
291
+ claimed_at: str | None
292
+ validated_at: str | None
293
+ published_at: str | None
294
+ released_at: str | None
295
+ updated_at: str
296
+ equivalent_replays: tuple[dict[str, str], ...]
297
+ history: tuple[dict[str, Any], ...]
298
+
299
+
300
+ @dataclass(frozen=True)
301
+ class PreparedCandidateReceipt:
302
+ schema_version: int
303
+ receipt_id: str
304
+ repository_id: str
305
+ intent_id: str
306
+ allocation_id: str
307
+ attempt: int
308
+ base_origin_main_oid: str
309
+ candidate_oid: str
310
+ tree_oid: str
311
+ scope_digest: str
312
+ prepare_identity: str
313
+ validation_evidence_id: str
314
+ tests_receipt_id: str
315
+ snapshot_receipt_id: str
316
+ created_at: str
317
+
318
+
319
+ @dataclass(frozen=True)
320
+ class LifecycleEvent:
321
+ schema_version: int
322
+ event_id: str
323
+ repository_id: str
324
+ intent_id: str
325
+ allocation_id: str | None
326
+ event_type: str
327
+ state: str
328
+ sequence: int
329
+ identity: str
330
+ created_at: str
331
+
332
+
333
+ @dataclass(frozen=True)
334
+ class IntegrationReconciliationReceipt:
335
+ schema_version: int
336
+ receipt_id: str
337
+ repository_id: str
338
+ intent_id: str
339
+ observed_intent_schema_version: int
340
+ observed_intent_state: str
341
+ observed_allocation_id: str | None
342
+ observed_record_version: int | None
343
+ observed_record_lifecycle: str | None
344
+ effective_terminal_state: str
345
+ result: str
346
+ actor: str
347
+ reconciled_at: str
348
+
349
+
350
+ @dataclass(frozen=True)
351
+ class PublicationLease:
352
+ schema_version: int
353
+ lease_id: str
354
+ repository_id: str
355
+ allocation_id: str
356
+ holder: str
357
+ run_id: str
358
+ remote: str
359
+ branch: str
360
+ target_ref: str
361
+ candidate_oid: str
362
+ expected_main_oid: str
363
+ validation_evidence_id: str
364
+ acquired_at: str
365
+ expires_at: str
366
+
367
+
368
+ @dataclass(frozen=True)
369
+ class WriterHolder:
370
+ """Identity of the live process that currently owns write permission.
371
+
372
+ ``pid`` alone cannot identify a process: Linux reuses pids, and a stale lease
373
+ naming a recycled pid would read as live. ``boot_id`` plus the kernel's
374
+ ``starttime`` for that pid make the triple unique for the life of the boot, so
375
+ a recycled pid never satisfies a lease written for its predecessor.
376
+ """
377
+
378
+ client_id: str
379
+ pid: int
380
+ boot_id: str
381
+ start_ticks: int
382
+ session_id: str | None
383
+ run_id: str | None
384
+
385
+
386
+ @dataclass(frozen=True)
387
+ class WriterLease:
388
+ schema_version: int
389
+ lease_id: str
390
+ repository_id: str
391
+ allocation_id: str
392
+ worktree_path: str
393
+ owner: str
394
+ holder: WriterHolder
395
+ acquired_at: str
396
+ renewed_at: str
397
+ expires_at: str | None
398
+ record_version: int
399
+
400
+
401
+ @dataclass(frozen=True)
402
+ class WriterReconciliationReceipt:
403
+ schema_version: int
404
+ receipt_id: str
405
+ repository_id: str
406
+ allocation_id: str
407
+ actor: str
408
+ owner: str
409
+ trigger: str
410
+ outcome: str
411
+ reason: str
412
+ evidence: tuple[str, ...]
413
+ previous_lifecycle: str
414
+ new_lifecycle: str
415
+ reconciled_at: str
416
+
417
+
418
+ @dataclass(frozen=True)
419
+ class PublicationReceipt:
420
+ schema_version: int
421
+ receipt_id: str
422
+ repository_id: str
423
+ allocation_id: str
424
+ intent_id: str | None
425
+ target_ref: str
426
+ holder: str
427
+ run_id: str
428
+ expected_main_oid: str
429
+ candidate_oid: str
430
+ validation_evidence_id: str
431
+ finalizer_record_id: str
432
+ status: str
433
+ acquired_at: str
434
+ remote_verified_at: str
435
+ lease_released_at: str | None
436
+ released_at: str | None
437
+ lease_duration_ms: int
438
+ remote_verified_to_released_ms: int | None
439
+ source_results: tuple[dict[str, str], ...]
440
+
441
+
442
+ @dataclass(frozen=True)
443
+ class ReleaseIntent:
444
+ schema_version: int
445
+ intent_id: str
446
+ repository_id: str
447
+ allocation_id: str
448
+ task_key: str
449
+ authority_key: str
450
+ owner: str
451
+ role: str
452
+ expected_head: str
453
+ expected_record_version: int
454
+ task_commit: str
455
+ handoff_commit: str
456
+ finalizer_commit: str
457
+ finalizer_record_id: str
458
+ integration_intent_id: str | None
459
+ integration_allocation_id: str | None
460
+ publication_receipt_id: str | None
461
+ integration_candidate_oid: str | None
462
+ integration_mode: str | None
463
+ integration_evidence_id: str | None
464
+ trigger: str
465
+ status: str
466
+ blocker: str | None
467
+ created_at: str
468
+ updated_at: str
469
+ expires_at: str
470
+ history: tuple[dict[str, Any], ...]
471
+
472
+
473
+ @dataclass(frozen=True)
474
+ class ReleasePlan:
475
+ schema_version: int
476
+ plan_id: str
477
+ repository_id: str
478
+ allocation_id: str
479
+ release_intent_id: str | None
480
+ target_path: str
481
+ branch: str
482
+ expected_head: str
483
+ expected_record_version: int
484
+ owner: str
485
+ authority_key: str
486
+ role: str
487
+ lifecycle: str
488
+ trigger: str
489
+ publication_receipt_id: str | None
490
+ resource_actions: tuple[dict[str, Any], ...]
491
+ created_at: str
492
+
493
+
494
+ @dataclass(frozen=True)
495
+ class ReleaseReceipt:
496
+ schema_version: int
497
+ receipt_id: str
498
+ plan_id: str
499
+ repository_id: str
500
+ allocation_id: str
501
+ release_intent_id: str | None
502
+ target_path: str
503
+ branch: str
504
+ released_head: str
505
+ expected_record_version: int
506
+ released_record_version: int
507
+ trigger: str
508
+ resource_receipt_ids: tuple[str, ...]
509
+ result: str
510
+ released_at: str
511
+
512
+
513
+ @dataclass(frozen=True)
514
+ class ReconciliationReceipt:
515
+ schema_version: int
516
+ receipt_id: str
517
+ repository_id: str
518
+ source_receipt_id: str
519
+ allocation_id: str
520
+ observed_record_version: int
521
+ observed_record_lifecycle: str
522
+ observed_receipt_status: str
523
+ differences: tuple[str, ...]
524
+ recovered_intent_id: str | None
525
+ recovered_intent_state: str | None
526
+ result: str
527
+ actor: str
528
+ reconciled_at: str
529
+
530
+
531
+ @dataclass(frozen=True)
532
+ class MetadataSnapshot:
533
+ repository_id: str | None
534
+ records: dict[str, MetadataRecord]
535
+ bindings: dict[str, str]
536
+ integration_intents: dict[str, IntegrationIntent]
537
+ prepared_candidate_receipts: dict[str, PreparedCandidateReceipt]
538
+ integration_reconciliation_receipts: dict[str, IntegrationReconciliationReceipt]
539
+ release_intents: dict[str, ReleaseIntent]
540
+ release_plans: dict[str, ReleasePlan]
541
+ publication_lease: PublicationLease | None
542
+ publication_receipts: dict[str, PublicationReceipt]
543
+ release_receipts: dict[str, ReleaseReceipt]
544
+ reconciliation_receipts: dict[str, ReconciliationReceipt]
545
+ writer_leases: dict[str, WriterLease]
546
+ warnings: tuple[dict[str, str], ...]