ccx-native 0.0.1 → 0.2.0

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 (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +240 -4
  3. package/bin/ccx +55 -2
  4. package/ccx/__init__.py +167 -0
  5. package/ccx/__main__.py +28 -0
  6. package/ccx/claude/__init__.py +0 -0
  7. package/ccx/claude/analytics.py +466 -0
  8. package/ccx/claude/attach.py +807 -0
  9. package/ccx/claude/context.py +465 -0
  10. package/ccx/claude/delivery.py +806 -0
  11. package/ccx/claude/external.py +253 -0
  12. package/ccx/claude/host/__init__.py +0 -0
  13. package/ccx/claude/host/events.py +297 -0
  14. package/ccx/claude/host/hooks.py +438 -0
  15. package/ccx/claude/host/pty.py +269 -0
  16. package/ccx/claude/host/session.py +5244 -0
  17. package/ccx/claude/host/subagents.py +169 -0
  18. package/ccx/claude/host/successor.py +267 -0
  19. package/ccx/claude/managed_turns.py +240 -0
  20. package/ccx/claude/oauth.py +176 -0
  21. package/ccx/claude/settings.py +339 -0
  22. package/ccx/claude/spawn.py +557 -0
  23. package/ccx/claude/tasks.py +72 -0
  24. package/ccx/claude/telemetry.py +282 -0
  25. package/ccx/claude/transcript.py +2535 -0
  26. package/ccx/claude/usage.py +796 -0
  27. package/ccx/claude/usage_control.py +204 -0
  28. package/ccx/cli/__init__.py +0 -0
  29. package/ccx/cli/codex.py +1009 -0
  30. package/ccx/cli/commands/__init__.py +0 -0
  31. package/ccx/cli/commands/_common.py +407 -0
  32. package/ccx/cli/commands/control.py +161 -0
  33. package/ccx/cli/commands/create.py +669 -0
  34. package/ccx/cli/commands/models.py +53 -0
  35. package/ccx/cli/commands/read.py +1104 -0
  36. package/ccx/cli/commands/turns.py +1081 -0
  37. package/ccx/cli/controller.py +757 -0
  38. package/ccx/cli/main.py +302 -0
  39. package/ccx/cli/parser.py +1439 -0
  40. package/ccx/cli/skills.py +215 -0
  41. package/ccx/cli/statusline.py +160 -0
  42. package/ccx/client.py +1904 -0
  43. package/ccx/codex/__init__.py +0 -0
  44. package/ccx/codex/attach_tty.py +267 -0
  45. package/ccx/codex/client.py +738 -0
  46. package/ccx/codex/completion.py +121 -0
  47. package/ccx/codex/control.py +288 -0
  48. package/ccx/codex/daemon/__init__.py +0 -0
  49. package/ccx/codex/daemon/__main__.py +4 -0
  50. package/ccx/codex/daemon/daemon.py +1558 -0
  51. package/ccx/codex/daemon/models.py +140 -0
  52. package/ccx/codex/daemon/native.py +1590 -0
  53. package/ccx/codex/daemon/release.py +367 -0
  54. package/ccx/codex/daemon/worker.py +2424 -0
  55. package/ccx/codex/delivery.py +758 -0
  56. package/ccx/codex/delivery_recovery.py +232 -0
  57. package/ccx/codex/evidence.py +827 -0
  58. package/ccx/codex/history.py +158 -0
  59. package/ccx/codex/history_store.py +131 -0
  60. package/ccx/codex/notify_history.py +140 -0
  61. package/ccx/codex/ops.py +666 -0
  62. package/ccx/codex/paths.py +70 -0
  63. package/ccx/codex/protocol.py +805 -0
  64. package/ccx/codex/provenance.py +105 -0
  65. package/ccx/codex/read.py +29 -0
  66. package/ccx/codex/records.py +51 -0
  67. package/ccx/codex/reports.py +177 -0
  68. package/ccx/codex/runtime.py +1454 -0
  69. package/ccx/codex/settle.py +363 -0
  70. package/ccx/codex/telemetry.py +792 -0
  71. package/ccx/codex/types.py +659 -0
  72. package/ccx/codex/worker_environment.py +25 -0
  73. package/ccx/constants.py +288 -0
  74. package/ccx/controllers/__init__.py +0 -0
  75. package/ccx/controllers/controller.py +1449 -0
  76. package/ccx/controllers/live_vars.py +1706 -0
  77. package/ccx/controllers/live_vars_runner.py +216 -0
  78. package/ccx/controllers/notify.py +213 -0
  79. package/ccx/controllers/orchestrator.py +193 -0
  80. package/ccx/controllers/policy.py +57 -0
  81. package/ccx/controllers/store.py +681 -0
  82. package/ccx/errors.py +634 -0
  83. package/ccx/models.py +225 -0
  84. package/ccx/notify/__init__.py +0 -0
  85. package/ccx/notify/__main__.py +5 -0
  86. package/ccx/notify/client.py +116 -0
  87. package/ccx/notify/daemon.py +480 -0
  88. package/ccx/notify/deliver.py +304 -0
  89. package/ccx/notify/paths.py +51 -0
  90. package/ccx/notify/registry.py +649 -0
  91. package/ccx/notify/select.py +750 -0
  92. package/ccx/notify/settle.py +684 -0
  93. package/ccx/ops/__init__.py +0 -0
  94. package/ccx/ops/background.py +115 -0
  95. package/ccx/ops/cancel.py +296 -0
  96. package/ccx/ops/codex_import.py +96 -0
  97. package/ccx/ops/common.py +397 -0
  98. package/ccx/ops/compact.py +337 -0
  99. package/ccx/ops/compact_ops.py +550 -0
  100. package/ccx/ops/context_events.py +48 -0
  101. package/ccx/ops/conversation.py +1593 -0
  102. package/ccx/ops/conversation_paging.py +1339 -0
  103. package/ccx/ops/create.py +56 -0
  104. package/ccx/ops/creation.py +952 -0
  105. package/ccx/ops/creation_steps.py +575 -0
  106. package/ccx/ops/event_feed.py +710 -0
  107. package/ccx/ops/forking.py +711 -0
  108. package/ccx/ops/history.py +1041 -0
  109. package/ccx/ops/input.py +556 -0
  110. package/ccx/ops/maintenance.py +295 -0
  111. package/ccx/ops/report.py +773 -0
  112. package/ccx/ops/semantics.py +490 -0
  113. package/ccx/ops/send.py +961 -0
  114. package/ccx/ops/steer_ops.py +867 -0
  115. package/ccx/ops/terminate.py +383 -0
  116. package/ccx/ops/turn_reports.py +348 -0
  117. package/ccx/ops/wait.py +762 -0
  118. package/ccx/ops/worker_events.py +640 -0
  119. package/ccx/rpc/__init__.py +0 -0
  120. package/ccx/rpc/local_rpc.py +1602 -0
  121. package/ccx/rpc/rpc.py +559 -0
  122. package/ccx/rpc/serve.py +374 -0
  123. package/ccx/rpc/transport.py +409 -0
  124. package/ccx/rpc/view.py +125 -0
  125. package/ccx/store/__init__.py +0 -0
  126. package/ccx/store/discovery.py +170 -0
  127. package/ccx/store/identity.py +105 -0
  128. package/ccx/store/journal_io.py +79 -0
  129. package/ccx/store/liveness.py +194 -0
  130. package/ccx/store/paths.py +250 -0
  131. package/ccx/store/record_cache.py +40 -0
  132. package/ccx/store/records.py +309 -0
  133. package/ccx/store/root.py +789 -0
  134. package/ccx/store/runtime_presence.py +137 -0
  135. package/ccx/store/store.py +2332 -0
  136. package/ccx/store/worker_state.py +86 -0
  137. package/ccx/system/__init__.py +0 -0
  138. package/ccx/system/bounded.py +296 -0
  139. package/ccx/system/cwd_paths.py +25 -0
  140. package/ccx/system/engine_binary.py +59 -0
  141. package/ccx/system/executable.py +50 -0
  142. package/ccx/system/oslayer.py +667 -0
  143. package/ccx/system/pipes.py +46 -0
  144. package/ccx/system/private_json.py +54 -0
  145. package/ccx/system/sanitize.py +43 -0
  146. package/ccx/types.py +809 -0
  147. package/ccx/version.py +29 -0
  148. package/ccx/workers.py +2093 -0
  149. package/ccx.py +9 -0
  150. package/package.json +39 -7
  151. package/pyproject.toml +87 -0
  152. package/skills/ccx/SKILL.md +166 -0
  153. package/skills/ccx/references/controllers.md +244 -0
  154. package/skills/ccx/references/history.md +50 -0
  155. package/skills/ccx/references/troubleshooting.md +94 -0
  156. package/skills/ccx/references/variables.md +62 -0
@@ -0,0 +1,2332 @@
1
+ from __future__ import annotations
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import re
6
+ import stat
7
+ import time
8
+ from contextlib import ExitStack, contextmanager
9
+ from dataclasses import dataclass
10
+ from pathlib import Path
11
+ from typing import Callable, Iterator, Mapping
12
+ from uuid import uuid4, uuid5, NAMESPACE_URL
13
+ from ccx.store.liveness import recorded_host_loaded
14
+ from ccx.store.worker_state import normalize_native_state
15
+ from ccx.store.identity import (
16
+ ENGINES, EngineMismatch, InvalidTarget, OwnerConflict, UnknownWorker,
17
+ WorkerOwner, canonical_worker_id, parse_managed_target,
18
+ )
19
+ from ccx.store.root import (
20
+ CreationConflict, CreationIndeterminate, GenerationError, GenerationPointer, GenerationUnavailable, NativeActionDenied, NativeEffectIndeterminate, RecordConflict, StaleGeneration, StorageIntegrityError, _CLAIM_SCHEMA, _FAMILIES, _FILE_FLAGS, _HASH, _NATIVE_WRITES, _OWNER_SCHEMA, _RECEIPT_SCHEMA, _flock_at, _open_child_dir, _open_dir, _read_json, _write_json, inspect_generation
21
+ )
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class CreationReservation:
26
+ worker_id: str
27
+ engine: str
28
+ create_sha256: str
29
+ reconciled: bool
30
+ record_committed: bool
31
+ # Original native activation fate, never inferred from reconciled/record_committed.
32
+ activation_state: str = "unproven"
33
+ submission_id: str | None = None
34
+ # Immutable managed caller from the first owner intent, not the retry env.
35
+ spawned_by: dict | None = None
36
+
37
+
38
+ @dataclass(frozen=True)
39
+ class NativeActivation:
40
+ """Exact original activation intent (not permission to replay a native call)."""
41
+ worker_id: str
42
+ engine: str
43
+ source: str
44
+ submission_id: str
45
+ state: str
46
+ generation: str
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class OperationIntent:
51
+ worker_id: str
52
+ engine: str
53
+ family: str
54
+ operation_id: str
55
+ intent_sha256: str
56
+ state: str
57
+ generation: str
58
+ dispatch_token: str | None = None
59
+ replayed: bool = False
60
+ native_evidence_sha256: str | None = None
61
+
62
+
63
+ def _digest(obj: object) -> str:
64
+ try:
65
+ raw = json.dumps(obj, sort_keys=True, separators=(",", ":"),
66
+ ensure_ascii=False, allow_nan=False).encode("utf-8")
67
+ except (ValueError, TypeError) as exc:
68
+ raise StorageIntegrityError("noncanonical immutable input") from exc
69
+ return hashlib.sha256(raw).hexdigest()
70
+
71
+
72
+ def converted_binding_digest(worker_id: str, engine: str, native_binding: Mapping,
73
+ converted_from: Mapping) -> str:
74
+ """One domain-separated offline-conversion digest, NOT a native create hash."""
75
+ return _digest({
76
+ "kind": "ccx-convert", "workerId": canonical_worker_id(worker_id),
77
+ "engine": engine, "nativeBinding": dict(native_binding),
78
+ "convertedFrom": dict(converted_from),
79
+ })
80
+
81
+
82
+ def _converted_binding(owner: Mapping, claim: Mapping, record: Mapping) -> str:
83
+ """Pure proof of one current-format converted native identity; never an ACK."""
84
+ intent = owner.get("createIntent")
85
+ cf = intent.get("convertedFrom") if isinstance(intent, dict) else None
86
+ native = record.get("native")
87
+ activation = claim.get("nativeActivation")
88
+ wid, engine = owner.get("workerId"), owner.get("engine")
89
+ if (owner.get("schemaVersion") != _OWNER_SCHEMA
90
+ or owner.get("status") != "worker" or owner.get("provenance") != "ccx-convert"
91
+ or not isinstance(intent, dict) or not isinstance(cf, dict)
92
+ or not isinstance(native, dict) or not isinstance(activation, dict)
93
+ or engine not in ENGINES or record.get("workerId") != wid
94
+ or record.get("engine") != engine
95
+ or claim.get("workerId") != wid or claim.get("engine") != engine
96
+ or owner.get("generation") != claim.get("generation")
97
+ or claim.get("schemaVersion") != _CLAIM_SCHEMA
98
+ or record.get("schemaVersion") != 2
99
+ or any(doc.get("fingerprintVersion") != 2
100
+ for doc in (intent, claim, record))
101
+ or intent.get("forkAnchor") is not None or claim.get("forkAnchor") is not None
102
+ or record.get("forkAnchor") is not None
103
+ or intent.get("activationStateAtReservation") != "converted"
104
+ or activation.get("state") != "confirmed"
105
+ or activation.get("submissionId") is not None
106
+ or claim.get("activatedAtMs") is not None
107
+ or record.get("terminalOnly") is not False
108
+ or record.get("nativeCreate") is not None
109
+ or record.get("spawnedBy") != intent.get("spawnedBy")
110
+ or record.get("importedFrom") != cf.get("importedFrom")):
111
+ raise StorageIntegrityError("converted owner/claim/record provenance differs")
112
+ if (not isinstance(cf.get("legacyShort"), str)
113
+ or not re.fullmatch(r"[0-9a-f]{8}", cf["legacyShort"])
114
+ or not isinstance(cf.get("sourcePath"), str) or not cf["sourcePath"]
115
+ or "\x00" in cf["sourcePath"]
116
+ or any(not isinstance(cf.get(key), str)
117
+ or not _HASH.fullmatch(cf[key])
118
+ for key in ("sourceRecordSha256", "planSha256"))
119
+ or any(cf.get(key) is not None
120
+ and (not isinstance(cf[key], str) or not _HASH.fullmatch(cf[key]))
121
+ for key in ("originalClaimSha256", "originalCreateSha256"))
122
+ or (cf.get("importedFrom") is not None
123
+ and not isinstance(cf.get("importedFrom"), dict))):
124
+ raise StorageIntegrityError("converted frozen source/plan proof malformed")
125
+ if cf.get("importedFrom") is None:
126
+ if record.get("origin") == "ccx-import" or "importedFrom" in record:
127
+ raise StorageIntegrityError("converted import history has no original proof")
128
+ elif record.get("origin") != "ccx-import":
129
+ raise StorageIntegrityError("converted import history lost original origin")
130
+ nb = intent.get("nativeBinding")
131
+ if not isinstance(nb, dict) or nb != claim.get("nativeBinding"):
132
+ raise StorageIntegrityError("converted original native binding differs")
133
+ if engine == "claude":
134
+ sid = nb.get("sessionId")
135
+ if (set(nb) != {"sessionId"} or not isinstance(sid, str) or not sid
136
+ or cf.get("nativeProof") is not None
137
+ or (cf.get("importedFrom") is not None
138
+ and cf["importedFrom"].get("sessionId") != sid)
139
+ or native.get("sessionId") != sid
140
+ or native.get("nativeState") != "session-bound"):
141
+ raise StorageIntegrityError("converted Claude SID/proof differs")
142
+ source = f"claude-session:{sid}"
143
+ else:
144
+ tid = nb.get("threadId")
145
+ proof = cf.get("nativeProof")
146
+ if (set(nb) != {"threadId"} or not isinstance(tid, str) or not tid
147
+ or not isinstance(proof, dict)
148
+ or set(proof) != {"kind", "threadId", "threadSource",
149
+ "headerPath", "headerSha256"}
150
+ or proof.get("kind") != "codex-session-meta"
151
+ or proof.get("threadId") != tid
152
+ or not isinstance(proof.get("headerPath"), str)
153
+ or not proof["headerPath"] or "\x00" in proof["headerPath"]
154
+ or not isinstance(proof.get("headerSha256"), str)
155
+ or not _HASH.fullmatch(proof["headerSha256"])):
156
+ raise StorageIntegrityError("converted Codex session-meta proof malformed")
157
+ source = proof.get("threadSource")
158
+ original = source.removeprefix("ccx-worker:") if isinstance(source, str) else ""
159
+ if (not isinstance(source, str) or not source.startswith("ccx-worker:")
160
+ or original[:8] != cf["legacyShort"]
161
+ or (original != cf["legacyShort"]
162
+ and not _is_canonical_worker_uuid(original))
163
+ or native.get("threadId") != tid
164
+ or native.get("threadSource") != source
165
+ or native.get("nativeState") != "thread-bound"):
166
+ raise StorageIntegrityError("converted Codex original source tag differs")
167
+ digest = converted_binding_digest(wid, engine, nb, cf)
168
+ if (any(doc.get("createSha256") != digest
169
+ for doc in (intent, claim, record))
170
+ or activation.get("nativeEvidenceSha256") != digest
171
+ or activation.get("nativeBinding") != nb
172
+ or activation.get("source") != source):
173
+ raise StorageIntegrityError("converted native binding digest/source differs")
174
+ return source
175
+
176
+
177
+ def _is_canonical_worker_uuid(value: str) -> bool:
178
+ try:
179
+ canonical_worker_id(value)
180
+ return True
181
+ except InvalidTarget:
182
+ return False
183
+
184
+
185
+ def codex_fork_lineage(anchor: Mapping, *, child_id: str | None = None) -> dict:
186
+ """Project a frozen Codex native fork; external source has no CCX owner."""
187
+ from ccx.store.identity import InvalidTarget
188
+ keys = {"engine", "sourceWorkerId", "sourceNativeThreadId", "lastTurnId",
189
+ "cwd", "createArgs", "seed", "overrides", "forkedAtMs"}
190
+ if not isinstance(anchor, Mapping) or anchor.get("engine") != "codex":
191
+ raise CreationIndeterminate("Codex fork source identity is invalid")
192
+ external = anchor.get("sourceKind") == "codex-thread"
193
+ if set(anchor) != (keys | {"sourceKind"} if external else keys):
194
+ raise CreationIndeterminate("Codex fork source identity is invalid")
195
+ if external:
196
+ if anchor.get("sourceWorkerId") is not None:
197
+ raise CreationIndeterminate("external Codex source has no CCX worker owner")
198
+ else:
199
+ try:
200
+ source = canonical_worker_id(anchor.get("sourceWorkerId"))
201
+ except (InvalidTarget, TypeError, ValueError) as exc:
202
+ raise CreationIndeterminate("native Codex fork source worker ID invalid") from exc
203
+ if source == child_id:
204
+ raise CreationIndeterminate("Codex child may not fork its own worker ID")
205
+ lineage = {key: anchor[key] for key in (
206
+ "engine", "sourceWorkerId", "sourceNativeThreadId",
207
+ "lastTurnId", "forkedAtMs",
208
+ )}
209
+ if external:
210
+ lineage["sourceKind"] = "codex-thread"
211
+ return lineage
212
+
213
+
214
+ def fingerprint_v2(*, engine: str, cwd: str, create_args: Mapping,
215
+ fork_anchor: Mapping | None = None) -> str:
216
+ """Immutable creation spec with a frozen fork anchor.
217
+
218
+ An exact fork anchor must already be frozen by the caller. No display name
219
+ or mutable lifecycle field enters the immutable preimage.
220
+ """
221
+ if engine not in ENGINES or not isinstance(cwd, str) or not cwd.startswith("/"):
222
+ raise CreationConflict("create engine/cwd must be explicit and canonical")
223
+ if not isinstance(create_args, Mapping):
224
+ raise CreationConflict("createArgs must be a mapping")
225
+ if fork_anchor is not None and not isinstance(fork_anchor, Mapping):
226
+ raise CreationConflict("forkAnchor must be a frozen mapping")
227
+ return _digest({
228
+ "version": 2, "engine": engine, "cwd": cwd,
229
+ "createArgs": dict(create_args),
230
+ "forkAnchor": dict(fork_anchor) if fork_anchor is not None else None,
231
+ })
232
+
233
+
234
+ class GenerationStore:
235
+ """ACTIVE backend pinned to one sealed generation; never hot-switches."""
236
+
237
+ def __init__(self, pointer: GenerationPointer):
238
+ if pointer.phase != "ACTIVE":
239
+ raise GenerationUnavailable("only ACTIVE generation is operational")
240
+ # Validate again: caller must not forge a dataclass to bypass loader.
241
+ current = inspect_generation(pointer.root)
242
+ if current != pointer:
243
+ raise StaleGeneration("stale or forged generation context")
244
+ # Direct GenerationStore(pointer) must be equally fail-closed and
245
+ # typed as require_ccx_active, not leak raw FileNotFoundError.
246
+ try:
247
+ with _open_dir(pointer.root, "identity") as identity:
248
+ try:
249
+ lock_fd = os.open("activation.lock", _FILE_FLAGS, dir_fd=identity)
250
+ except OSError as exc:
251
+ raise GenerationUnavailable("activation lock missing/unsafe; offline stage required") from exc
252
+ try:
253
+ st = os.fstat(lock_fd)
254
+ named = os.stat("activation.lock", dir_fd=identity,
255
+ follow_symlinks=False)
256
+ if ((st.st_dev, st.st_ino) != (named.st_dev, named.st_ino)
257
+ or not stat.S_ISREG(st.st_mode)
258
+ or st.st_uid != os.getuid() or st.st_mode & 0o077):
259
+ raise GenerationUnavailable("activation lock inode is not safe/owner-private")
260
+ finally:
261
+ os.close(lock_fd)
262
+ except FileNotFoundError as exc:
263
+ raise GenerationUnavailable("activation lock not installed by offline stage") from exc
264
+ self.pointer = pointer
265
+ self.root = pointer.root
266
+ self.generation = pointer.generation
267
+ self._gen_parts = ("identity", "generations", self.generation)
268
+
269
+ def revalidate(self) -> None:
270
+ current = inspect_generation(self.root)
271
+ if current != self.pointer:
272
+ raise StaleGeneration("generation changed; refuse and restart on a new context")
273
+
274
+ @contextmanager
275
+ def lease(self, *, timeout: float = .5) -> Iterator[None]:
276
+ """Stable shared activation lock for bounded local intent/dispatch only.
277
+
278
+ DO NOT hold across native provider turn waits, --wait, streaming or
279
+ arbitrary user callbacks. Every public store mutation uses this lease;
280
+ a later cutover runner needs LOCK_EX on the same never-replaced inode.
281
+ """
282
+ self.revalidate()
283
+ with _open_dir(self.root, "identity") as identity:
284
+ with _flock_at(identity, "activation.lock", exclusive=False, timeout=timeout):
285
+ self.revalidate()
286
+ yield
287
+ self.revalidate()
288
+
289
+ @contextmanager
290
+ def _gen(self, *parts: str, create: bool = False) -> Iterator[int]:
291
+ with _open_dir(self.root, *self._gen_parts, *parts, create=create) as fd:
292
+ yield fd
293
+
294
+ @contextmanager
295
+ def _claim_lock(self, worker_id: str) -> Iterator[None]:
296
+ with self._gen("locks", "creation", create=True) as fd:
297
+ with _flock_at(fd, worker_id + ".lock", exclusive=True, create=True):
298
+ yield
299
+
300
+ def _owner(self, worker_id: str) -> dict | None:
301
+ try:
302
+ with self._gen("owners") as fd:
303
+ return _read_json(fd, worker_id + ".json")
304
+ except FileNotFoundError:
305
+ return None
306
+
307
+ def _claim(self, worker_id: str) -> dict | None:
308
+ try:
309
+ with self._gen("claims") as fd:
310
+ return _read_json(fd, worker_id + ".json")
311
+ except FileNotFoundError:
312
+ return None
313
+
314
+ def _record(self, engine: str, worker_id: str) -> dict | None:
315
+ try:
316
+ with self._gen("workers", engine, worker_id) as fd:
317
+ return _read_json(fd, "record.json")
318
+ except FileNotFoundError:
319
+ return None
320
+
321
+ def _write(self, parts: tuple[str, ...], basename: str, data: Mapping) -> None:
322
+ with self._gen(*parts, create=True) as fd:
323
+ _write_json(fd, basename, data)
324
+
325
+ def _owned(self, target: str, *, require_record: bool = True) -> tuple[WorkerOwner, dict | None]:
326
+ parsed = parse_managed_target(target)
327
+ record = self._owner(parsed.worker_id)
328
+ if record is None:
329
+ raise UnknownWorker("full UUID is not present in global owner index")
330
+ if (record.get("schemaVersion") != _OWNER_SCHEMA
331
+ or record.get("workerId") != parsed.worker_id
332
+ or record.get("generation") != self.generation
333
+ or record.get("engine") not in ENGINES):
334
+ raise StorageIntegrityError("global full-ID owner record disagrees")
335
+ if parsed.qualified_engine is not None and parsed.qualified_engine != record["engine"]:
336
+ raise EngineMismatch(
337
+ f"{parsed.worker_id} is a {record['engine'].capitalize()} worker; use "
338
+ f"{record['engine']}:{parsed.worker_id} or the bare UUID")
339
+ if record.get("status") != "worker":
340
+ raise UnknownWorker("full UUID is reserved/quarantined, not an active worker")
341
+ source = self._record(record["engine"], parsed.worker_id)
342
+ if require_record and source is None:
343
+ raise StorageIntegrityError("active owner has no matching full-ID worker record")
344
+ if source is not None and (source.get("workerId") != parsed.worker_id
345
+ or source.get("engine") != record["engine"]):
346
+ raise StorageIntegrityError("record and unique global worker owner disagree")
347
+ return WorkerOwner(parsed.worker_id, record["engine"]), source
348
+
349
+ def resolve(self, target: str) -> WorkerOwner:
350
+ with self.lease():
351
+ owner, _rec = self._owned(target)
352
+ return owner
353
+
354
+ def list_workers(self, *, engine: str | None = None, cwd: str | None = None,
355
+ state: str | None = None, parent: str | None = None,
356
+ limit: int | None = None,
357
+ max_owners: int = 60000,
358
+ records: dict | None = None) -> list[dict]:
359
+ """Pure durable managed-worker inventory, no native discovery/spawn.
360
+
361
+ Every row is selected by its one global full-UUID owner file and a
362
+ separate exact engine/full-ID record. No short, display name or native
363
+ session/thread ID ever authorizes a row. Status is RECORDED, not a
364
+ guess of host liveness; callers inspect live host status separately.
365
+ """
366
+ if engine is not None and engine not in ENGINES:
367
+ raise StorageIntegrityError("unsupported inventory engine")
368
+ if cwd is not None and not isinstance(cwd, str):
369
+ raise StorageIntegrityError("invalid explicit cwd filter")
370
+ if state is not None and (not isinstance(state, str) or not state):
371
+ raise StorageIntegrityError("invalid lifecycle-state filter")
372
+ if limit is not None and (type(limit) is not int or limit < 0):
373
+ raise StorageIntegrityError("worker list limit must be nonnegative")
374
+ if type(max_owners) is not int or not (0 < max_owners <= 1000000):
375
+ raise StorageIntegrityError("invalid bounded owner scan limit")
376
+ # Exact global owner, not a name, short, native ID or fork source.
377
+ if parent is not None:
378
+ with self.lease():
379
+ parent_owner, _ = self._owned(parent)
380
+ parent = parent_owner.worker
381
+ rows: list[dict] = []
382
+ # ONE shared lease and ONE pinned fd per directory for the whole bounded
383
+ # scan. Per-row leases and root-anchored path walks cost ~30 open()
384
+ # calls and three generation inspections per owner (seconds per list
385
+ # on a 2.8k-owner fleet that the dashboard polls). Pure local reads.
386
+ with self.lease(), ExitStack() as dirs:
387
+ try:
388
+ owners_fd = dirs.enter_context(self._gen("owners"))
389
+ except FileNotFoundError:
390
+ return [] # new empty generation has no owners directory yet
391
+ with os.scandir(owners_fd) as iterator:
392
+ names = sorted(entry.name for entry in iterator
393
+ if entry.name.endswith(".json"))
394
+ if len(names) > max_owners:
395
+ raise StorageIntegrityError("owner inventory exceeds explicit scan budget")
396
+ engine_fds: dict[str, int] = {}
397
+ for name in names:
398
+ worker_id = canonical_worker_id(name[:-5])
399
+ owner = _read_json(owners_fd, name)
400
+ if (owner is None or owner.get("schemaVersion") != _OWNER_SCHEMA
401
+ or owner.get("workerId") != worker_id
402
+ or owner.get("generation") != self.generation
403
+ or owner.get("engine") not in ENGINES
404
+ or owner.get("status") not in ("worker", "indeterminate-reserved",
405
+ "quarantined")):
406
+ raise StorageIntegrityError("malformed global owner inventory entry")
407
+ if owner["status"] != "worker":
408
+ continue # known reservation, not an addressable worker
409
+ if engine is not None and owner["engine"] != engine:
410
+ continue
411
+ try:
412
+ if owner["engine"] not in engine_fds:
413
+ engine_fds[owner["engine"]] = dirs.enter_context(
414
+ self._gen("workers", owner["engine"]))
415
+ with _open_child_dir(engine_fds[owner["engine"]], worker_id) as wfd:
416
+ rec = _read_json(wfd, "record.json")
417
+ except FileNotFoundError:
418
+ rec = None
419
+ # Same owner/record agreement `_owned` enforces, on the fds above.
420
+ if rec is None:
421
+ raise StorageIntegrityError("active owner has no matching full-ID worker record")
422
+ if rec.get("workerId") != worker_id or rec.get("engine") != owner["engine"]:
423
+ raise StorageIntegrityError("record and unique global worker owner disagree")
424
+ proven_owner = WorkerOwner(worker_id, owner["engine"])
425
+ spawned = rec.get("spawnedBy")
426
+ intent = owner.get("createIntent")
427
+ if spawned != (intent.get("spawnedBy") if isinstance(intent, dict) else None):
428
+ raise StorageIntegrityError("unproven managed parent edge in worker record")
429
+ if spawned is not None:
430
+ if (not isinstance(spawned, dict)
431
+ or spawned.get("kind") != "ccx-worker"
432
+ or spawned.get("engine") not in ENGINES):
433
+ raise StorageIntegrityError("unproven managed parent edge in worker record")
434
+ try:
435
+ canonical_worker_id(spawned.get("workerId"))
436
+ except InvalidTarget as exc:
437
+ raise StorageIntegrityError("invalid owned parent full UUID") from exc
438
+ if parent is not None and (
439
+ spawned is None or f"{spawned['engine']}:{spawned['workerId']}" != parent
440
+ ):
441
+ continue
442
+ if cwd is not None and rec.get("cwd") != cwd:
443
+ continue
444
+ lifecycle = rec.get("lifecycle")
445
+ lc = lifecycle if isinstance(lifecycle, dict) else {}
446
+ durable_state = lc.get("state")
447
+ normalized_state = normalize_native_state(durable_state)
448
+ if owner["engine"] == "claude" and normalized_state == "idle":
449
+ from ccx.store.worker_state import claude_recorded_delivery_busy
450
+ # Stream owner-matched receipts and stop at the first
451
+ # actual pending new turn. File age is not a safe proxy
452
+ # for receipt timestamps (wall clock can move backwards).
453
+ def eligible_receipts():
454
+ try:
455
+ with self._gen("semantic-deliveries", "claude", worker_id) as rfd:
456
+ with os.scandir(rfd) as entries:
457
+ for entry in entries:
458
+ if not entry.name.endswith(".json"):
459
+ continue
460
+ try:
461
+ receipt = _read_json(rfd, entry.name)
462
+ except StorageIntegrityError:
463
+ continue
464
+ if (isinstance(receipt, dict)
465
+ and receipt.get("workerId") == worker_id
466
+ and receipt.get("engine") == "claude"
467
+ and receipt.get("generation") == self.generation):
468
+ yield receipt
469
+ except FileNotFoundError:
470
+ return
471
+ if claude_recorded_delivery_busy(rec, eligible_receipts()):
472
+ normalized_state = "busy"
473
+ if state is not None and state != normalized_state:
474
+ continue
475
+ born = rec.get("createdAt")
476
+ last = rec.get("lastTurnAt")
477
+ bad_stamps = []
478
+ if born is not None and (type(born) is not int or born < 0):
479
+ born = None
480
+ bad_stamps.append("createdAt")
481
+ if last is not None and (type(last) is not int or last < 0):
482
+ last = None
483
+ bad_stamps.append("lastTurnAt")
484
+ stats = rec.get("stats")
485
+ stats = stats if isinstance(stats, dict) else {}
486
+ args = rec.get("createArgs")
487
+ args = args if isinstance(args, dict) else {}
488
+ row = {
489
+ "worker": proven_owner.worker,
490
+ "worker_id": proven_owner.worker_id,
491
+ "engine": proven_owner.engine,
492
+ "short": proven_owner.short,
493
+ "name": rec.get("name"),
494
+ "cwd": rec.get("cwd"),
495
+ "spawned_by": spawned,
496
+ "state": normalized_state,
497
+ "state_source": "recorded", # no live/native guesses
498
+ "native_state": durable_state,
499
+ "terminal_only": bool(rec.get("terminalOnly")),
500
+ "created_at": born,
501
+ "last_turn_at": last,
502
+ "turns": stats.get("turns"),
503
+ **({"input_tokens": stats.get("inputTokens"),
504
+ "output_tokens": stats.get("outputTokens")}
505
+ if proven_owner.engine == "codex" else {}),
506
+ "requested_model": args.get("model"),
507
+ "requested_effort": args.get("effort"),
508
+ "loaded": recorded_host_loaded(proven_owner.engine, lc),
509
+ }
510
+ if bad_stamps:
511
+ row["warning"] = ("invalid optional timestamp: "
512
+ + ", ".join(bad_stamps) + "; treated as unknown")
513
+ rows.append(row)
514
+ if records is not None:
515
+ records[proven_owner.worker] = (proven_owner, rec)
516
+ # Presentation order never grants authority: two same-prefix/full
517
+ # distinct IDs remain separate independent rows, even if names match.
518
+ rows.sort(key=lambda r: (
519
+ -max(r.get("last_turn_at") or 0, r.get("created_at") or 0),
520
+ r["engine"], r["worker_id"],
521
+ ))
522
+ prefixes: dict[str, int] = {}
523
+ for row in rows:
524
+ prefixes[row["short"]] = prefixes.get(row["short"], 0) + 1
525
+ for row in rows:
526
+ row["ambiguous_short"] = prefixes[row["short"]] > 1
527
+ return rows[:limit] if limit is not None else rows
528
+
529
+ def owner_ids(self, *, prefix: str = "") -> list[str]:
530
+ """Sorted full UUIDs whose owner file name starts with ``prefix``.
531
+
532
+ Names only: callers must still ``resolve`` each candidate, which
533
+ validates the owner, its status and the worker record.
534
+ """
535
+ with self.lease():
536
+ try:
537
+ with self._gen("owners") as fd, os.scandir(fd) as iterator:
538
+ return sorted(entry.name[:-5] for entry in iterator
539
+ if entry.name.endswith(".json")
540
+ and entry.name.startswith(prefix))
541
+ except FileNotFoundError:
542
+ return []
543
+
544
+ def record(self, target: str) -> dict:
545
+ with self.lease():
546
+ _owner, record = self._owned(target)
547
+ assert record is not None
548
+ return record
549
+
550
+ def variables_info(self, target: str, name: str | None = None) -> dict:
551
+ """Read full-ID durable variable bindings; no runtime Python/live-owner revival.
552
+
553
+ Variable *stateId* is an independent original identity and remains
554
+ unchanged when two managed full-UUID owners intentionally share state.
555
+ Reads are fd-relative bounded snapshots of atomic state replacements.
556
+ Writers serialize separately; readers never wait on their locks.
557
+ """
558
+ import keyword
559
+
560
+ if name is not None and (
561
+ not isinstance(name, str) or not name.isidentifier()
562
+ or keyword.iskeyword(name)
563
+ ):
564
+ raise StorageIntegrityError("variable name is not a Python identifier")
565
+ with self.lease():
566
+ owner, _source = self._owned(target)
567
+ root_result = {
568
+ "worker": owner.worker, "worker_id": owner.worker_id,
569
+ "engine": owner.engine, "short": owner.short,
570
+ "mode": None, "state_id": None, "names": [],
571
+ "available": False,
572
+ }
573
+ try:
574
+ with self._gen("variables", owner.engine) as fd:
575
+ sidecar = _read_json(fd, owner.worker_id + ".json")
576
+ except FileNotFoundError:
577
+ sidecar = None
578
+ if sidecar is None:
579
+ if name is not None:
580
+ raise UnknownWorker(
581
+ f"worker {owner.worker} has no variable {name!r} (has: none)"
582
+ )
583
+ return root_result
584
+ if (sidecar.get("schemaVersion") != 2
585
+ or sidecar.get("engine") != owner.engine
586
+ or sidecar.get("workerId") != owner.worker_id):
587
+ raise StorageIntegrityError("variable sidecar lacks full-ID owner provenance")
588
+ mode = sidecar.get("mode")
589
+ if mode not in ("durable", "live"):
590
+ raise StorageIntegrityError("unknown worker variable binding mode")
591
+ if mode == "live":
592
+ names = sidecar.get("names")
593
+ if not isinstance(names, list) or any(
594
+ not isinstance(item, str) or not item.isidentifier()
595
+ or keyword.iskeyword(item) for item in names
596
+ ):
597
+ raise StorageIntegrityError("malformed live variable names")
598
+ result = {**root_result, "mode": "live", "names": sorted(names)}
599
+ if name is not None:
600
+ if name not in names:
601
+ raise UnknownWorker(
602
+ f"worker {owner.worker} has no variable {name!r} "
603
+ f"(has: {', '.join(sorted(names)) or 'none'})"
604
+ )
605
+ raise NativeActionDenied(
606
+ "live Python variable cannot be materialized; original "
607
+ "in-memory controller owner may be unavailable"
608
+ )
609
+ return result
610
+ state_id = sidecar.get("stateId")
611
+ if (not isinstance(state_id, str) or len(state_id) != 32
612
+ or any(c not in "0123456789abcdef" for c in state_id)):
613
+ raise StorageIntegrityError("durable variable stateId is invalid")
614
+ try:
615
+ with self._gen("variables", "states") as fd:
616
+ state = _read_json(fd, state_id + ".json")
617
+ except FileNotFoundError as exc:
618
+ raise StorageIntegrityError(
619
+ "full UUID durable variable binding references missing state"
620
+ ) from exc
621
+ if (state is None or state.get("schemaVersion") != 1
622
+ or state.get("stateId") != state_id
623
+ or not isinstance(state.get("names"), list)
624
+ or not isinstance(state.get("variables"), dict)):
625
+ raise StorageIntegrityError("durable variable state not sealed/owned")
626
+ variables = state["variables"]
627
+ names = state["names"]
628
+ if (len(set(names)) != len(names)
629
+ or not all(isinstance(item, str) and item.isidentifier()
630
+ and not keyword.iskeyword(item) for item in names)
631
+ or set(names) != set(variables)):
632
+ raise StorageIntegrityError(
633
+ "durable variable names differ from original state entries"
634
+ )
635
+ # The worker sidecar is immutable during a concurrent state
636
+ # access by design. If it did change across this read, refuse
637
+ # rather than returning data belonging to an old/other binding.
638
+ try:
639
+ with self._gen("variables", owner.engine) as fd:
640
+ after = _read_json(fd, owner.worker_id + ".json")
641
+ except FileNotFoundError:
642
+ after = None
643
+ if after != sidecar:
644
+ raise StorageIntegrityError("worker variable binding changed while read")
645
+ result = {
646
+ **root_result,
647
+ "mode": "durable", "state_id": state_id,
648
+ "names": sorted(names), "available": True,
649
+ }
650
+ if name is not None:
651
+ if name not in variables:
652
+ raise UnknownWorker(
653
+ f"worker {owner.worker} has no variable {name!r} "
654
+ f"(has: {', '.join(sorted(names)) or 'none'})"
655
+ )
656
+ result["name"] = name
657
+ result["value"] = variables[name]
658
+ return result
659
+
660
+ def read_worker_sidecar(self, target: str, filename: str) -> dict | None:
661
+ """Descriptor-relative JSON sidecar read, never a returned pathname open.
662
+
663
+ Host/client readers use this rather than opening an unpinned
664
+ pathname returned by worker_path().
665
+ """
666
+ if filename not in ("record.json", "settings.json",
667
+ "statusline.json", "compact.json",
668
+ "context-observation.json"):
669
+ raise StorageIntegrityError("unsupported CCX-owned JSON worker sidecar")
670
+ with self.lease():
671
+ owner, _record = self._owned(target)
672
+ with self._gen("workers", owner.engine, owner.worker_id) as dir_fd:
673
+ return _read_json(dir_fd, filename)
674
+
675
+ def worker_path(self, target: str, filename: str = "record.json") -> Path:
676
+ if filename not in ("record.json", "settings.json",
677
+ "statusline.json", "events.ndjson",
678
+ "evidence.jsonl", "compact.json", "context-observation.json"):
679
+ raise StorageIntegrityError("unknown worker sidecar family")
680
+ with self.lease():
681
+ owner, _ = self._owned(target)
682
+ return self.pointer.generation_root / "workers" / owner.engine / owner.worker_id / filename
683
+
684
+ def receipt_path(self, target: str, family: str, token: str) -> Path:
685
+ if family not in _FAMILIES or not isinstance(token, str) or not token or len(token) > 512 or "\x00" in token:
686
+ raise StorageIntegrityError("invalid semantic operation family/token")
687
+ with self.lease():
688
+ owner, _rec = self._owned(target)
689
+ return (self.pointer.generation_root / "receipts" / family / owner.engine
690
+ / owner.worker_id / (hashlib.sha256(token.encode("utf-8")).hexdigest() + ".json"))
691
+
692
+ @staticmethod
693
+ def _new_claim(worker_id: str, engine: str, generation: str, fingerprint: str,
694
+ native: dict, anchor: dict | None) -> dict:
695
+ return {
696
+ "schemaVersion": _CLAIM_SCHEMA, "generation": generation,
697
+ "workerId": worker_id, "engine": engine,
698
+ "createSha256": fingerprint, "fingerprintVersion": 2,
699
+ "forkAnchor": anchor, "nativeBinding": native,
700
+ "activatedAtMs": None,
701
+ "nativeActivation": {
702
+ "state": "unprepared", "source": None,
703
+ "submissionId": None, "nativeEvidenceSha256": None,
704
+ },
705
+ }
706
+
707
+ def _check_original_create_intent(self, old: dict, *, engine: str,
708
+ worker_id: str, fingerprint: str,
709
+ native: dict, anchor: dict | None,
710
+ provenance: str | None = None,
711
+ imported_from: Mapping | None = None) -> dict:
712
+ if old.get("engine") != engine:
713
+ raise OwnerConflict("full UUID globally belongs to a different engine")
714
+ if (old.get("workerId") != worker_id or old.get("generation") != self.generation
715
+ or old.get("schemaVersion") != _OWNER_SCHEMA):
716
+ raise StorageIntegrityError("existing global owner identity/generation mismatch")
717
+ intent = old.get("createIntent")
718
+ if not isinstance(intent, dict):
719
+ raise CreationIndeterminate("owner predates durable create intent; never infer native absence")
720
+ origin = old.get("provenance")
721
+ source = intent.get("importedFrom")
722
+ if origin == "ccx-convert":
723
+ if provenance is not None and provenance != origin:
724
+ raise CreationConflict("converted UUID is not a fresh creation/import")
725
+ if (intent.get("createSha256") != fingerprint
726
+ or intent.get("nativeBinding") != native
727
+ or intent.get("forkAnchor") != anchor):
728
+ raise CreationConflict("converted immutable owner intent changed")
729
+ claim = self._claim(worker_id)
730
+ record = self._record(engine, worker_id)
731
+ if not isinstance(claim, dict) or not isinstance(record, dict):
732
+ raise StorageIntegrityError("converted worker has no attested claim/record")
733
+ _converted_binding(old, claim, record)
734
+ return intent
735
+ if (origin not in {"ccx-new", "ccx-import"}
736
+ or (provenance is not None and provenance != origin)
737
+ or (origin == "ccx-import" and
738
+ (engine != "claude" or not isinstance(source, dict)
739
+ or source.get("sessionId") != native.get("sessionId")))
740
+ or (origin == "ccx-new" and source is not None)
741
+ or (provenance is not None and source !=
742
+ (dict(imported_from) if imported_from is not None else None))
743
+ or intent.get("fingerprintVersion") != 2
744
+ or intent.get("createSha256") != fingerprint
745
+ or intent.get("nativeBinding") != native
746
+ or intent.get("forkAnchor") != anchor):
747
+ raise CreationConflict("same UUID has different original owner create specification")
748
+ if intent.get("activationStateAtReservation") != "unprepared":
749
+ raise CreationIndeterminate("owner does not prove native activation was unprepared")
750
+ return intent
751
+
752
+ def creation_binding(self, target: str, *, engine: str,
753
+ fingerprint: str, provenance: str | None = None,
754
+ imported_from: Mapping | None = None) -> dict | None:
755
+ """Read immutable new-v2 owner intent, including an indeterminate owner.
756
+
757
+ Omitted provenance observes the original owner; creation passes its explicit origin.
758
+ A generated full ID must NOT remint its original Claude native session
759
+ ID after a crash between owner intent and claim publication. Checking
760
+ this owner intent is read-only and never authorizes a native restart.
761
+ """
762
+ from ccx.store.identity import parse_managed_target
763
+ parsed = parse_managed_target(target)
764
+ parsed.qualified(engine)
765
+ with self.lease():
766
+ old = self._owner(parsed.worker_id)
767
+ if old is None:
768
+ return None
769
+ intent = old.get("createIntent")
770
+ if not isinstance(intent, dict):
771
+ raise CreationIndeterminate(
772
+ "existing worker identity lacks original v2 native binding proof"
773
+ )
774
+ native = intent.get("nativeBinding")
775
+ anchor = intent.get("forkAnchor")
776
+ if not isinstance(native, dict):
777
+ raise CreationIndeterminate("owned create intent has no native binding")
778
+ self._check_original_create_intent(
779
+ old, engine=engine, worker_id=parsed.worker_id,
780
+ fingerprint=fingerprint, native=native, anchor=anchor,
781
+ provenance=provenance, imported_from=imported_from,
782
+ )
783
+ return dict(native)
784
+
785
+ @staticmethod
786
+ def _check_record_origin(owner: dict, record: Mapping) -> None:
787
+ """First claimed source and recovered record must describe one origin."""
788
+ intent = owner.get("createIntent") or {}
789
+ if record.get("spawnedBy") != intent.get("spawnedBy"):
790
+ raise CreationConflict("first record changed original managed caller")
791
+ if owner.get("provenance") == "ccx-convert":
792
+ cf = intent.get("convertedFrom") or {}
793
+ if record.get("importedFrom") != cf.get("importedFrom"):
794
+ raise CreationConflict("converted import history differs")
795
+ if cf.get("importedFrom") is not None and record.get("origin") != "ccx-import":
796
+ raise CreationConflict("converted import origin differs")
797
+ return
798
+ if owner.get("provenance") == "ccx-import":
799
+ if (record.get("origin") != "ccx-import"
800
+ or record.get("importedFrom") != intent.get("importedFrom")
801
+ or (record.get("native") or {}).get("sessionId") !=
802
+ (intent.get("nativeBinding") or {}).get("sessionId")):
803
+ raise CreationConflict("original imported source/record differs")
804
+ elif record.get("origin") == "ccx-import" or "importedFrom" in record:
805
+ raise CreationConflict("ordinary create contradicts import provenance")
806
+
807
+ def recover_first_record(self, reservation: CreationReservation) -> bool:
808
+ """Promote an exact previously fsynced first record after crash.
809
+
810
+ A new record with changing timestamps cannot be reconstructed from
811
+ mutable fields. Instead verify its immutable v2 spec/fork/native source
812
+ against original claim+owner and promote the *existing* bytes only
813
+ while nativeActivation is durably UNPREPARED.
814
+ """
815
+ wid = canonical_worker_id(reservation.worker_id)
816
+ with self.lease():
817
+ with self._claim_lock(wid):
818
+ self.revalidate()
819
+ old, claim = self._owner(wid), self._claim(wid)
820
+ if not old or not claim:
821
+ raise CreationIndeterminate("original owner/claim unavailable")
822
+ immutable = self._check_original_create_intent(
823
+ old, engine=reservation.engine, worker_id=wid,
824
+ fingerprint=reservation.create_sha256,
825
+ native=claim.get("nativeBinding"),
826
+ anchor=claim.get("forkAnchor"),
827
+ )
828
+ if claim.get("createSha256") != reservation.create_sha256:
829
+ raise CreationConflict("original immutable claim changed")
830
+ act = claim.get("nativeActivation")
831
+ if not isinstance(act, dict) or act.get("state") != "unprepared":
832
+ raise CreationIndeterminate(
833
+ "first record replay not safe after native activation intent"
834
+ )
835
+ original = self._record(reservation.engine, wid)
836
+ if original is None:
837
+ return False
838
+ self._check_record_origin(old, original)
839
+ if (original.get("workerId") != wid
840
+ or original.get("engine") != reservation.engine
841
+ or original.get("createSha256") != reservation.create_sha256
842
+ or original.get("fingerprintVersion") != 2
843
+ or not isinstance(original.get("createArgs"), dict)
844
+ or fingerprint_v2(
845
+ engine=reservation.engine, cwd=original.get("cwd"),
846
+ create_args=original["createArgs"],
847
+ fork_anchor=claim.get("forkAnchor"),
848
+ ) != reservation.create_sha256
849
+ or original.get("forkAnchor") != claim.get("forkAnchor")
850
+ or not isinstance(original.get("native"), dict)
851
+ or any(original["native"].get(k) != v for k, v
852
+ in claim.get("nativeBinding", {}).items())):
853
+ raise CreationIndeterminate(
854
+ "original first-record immutable full-UUID proof differs"
855
+ )
856
+ if old.get("status") == "indeterminate-reserved":
857
+ updated = dict(old)
858
+ updated["status"] = "worker"
859
+ self._write(("owners",), wid + ".json", updated)
860
+ elif old.get("status") != "worker":
861
+ raise CreationIndeterminate("original UUID remains quarantined")
862
+ return True
863
+
864
+ def _native_sid_owner(self, session_id: str) -> dict | None:
865
+ """Current owner files, including pending/converted records; no index."""
866
+ with self.lease():
867
+ try:
868
+ with self._gen("owners") as fd:
869
+ with os.scandir(fd) as entries:
870
+ names = sorted(e.name for e in entries if e.name.endswith(".json"))
871
+ except FileNotFoundError:
872
+ return None
873
+ if len(names) > 60000:
874
+ raise StorageIntegrityError("native owner census exceeds bounded scan")
875
+ found = None
876
+ for filename in names:
877
+ wid = canonical_worker_id(filename[:-5])
878
+ owner = self._owner(wid)
879
+ if (not isinstance(owner, dict) or owner.get("workerId") != wid
880
+ or owner.get("generation") != self.generation
881
+ or owner.get("schemaVersion") != _OWNER_SCHEMA
882
+ or owner.get("engine") not in ENGINES):
883
+ raise StorageIntegrityError("native owner census contains invalid owner")
884
+ if owner.get("status") not in {"worker", "indeterminate-reserved", "quarantined"}:
885
+ raise StorageIntegrityError("native owner has unsupported claim state")
886
+ if owner["engine"] != "claude":
887
+ continue
888
+ intent = owner.get("createIntent")
889
+ if intent is not None and not isinstance(intent, dict):
890
+ raise StorageIntegrityError("invalid original Claude owner intent")
891
+ binding = intent.get("nativeBinding") if isinstance(intent, dict) else None
892
+ if isinstance(intent, dict) and not isinstance(binding, dict):
893
+ raise StorageIntegrityError("unverifiable owned Claude native identity")
894
+ record = self._record("claude", wid)
895
+ if record is not None and not isinstance(record.get("native"), dict):
896
+ raise StorageIntegrityError("invalid owned native Claude session binding")
897
+ recorded = (record["native"].get("sessionId")
898
+ if isinstance(record, dict) else None)
899
+ bound = binding.get("sessionId") if binding is not None else None
900
+ if (record is not None and (record.get("workerId") != wid
901
+ or record.get("engine") != "claude")):
902
+ raise StorageIntegrityError("native owner record disagrees")
903
+ if bound is not None and recorded is not None and bound != recorded:
904
+ raise StorageIntegrityError("owner and worker bind different native sessions")
905
+ if bound is None and recorded is None:
906
+ # An unprovable Claude owner is not evidence that its native
907
+ # session differs. Never silently permit another owner.
908
+ raise CreationIndeterminate("unproven historical Claude native owner")
909
+ if (bound or recorded) != session_id:
910
+ continue
911
+ if found is not None:
912
+ raise StorageIntegrityError("multiple owners of one native Claude session")
913
+ claim = self._claim(wid)
914
+ activation = claim.get("nativeActivation") if isinstance(claim, dict) else None
915
+ proven = (owner.get("provenance") == "ccx-import"
916
+ and owner.get("status") == "worker"
917
+ and isinstance(intent, dict)
918
+ and isinstance(activation, dict)
919
+ and activation.get("state") == "confirmed"
920
+ and isinstance(record, dict)
921
+ and record.get("origin") == "ccx-import"
922
+ and (record.get("native") or {}).get("nativeState") == "session-bound"
923
+ and record.get("importedFrom") == intent.get("importedFrom"))
924
+ found = {"worker": f"claude:{wid}", "worker_id": wid,
925
+ "provenance": owner.get("provenance"),
926
+ "confirmed": proven,
927
+ "activation_state": (activation or {}).get("state") or "unknown"}
928
+ return found
929
+
930
+ @staticmethod
931
+ def _import_sid_lock_name(session_id: str) -> str:
932
+ return str(uuid5(NAMESPACE_URL, "ccx-import:" + session_id))
933
+
934
+ def import_native_owner(self, session_id: str) -> dict | None:
935
+ """Read one original owner under the same admission lock as import."""
936
+ with self._claim_lock(self._import_sid_lock_name(session_id)):
937
+ return self._native_sid_owner(session_id)
938
+
939
+ def reserve_import_creation(self, target: str, *, session_id: str,
940
+ fingerprint: str, imported_from: Mapping,
941
+ native_binding: Mapping) -> CreationReservation:
942
+ """One atomically admitted SID→independent worker owner reservation."""
943
+ with self._claim_lock(self._import_sid_lock_name(session_id)):
944
+ claimed = self._native_sid_owner(session_id)
945
+ wid = parse_managed_target(target).worker_id
946
+ if claimed is not None and claimed["worker_id"] != wid:
947
+ raise CreationConflict("native Claude session already owned by " + claimed["worker"])
948
+ return self.reserve_creation(
949
+ target, engine="claude", fingerprint=fingerprint,
950
+ native_binding=native_binding, provenance="ccx-import",
951
+ imported_from=imported_from)
952
+
953
+ def reserve_creation(self, target: str, *, engine: str, fingerprint: str,
954
+ native_binding: Mapping | None = None,
955
+ fork_anchor: Mapping | None = None,
956
+ provenance: str = "ccx-new",
957
+ imported_from: Mapping | None = None,
958
+ spawned_by: Mapping | None = None,
959
+ _failpoint: Callable[[str], None] | None = None) -> CreationReservation:
960
+ """Globally exact-ID creation fence. never performs native activation.
961
+
962
+ An existing orphan, historical claimless worker or unproven/changed
963
+ create preimage never triggers a second native start. A crash after
964
+ owner publication but before claim requires explicit reconciliation.
965
+ """
966
+ parsed = parse_managed_target(target)
967
+ if engine not in ENGINES:
968
+ raise CreationConflict("invalid engine")
969
+ parsed.qualified(engine)
970
+ if not isinstance(fingerprint, str) or not _HASH.fullmatch(fingerprint):
971
+ raise CreationConflict("invalid immutable create digest")
972
+ if native_binding is not None and not isinstance(native_binding, Mapping):
973
+ raise CreationConflict("native binding must be a mapping")
974
+ if fork_anchor is not None and not isinstance(fork_anchor, Mapping):
975
+ raise CreationConflict("fork anchor must be a mapping")
976
+ native = dict(native_binding or {})
977
+ anchor = dict(fork_anchor) if fork_anchor is not None else None
978
+ if provenance not in {"ccx-new", "ccx-import"}:
979
+ raise CreationConflict("unsupported CCX owner origin")
980
+ frozen_import = dict(imported_from) if imported_from is not None else None
981
+ if (provenance == "ccx-import" and
982
+ (engine != "claude" or not isinstance(frozen_import, dict)
983
+ or frozen_import.get("sessionId") != native.get("sessionId"))):
984
+ raise CreationConflict("import provenance/native session mismatch")
985
+ if provenance == "ccx-new" and frozen_import is not None:
986
+ raise CreationConflict("non-import claim cannot carry imported history")
987
+ with self.lease():
988
+ with self._claim_lock(parsed.worker_id):
989
+ self.revalidate()
990
+ old = self._owner(parsed.worker_id)
991
+ # The first owner intent wins; later caller environment must
992
+ # neither overwrite its edge nor add an authority requirement
993
+ # to a compatible retry.
994
+ if old is None and spawned_by is not None:
995
+ if (not isinstance(spawned_by, Mapping)
996
+ or set(spawned_by) != {"kind", "engine", "workerId"}
997
+ or spawned_by.get("kind") != "ccx-worker"
998
+ or spawned_by.get("engine") not in ENGINES):
999
+ raise CreationConflict("managed caller stamp is invalid")
1000
+ caller, _ = self._owned(
1001
+ f"{spawned_by['engine']}:{spawned_by['workerId']}"
1002
+ )
1003
+ spawned_by = {"kind": "ccx-worker", "engine": caller.engine,
1004
+ "workerId": caller.worker_id}
1005
+ claim = self._claim(parsed.worker_id)
1006
+ if old is not None:
1007
+ self._check_original_create_intent(
1008
+ old, engine=engine, worker_id=parsed.worker_id,
1009
+ fingerprint=fingerprint, native=native, anchor=anchor,
1010
+ provenance=provenance, imported_from=frozen_import,
1011
+ )
1012
+ if claim is None:
1013
+ # same immutable intent may finish only the missing
1014
+ # claim in preparation phase. No native dispatch can be
1015
+ # reached without the claim AND a later durable
1016
+ # prepared/submitted fence. A missing record for an
1017
+ # already-active owner is never recreated here.
1018
+ if old.get("status") != "indeterminate-reserved":
1019
+ raise CreationIndeterminate("missing claim under committed owner")
1020
+ if self._record(engine, parsed.worker_id) is not None:
1021
+ raise CreationIndeterminate("record exists without claim; native result unknown")
1022
+ claim = self._new_claim(
1023
+ parsed.worker_id, engine, self.generation, fingerprint,
1024
+ native, anchor,
1025
+ )
1026
+ self._write(("claims",), parsed.worker_id + ".json", claim)
1027
+ if _failpoint:
1028
+ _failpoint("after-claim")
1029
+ return CreationReservation(parsed.worker_id, engine, fingerprint,
1030
+ True, False, "unprepared",
1031
+ spawned_by=old["createIntent"].get("spawnedBy"))
1032
+ if (claim.get("engine") != engine
1033
+ or claim.get("workerId") != parsed.worker_id
1034
+ or claim.get("createSha256") != fingerprint
1035
+ or claim.get("fingerprintVersion") != 2
1036
+ or claim.get("nativeBinding") != native
1037
+ or claim.get("forkAnchor") != anchor):
1038
+ raise CreationConflict("same UUID has incompatible immutable create intent")
1039
+ if claim.get("generation") != self.generation:
1040
+ raise StorageIntegrityError("claim generation disagrees")
1041
+ activation = claim.get("nativeActivation")
1042
+ if not isinstance(activation, dict) or activation.get("state") not in (
1043
+ "unprepared", "prepared", "submitted-unknown", "confirmed", "rejected", "local-refused",
1044
+ ):
1045
+ # A pre-fence record has NO evidence of never-started.
1046
+ raise CreationIndeterminate("original activation fate is not durably known")
1047
+ if claim.get("activatedAtMs") is not None and activation.get("state") != "confirmed":
1048
+ raise CreationIndeterminate("old activation timestamp without matching proof")
1049
+ existing_record = self._record(engine, parsed.worker_id)
1050
+ if old.get("status") == "worker" and existing_record is None:
1051
+ raise CreationIndeterminate("active claim record missing; never create another native worker")
1052
+ if old.get("status") not in ("worker", "indeterminate-reserved"):
1053
+ raise CreationIndeterminate("historical/quarantined reservation cannot recreate")
1054
+ return CreationReservation(
1055
+ parsed.worker_id, engine, fingerprint, True,
1056
+ existing_record is not None, activation["state"],
1057
+ activation.get("submissionId"),
1058
+ old["createIntent"].get("spawnedBy"),
1059
+ )
1060
+ if claim is not None:
1061
+ raise CreationIndeterminate("orphan claim cannot recreate missing global owner")
1062
+ # Owner creates an immutable original v2 intent before the
1063
+ # claim. If this write crashes, the caller-supplied AND the
1064
+ # freshly generated UUID remain owned; no reminting.
1065
+ self._write(("owners",), parsed.worker_id + ".json", {
1066
+ "schemaVersion": _OWNER_SCHEMA, "generation": self.generation,
1067
+ "workerId": parsed.worker_id, "engine": engine,
1068
+ "status": "indeterminate-reserved", "provenance": provenance,
1069
+ "createIntent": {
1070
+ "fingerprintVersion": 2, "createSha256": fingerprint,
1071
+ "nativeBinding": native, "forkAnchor": anchor,
1072
+ **({"spawnedBy": spawned_by} if spawned_by is not None else {}),
1073
+ "activationStateAtReservation": "unprepared",
1074
+ **({"importedFrom": frozen_import}
1075
+ if frozen_import is not None else {}),
1076
+ },
1077
+ })
1078
+ if _failpoint:
1079
+ _failpoint("after-owner")
1080
+ self._write(
1081
+ ("claims",), parsed.worker_id + ".json",
1082
+ self._new_claim(parsed.worker_id, engine, self.generation,
1083
+ fingerprint, native, anchor),
1084
+ )
1085
+ if _failpoint:
1086
+ _failpoint("after-claim")
1087
+ return CreationReservation(parsed.worker_id, engine, fingerprint,
1088
+ False, False, "unprepared",
1089
+ spawned_by=dict(spawned_by) if spawned_by is not None else None)
1090
+
1091
+ def publish_first_record(self, reservation: CreationReservation,
1092
+ record: Mapping, *, _failpoint: Callable[[str], None] | None = None) -> None:
1093
+ """Commit first record in full-ID space; no native session/thread start."""
1094
+ canonical_worker_id(reservation.worker_id)
1095
+ if record.get("workerId") != reservation.worker_id or record.get("engine") != reservation.engine:
1096
+ raise StorageIntegrityError("initial record owner/native binding mismatch")
1097
+ if not isinstance(record.get("native"), dict):
1098
+ raise StorageIntegrityError("first record needs separate native binding")
1099
+ with self.lease():
1100
+ with self._claim_lock(reservation.worker_id):
1101
+ self.revalidate()
1102
+ old, claim = self._owner(reservation.worker_id), self._claim(reservation.worker_id)
1103
+ if not old or not claim or (old.get("engine"), old.get("workerId"), old.get("generation")) != (
1104
+ reservation.engine, reservation.worker_id, self.generation
1105
+ ):
1106
+ raise CreationIndeterminate("missing original owner/create claim")
1107
+ if (claim.get("createSha256") != reservation.create_sha256
1108
+ or claim.get("fingerprintVersion") != 2
1109
+ or claim.get("workerId") != reservation.worker_id):
1110
+ raise CreationConflict("initial record differs from original immutable claim")
1111
+ if claim.get("activatedAtMs") is not None:
1112
+ raise CreationIndeterminate("original native activation may have happened")
1113
+ if not isinstance(record.get("native"), dict):
1114
+ raise StorageIntegrityError("native binding requires separate typed identity")
1115
+ expected_binding = claim.get("nativeBinding")
1116
+ if not isinstance(expected_binding, dict) or any(
1117
+ record["native"].get(k) != v for k, v in expected_binding.items()
1118
+ ):
1119
+ raise CreationConflict("initial record contradicts immutable native binding intent")
1120
+ self._check_record_origin(old, record)
1121
+ original = self._record(reservation.engine, reservation.worker_id)
1122
+ if original is not None:
1123
+ self._check_record_origin(old, original)
1124
+ if original is None:
1125
+ self._write(("workers", reservation.engine, reservation.worker_id),
1126
+ "record.json", dict(record))
1127
+ elif _digest(original) != _digest(dict(record)):
1128
+ # Two processes can each inspect a missing record and
1129
+ # construct different *mutable* name/createdAt fields
1130
+ # before the first one atomically publishes. Different
1131
+ # prepared documents must not overwrite one another,
1132
+ # but a verified identical original immutable v2 spec,
1133
+ # native source and fork-anchor can safely adopt the
1134
+ # first published bytes under this UUID claim lock.
1135
+ if (claim.get("nativeActivation", {}).get("state") != "unprepared"
1136
+ or original.get("workerId") != reservation.worker_id
1137
+ or original.get("engine") != reservation.engine
1138
+ or original.get("fingerprintVersion") != 2
1139
+ or original.get("createSha256") != reservation.create_sha256
1140
+ or not isinstance(original.get("createArgs"), dict)
1141
+ or fingerprint_v2(
1142
+ engine=reservation.engine,
1143
+ cwd=original.get("cwd"),
1144
+ create_args=original["createArgs"],
1145
+ fork_anchor=claim.get("forkAnchor"),
1146
+ ) != reservation.create_sha256
1147
+ or original.get("forkAnchor") != claim.get("forkAnchor")
1148
+ or not isinstance(original.get("native"), dict)
1149
+ or any(original["native"].get(k) != v for k, v
1150
+ in expected_binding.items())):
1151
+ raise CreationConflict(
1152
+ "existing first record lacks original immutable spec; no overwrite"
1153
+ )
1154
+ # Preserve the first writer's name, timestamp, current
1155
+ # revision and any subsequent separate metadata patches.
1156
+ if _failpoint:
1157
+ _failpoint("after-record")
1158
+ if old.get("status") == "worker":
1159
+ return # idempotent record-only retry
1160
+ if old.get("status") != "indeterminate-reserved":
1161
+ raise CreationIndeterminate("non-recreatable historical owner state")
1162
+ promoted = dict(old)
1163
+ promoted["status"] = "worker"
1164
+ self._write(("owners",), reservation.worker_id + ".json", promoted)
1165
+
1166
+ def native_activation(self, target: str) -> dict:
1167
+ """Read-only original claim activation fate; no synthetic native proof."""
1168
+ with self.lease():
1169
+ owner, _ = self._owned(target)
1170
+ claim = self._claim(owner.worker_id)
1171
+ if claim is None:
1172
+ raise CreationIndeterminate("historical claimless worker has no activation proof")
1173
+ activation = claim.get("nativeActivation")
1174
+ if not isinstance(activation, dict) or activation.get("state") not in (
1175
+ "unprepared", "prepared", "submitted-unknown", "confirmed", "rejected", "local-refused",
1176
+ ):
1177
+ raise CreationIndeterminate("activation state missing or unsupported")
1178
+ return dict(activation)
1179
+
1180
+ def prepare_native_activation(self, reservation: CreationReservation, *,
1181
+ native_source: str) -> NativeActivation:
1182
+ """Durably bind one original native activation source, no provider call.
1183
+
1184
+ Only a fully committed *first* record with positive
1185
+ pre-native provenance may prepare. A no-thread migrated Codex worker,
1186
+ historical claimless owner, or an already submitted activation cannot.
1187
+ """
1188
+ if (not isinstance(native_source, str) or not native_source
1189
+ or len(native_source) > 512 or "\x00" in native_source):
1190
+ raise CreationConflict("missing/invalid original native activation source")
1191
+ wid = canonical_worker_id(reservation.worker_id)
1192
+ with self.lease():
1193
+ with self._claim_lock(wid):
1194
+ old, claim = self._owner(wid), self._claim(wid)
1195
+ if not old or not claim or old.get("status") != "worker":
1196
+ raise CreationIndeterminate("native prepare requires original committed first record")
1197
+ if old.get("provenance") not in {"ccx-new", "ccx-import"}:
1198
+ raise CreationIndeterminate("conversion is not an original native start")
1199
+ self._check_original_create_intent(
1200
+ old, engine=reservation.engine, worker_id=wid,
1201
+ fingerprint=reservation.create_sha256,
1202
+ native=claim.get("nativeBinding"), anchor=claim.get("forkAnchor"),
1203
+ )
1204
+ if claim.get("createSha256") != reservation.create_sha256:
1205
+ raise CreationConflict("original create claim differs")
1206
+ record = self._record(reservation.engine, wid)
1207
+ if not isinstance(record, dict):
1208
+ raise CreationIndeterminate("missing exact first worker record")
1209
+ self._check_record_origin(old, record)
1210
+ if record.get("terminalOnly") or record.get("nativeCreate"):
1211
+ raise CreationIndeterminate("worker has unknown/previous native-start evidence")
1212
+ native = record.get("native")
1213
+ if not isinstance(native, dict) or native.get("nativeState") in (
1214
+ "unbound-unknown", "submitted-or-unknown",
1215
+ ):
1216
+ raise CreationIndeterminate("no positive first-create native binding")
1217
+ if reservation.engine == "codex":
1218
+ if native.get("threadId") is not None:
1219
+ raise CreationIndeterminate("Codex record has native thread before first activation")
1220
+ if native_source != f"ccx-worker:{wid}":
1221
+ raise CreationConflict("native Codex source must name full CCX UUID")
1222
+ else:
1223
+ sid = native.get("sessionId")
1224
+ if not isinstance(sid, str) or native_source != f"claude-session:{sid}":
1225
+ raise CreationConflict("native Claude source must retain exact original sessionId")
1226
+ activation = claim.get("nativeActivation")
1227
+ if not isinstance(activation, dict):
1228
+ raise CreationIndeterminate("original activation fence unavailable")
1229
+ state = activation.get("state")
1230
+ if state == "prepared" and activation.get("source") == native_source:
1231
+ return NativeActivation(wid, reservation.engine, native_source,
1232
+ activation["submissionId"], state, self.generation)
1233
+ if state != "unprepared":
1234
+ raise CreationIndeterminate("native activation already submitted/settled/ambiguous")
1235
+ token = str(uuid4())
1236
+ updated = dict(claim)
1237
+ updated["nativeActivation"] = {
1238
+ "state": "prepared", "source": native_source,
1239
+ "submissionId": token, "nativeEvidenceSha256": None,
1240
+ }
1241
+ self._write(("claims",), wid + ".json", updated)
1242
+ return NativeActivation(wid, reservation.engine, native_source, token,
1243
+ "prepared", self.generation)
1244
+
1245
+ def native_activation_dispatch(self, prepared: NativeActivation, *,
1246
+ bounded_submit: Callable[[float], Mapping | None],
1247
+ dispatch_budget_s: float = 1.0,
1248
+ _failpoint: Callable[[str], None] | None = None) -> dict:
1249
+ """Exactly one original native submission under a *bounded* activation lease.
1250
+
1251
+ The provided transport function MUST send one bounded native request
1252
+ and honor the supplied deadline; it must never wait for a model turn.
1253
+ This atomic lock+state+transport admission is mandatory: no separate
1254
+ unlocked submitted→provider gap where a cutover could flip generation.
1255
+ The submitted-unknown fence is fsynced before calling the transport.
1256
+ No retry of a lost reply. A completed positive response is reconciled
1257
+ with confirm_native_activation, not guessed from idle.
1258
+ """
1259
+ if not 0 < dispatch_budget_s <= 2.0:
1260
+ raise GenerationError("native dispatch must have a bounded deadline <= 2 seconds")
1261
+ if prepared.generation != self.generation:
1262
+ raise StaleGeneration("native activation prepared for a different generation")
1263
+ if prepared.state != "prepared":
1264
+ raise CreationIndeterminate("only the original prepared submission can dispatch")
1265
+ wid = canonical_worker_id(prepared.worker_id)
1266
+ # One absolute admission budget includes the generation lease, claim
1267
+ # fsync, IPC admission and the daemon's eventual app-server pipe write.
1268
+ deadline = time.monotonic() + dispatch_budget_s
1269
+ with self.lease(timeout=min(dispatch_budget_s, 2.0)):
1270
+ with self._claim_lock(wid):
1271
+ self.revalidate()
1272
+ owner, record = self._owned(f"{prepared.engine}:{wid}")
1273
+ claim = self._claim(wid)
1274
+ if claim is None:
1275
+ raise CreationIndeterminate("original native activation claim missing")
1276
+ act = claim.get("nativeActivation")
1277
+ if (not isinstance(act, dict) or act.get("state") != "prepared"
1278
+ or act.get("source") != prepared.source
1279
+ or act.get("submissionId") != prepared.submission_id):
1280
+ raise CreationIndeterminate("native activation already submitted or claimed by another caller")
1281
+ if _failpoint:
1282
+ _failpoint("before-submitted")
1283
+ updated = dict(claim)
1284
+ updated["nativeActivation"] = {**act, "state": "submitted-unknown"}
1285
+ self._write(("claims",), wid + ".json", updated)
1286
+ if _failpoint:
1287
+ _failpoint("after-submitted")
1288
+ self.revalidate() # still inside activation lease and owner lock
1289
+ # The transport's timeout applies only to a SINGLE native IPC
1290
+ # dispatch. A late/lost ack cannot un-fence this submission.
1291
+ try:
1292
+ remaining = deadline - time.monotonic()
1293
+ if remaining <= 0:
1294
+ raise NativeEffectIndeterminate(
1295
+ "original native admission expired after durable submission fence"
1296
+ )
1297
+ evidence = bounded_submit(remaining)
1298
+ except Exception as exc:
1299
+ raise NativeEffectIndeterminate(
1300
+ "native transport result unknown; reconcile original submission"
1301
+ ) from exc
1302
+ if evidence is None or not isinstance(evidence, Mapping):
1303
+ raise NativeEffectIndeterminate(
1304
+ "no conclusive native acceptance evidence; original submission fenced"
1305
+ )
1306
+ # No assertion of success until explicit positive original
1307
+ # native evidence + matching ID/source is checked.
1308
+ return dict(evidence)
1309
+
1310
+ def confirm_native_activation(self, prepared: NativeActivation, *,
1311
+ native_evidence_sha256: str,
1312
+ native_binding: Mapping) -> NativeActivation:
1313
+ """Positive original native evidence only, never a retry of thread/start."""
1314
+ if (not isinstance(native_evidence_sha256, str)
1315
+ or not _HASH.fullmatch(native_evidence_sha256)
1316
+ or not isinstance(native_binding, Mapping)):
1317
+ raise CreationIndeterminate("native activation needs exact positive proof")
1318
+ if prepared.generation != self.generation:
1319
+ raise StaleGeneration("activation submitted under different generation")
1320
+ wid = canonical_worker_id(prepared.worker_id)
1321
+ with self.lease():
1322
+ with self._claim_lock(wid):
1323
+ owner, record = self._owned(f"{prepared.engine}:{wid}")
1324
+ claim = self._claim(wid)
1325
+ if not claim:
1326
+ raise CreationIndeterminate("missing original native activation claim")
1327
+ act = claim.get("nativeActivation")
1328
+ if (not isinstance(act, dict)
1329
+ or act.get("source") != prepared.source
1330
+ or act.get("submissionId") != prepared.submission_id):
1331
+ raise CreationIndeterminate("native submission identity mismatch")
1332
+ if act.get("state") == "confirmed":
1333
+ if (act.get("nativeEvidenceSha256") == native_evidence_sha256
1334
+ and act.get("nativeBinding") == dict(native_binding)):
1335
+ return NativeActivation(wid, prepared.engine, prepared.source,
1336
+ prepared.submission_id, "confirmed", self.generation)
1337
+ raise CreationConflict("native activation confirmation cannot change")
1338
+ if act.get("state") == "rejected":
1339
+ raise CreationConflict("native activation is terminal rejected; cannot confirm")
1340
+ if act.get("state") == "local-refused":
1341
+ raise CreationConflict("native activation is terminal local-refused; cannot confirm")
1342
+ if act.get("state") != "submitted-unknown":
1343
+ raise CreationIndeterminate("native activation never submitted")
1344
+ if prepared.engine == "claude":
1345
+ if native_binding.get("sessionId") != record["native"].get("sessionId"):
1346
+ raise CreationConflict("Claude native session does not match original CCX source")
1347
+ elif not isinstance(native_binding.get("threadId"), str) or not native_binding.get("threadId"):
1348
+ raise CreationConflict("Codex native confirmation requires exact threadId")
1349
+ result = dict(claim)
1350
+ result["nativeActivation"] = {
1351
+ **act, "state": "confirmed",
1352
+ "nativeEvidenceSha256": native_evidence_sha256,
1353
+ "nativeBinding": dict(native_binding),
1354
+ }
1355
+ # Claim confirmation is immutable and monotonically records
1356
+ # first native activation; follow-up normal operations may
1357
+ # only proceed after a separate record binding by CAS.
1358
+ result["activatedAtMs"] = int(time.time() * 1000)
1359
+ self._write(("claims",), wid + ".json", result)
1360
+ return NativeActivation(wid, prepared.engine, prepared.source,
1361
+ prepared.submission_id, "confirmed", self.generation)
1362
+
1363
+ def reconcile_codex_native_rejection(self, target: str) -> dict | None:
1364
+ """Seal/replay a definite original Codex provider rejection, offline.
1365
+
1366
+ The existing generation-scoped one-shot daemon effect marker must have
1367
+ fsynced the *matched coded JSON-RPC error response* before a claim can
1368
+ become terminal `rejected`. This method never contacts the app-server,
1369
+ never unlocks a native create for the same UUID and never infers a
1370
+ provider rejection from missing/timeout/transport evidence. It is the
1371
+ original claim's only submitted-unknown -> rejected transition, not a
1372
+ second creation receipt/state machine.
1373
+ """
1374
+ from ccx.store.identity import EngineMismatch
1375
+ wid = canonical_worker_id(parse_managed_target(target).worker_id)
1376
+ with self.lease():
1377
+ with self._claim_lock(wid):
1378
+ owner, record = self._owned(target)
1379
+ if owner.engine != "codex":
1380
+ raise EngineMismatch("native thread/start rejection requires Codex owner")
1381
+ claim = self._claim(wid)
1382
+ if not isinstance(claim, dict):
1383
+ raise CreationIndeterminate("missing original Codex claim")
1384
+ anchor = record.get("forkAnchor")
1385
+ if anchor is not None:
1386
+ lineage = record.get("forkedFrom")
1387
+ if (not isinstance(anchor, dict) or anchor.get("engine") != "codex"
1388
+ or anchor.get("cwd") != record.get("cwd")
1389
+ or not isinstance(anchor.get("createArgs"), dict)
1390
+ or fingerprint_v2(
1391
+ engine="codex", cwd=anchor["cwd"],
1392
+ create_args=anchor["createArgs"], fork_anchor=anchor,
1393
+ ) != record.get("createSha256")
1394
+ or not isinstance(lineage, dict)
1395
+ or lineage != codex_fork_lineage(anchor, child_id=wid)):
1396
+ raise CreationIndeterminate("Codex fork terminal original anchor mismatch")
1397
+ self._check_original_create_intent(
1398
+ self._owner(wid), engine="codex", worker_id=wid,
1399
+ fingerprint=record.get("createSha256"), native={}, anchor=anchor,
1400
+ )
1401
+ activation = claim.get("nativeActivation")
1402
+ source = f"ccx-worker:{wid}"
1403
+ # The first create is not yet natively submitted. There can
1404
+ # be no original provider rejection to recover here; leave
1405
+ # the existing one-shot prepare/admission path untouched.
1406
+ if (isinstance(activation, dict)
1407
+ and activation.get("state") in (
1408
+ "unprepared", "prepared", "local-refused",
1409
+ )):
1410
+ # Terminal local-refused is validated against its OWN
1411
+ # durable marker by the native-start reconciler, never
1412
+ # treated as a provider-side coded error.
1413
+ return None
1414
+ if (not isinstance(activation, dict)
1415
+ or activation.get("state") not in {
1416
+ "submitted-unknown", "confirmed", "rejected",
1417
+ }
1418
+ or activation.get("source") != source
1419
+ or not isinstance(activation.get("submissionId"), str)
1420
+ or not activation["submissionId"]
1421
+ or claim.get("workerId") != wid
1422
+ or claim.get("engine") != "codex"
1423
+ or claim.get("generation") != self.generation
1424
+ or claim.get("fingerprintVersion") != 2
1425
+ or claim.get("createSha256") != record.get("createSha256")
1426
+ or claim.get("nativeBinding") != {}
1427
+ or claim.get("forkAnchor") != anchor
1428
+ or record.get("fingerprintVersion") != 2
1429
+ or record.get("terminalOnly") or record.get("nativeCreate")
1430
+ or not isinstance(record.get("native"), dict)
1431
+ or (activation.get("state") != "confirmed" and (
1432
+ record["native"].get("threadId") is not None
1433
+ or record["native"].get("nativeState") != "not-started-proven"
1434
+ ))):
1435
+ raise CreationIndeterminate("original Codex submitted claim is not eligible")
1436
+ token = activation["submissionId"]
1437
+ key = hashlib.sha256(("activate\x00" + token).encode()).hexdigest()
1438
+ try:
1439
+ with self._gen("runtime-metadata", "codex-native-effects", wid) as fd:
1440
+ with _flock_at(fd, key + ".lock", exclusive=False,
1441
+ create=False, timeout=.5):
1442
+ effect = _read_json(fd, key + ".json")
1443
+ except (FileNotFoundError, GenerationUnavailable):
1444
+ if activation["state"] == "rejected":
1445
+ raise StorageIntegrityError(
1446
+ "sealed Codex rejection lacks its original one-shot effect"
1447
+ )
1448
+ return None
1449
+ if effect is None:
1450
+ if activation["state"] == "rejected":
1451
+ raise StorageIntegrityError("sealed Codex rejection marker missing")
1452
+ return None
1453
+ expected = {
1454
+ "workerId": wid, "engine": "codex",
1455
+ "generation": self.generation, "kind": "activate",
1456
+ "token": token, "state": "submitted-unknown",
1457
+ "source": source, "createSha256": record["createSha256"],
1458
+ }
1459
+ if (not isinstance(effect, dict)
1460
+ or any(effect.get(k) != v for k, v in expected.items())
1461
+ or (
1462
+ (effect.get("nativeMethod") != "thread/fork"
1463
+ or effect.get("forkAnchor") != anchor)
1464
+ if anchor is not None else
1465
+ ("nativeMethod" in effect or "forkAnchor" in effect)
1466
+ )
1467
+ or not isinstance(effect.get("daemonInstanceId"), str)
1468
+ or not effect["daemonInstanceId"]
1469
+ or type(effect.get("daemonPid")) is not int
1470
+ or effect["daemonPid"] <= 0
1471
+ or type(effect.get("appServerPid")) is not int
1472
+ or effect["appServerPid"] <= 0):
1473
+ raise StorageIntegrityError(
1474
+ "original Codex rejection effect identity/source/generation changed"
1475
+ )
1476
+ rejection = effect.get("nativeRejected")
1477
+ if rejection is None:
1478
+ if activation["state"] == "rejected":
1479
+ raise StorageIntegrityError("sealed rejection missing provider error proof")
1480
+ return None
1481
+ if (not isinstance(rejection, dict)
1482
+ or effect.get("nativeCandidate") is not None
1483
+ or effect.get("nativeNotSubmitted") is not None
1484
+ or type(rejection.get("code")) is not int
1485
+ or not isinstance(rejection.get("message"), str)
1486
+ or len(rejection["message"].encode("utf-8")) > 8192
1487
+ or type(rejection.get("requestId")) is not int
1488
+ or rejection["requestId"] <= 0
1489
+ or rejection.get("appServerPid") != effect["appServerPid"]
1490
+ or rejection.get("nativeErrorSha256") != _digest({
1491
+ "code": rejection["code"],
1492
+ "message": rejection["message"],
1493
+ "requestId": rejection["requestId"],
1494
+ "appServerPid": rejection["appServerPid"],
1495
+ })
1496
+ or not isinstance(rejection.get("messageSha256"), str)
1497
+ or rejection["messageSha256"] != hashlib.sha256(
1498
+ rejection["message"].encode("utf-8")
1499
+ ).hexdigest()
1500
+ or not isinstance(rejection.get("nativeReplySha256"), str)
1501
+ or not _HASH.fullmatch(rejection["nativeReplySha256"])):
1502
+ raise StorageIntegrityError(
1503
+ "Codex provider rejection marker malformed or contradictory"
1504
+ )
1505
+ evidence = _digest({
1506
+ "workerId": wid, "generation": self.generation,
1507
+ "createSha256": record["createSha256"],
1508
+ "source": source, "submissionId": token,
1509
+ "nativeRejected": rejection,
1510
+ })
1511
+ if activation["state"] == "confirmed":
1512
+ raise CreationConflict("confirmed native activation contradicts provider rejection")
1513
+ if activation["state"] == "rejected":
1514
+ if (activation.get("nativeRejection") != rejection
1515
+ or activation.get("nativeEvidenceSha256") != evidence):
1516
+ raise CreationConflict("terminal native rejection proof cannot change")
1517
+ else:
1518
+ newer = dict(claim)
1519
+ newer["nativeActivation"] = {
1520
+ **activation, "state": "rejected",
1521
+ "nativeEvidenceSha256": evidence,
1522
+ "nativeRejection": dict(rejection),
1523
+ }
1524
+ self._write(("claims",), wid + ".json", newer)
1525
+ return {
1526
+ "rejected": True, "workerId": wid, "engine": "codex",
1527
+ "source": source, "submissionId": token,
1528
+ "code": rejection["code"], "message": rejection["message"],
1529
+ "messageSha256": rejection["messageSha256"],
1530
+ "nativeEvidenceSha256": evidence,
1531
+ }
1532
+
1533
+ def reconcile_codex_local_refusal(self, target: str) -> dict | None:
1534
+ """Seal/replay only a durably proven local, pre-native Codex refusal.
1535
+
1536
+ The original one-shot daemon effect marker must prove that this exact
1537
+ attempt never entered a potentially writing `thread/start`. This is
1538
+ LOCAL attribution, not a cryptographic signature or a claim that an
1539
+ absent marker proves non-submission. One terminal claim, no new start.
1540
+ """
1541
+ from ccx.store.identity import EngineMismatch
1542
+ wid = canonical_worker_id(parse_managed_target(target).worker_id)
1543
+ with self.lease():
1544
+ with self._claim_lock(wid):
1545
+ owner, record = self._owned(target)
1546
+ if owner.engine != "codex":
1547
+ raise EngineMismatch("Codex local refusal requires original Codex owner")
1548
+ claim = self._claim(wid)
1549
+ if not isinstance(claim, dict):
1550
+ raise CreationIndeterminate("original local refusal claim absent")
1551
+ anchor = record.get("forkAnchor")
1552
+ if anchor is not None:
1553
+ lineage = record.get("forkedFrom")
1554
+ if (not isinstance(anchor, dict) or anchor.get("engine") != "codex"
1555
+ or anchor.get("cwd") != record.get("cwd")
1556
+ or not isinstance(anchor.get("createArgs"), dict)
1557
+ or fingerprint_v2(
1558
+ engine="codex", cwd=anchor["cwd"],
1559
+ create_args=anchor["createArgs"], fork_anchor=anchor,
1560
+ ) != record.get("createSha256")
1561
+ or not isinstance(lineage, dict)
1562
+ or lineage != codex_fork_lineage(anchor, child_id=wid)):
1563
+ raise CreationIndeterminate("Codex fork terminal original anchor mismatch")
1564
+ self._check_original_create_intent(
1565
+ self._owner(wid), engine="codex", worker_id=wid,
1566
+ fingerprint=record.get("createSha256"), native={}, anchor=anchor,
1567
+ )
1568
+ activation = claim.get("nativeActivation")
1569
+ if isinstance(activation, dict) and activation.get("state") in (
1570
+ "unprepared", "prepared",
1571
+ ):
1572
+ return None
1573
+ source = f"ccx-worker:{wid}"
1574
+ if (not isinstance(activation, dict)
1575
+ or activation.get("state") not in {
1576
+ "submitted-unknown", "confirmed", "rejected", "local-refused",
1577
+ }
1578
+ or activation.get("source") != source
1579
+ or not isinstance(activation.get("submissionId"), str)
1580
+ or not activation["submissionId"]
1581
+ or claim.get("workerId") != wid
1582
+ or claim.get("engine") != "codex"
1583
+ or claim.get("generation") != self.generation
1584
+ or claim.get("fingerprintVersion") != 2
1585
+ or claim.get("createSha256") != record.get("createSha256")
1586
+ or claim.get("nativeBinding") != {}
1587
+ or claim.get("forkAnchor") != anchor
1588
+ or record.get("fingerprintVersion") != 2
1589
+ or record.get("terminalOnly") or record.get("nativeCreate")
1590
+ or not isinstance(record.get("native"), dict)
1591
+ or (activation.get("state") not in {"confirmed", "rejected"}
1592
+ and (record["native"].get("threadId") is not None
1593
+ or record["native"].get("nativeState") != "not-started-proven"))):
1594
+ raise CreationIndeterminate("original local refusal claim/record ineligible")
1595
+ token = activation["submissionId"]
1596
+ key = hashlib.sha256(("activate\x00" + token).encode()).hexdigest()
1597
+ try:
1598
+ with self._gen("runtime-metadata", "codex-native-effects", wid) as fd:
1599
+ with _flock_at(fd, key + ".lock", exclusive=False,
1600
+ create=False, timeout=.5):
1601
+ effect = _read_json(fd, key + ".json")
1602
+ except FileNotFoundError:
1603
+ effect = None
1604
+ if effect is None:
1605
+ if activation["state"] == "local-refused":
1606
+ raise StorageIntegrityError("terminal local refusal lacks original effect marker")
1607
+ return None # absent evidence never establishes not-submitted
1608
+ expected = {
1609
+ "workerId": wid, "engine": "codex",
1610
+ "generation": self.generation, "kind": "activate",
1611
+ "token": token, "state": "submitted-unknown",
1612
+ "source": source, "createSha256": record["createSha256"],
1613
+ }
1614
+ if (not isinstance(effect, dict)
1615
+ or any(effect.get(k) != v for k, v in expected.items())
1616
+ or (
1617
+ (effect.get("nativeMethod") != "thread/fork"
1618
+ or effect.get("forkAnchor") != anchor)
1619
+ if anchor is not None else
1620
+ ("nativeMethod" in effect or "forkAnchor" in effect)
1621
+ )
1622
+ or not isinstance(effect.get("daemonInstanceId"), str)
1623
+ or not effect["daemonInstanceId"]
1624
+ or type(effect.get("daemonPid")) is not int
1625
+ or effect["daemonPid"] <= 0
1626
+ or (effect.get("appServerPid") is not None
1627
+ and (type(effect["appServerPid"]) is not int
1628
+ or effect["appServerPid"] <= 0))):
1629
+ raise StorageIntegrityError("original local refusal marker identity changed")
1630
+ proof = effect.get("nativeNotSubmitted")
1631
+ if proof is None:
1632
+ if activation["state"] == "local-refused":
1633
+ raise StorageIntegrityError("terminal local refusal has no original proof")
1634
+ return None
1635
+ if (not isinstance(proof, dict)
1636
+ or effect.get("nativeCandidate") is not None
1637
+ or effect.get("nativeRejected") is not None
1638
+ or proof.get("stage") not in {
1639
+ "capacity", "not-ready", "daemon-stopping",
1640
+ "transport-zero-bytes", "shell-env",
1641
+ }
1642
+ or proof.get("nativeRequestWritten") is not False
1643
+ or not isinstance(proof.get("reason"), str)
1644
+ or not proof["reason"]
1645
+ or len(proof["reason"].encode("utf-8")) > 1024
1646
+ or proof.get("reasonSha256") != hashlib.sha256(
1647
+ proof["reason"].encode("utf-8")
1648
+ ).hexdigest()
1649
+ or proof.get("appServerPid") != effect.get("appServerPid")
1650
+ or proof.get("localEvidenceSha256") != _digest({
1651
+ "workerId": wid, "generation": self.generation,
1652
+ "createSha256": record["createSha256"],
1653
+ "source": source, "submissionId": token,
1654
+ "daemonInstanceId": effect["daemonInstanceId"],
1655
+ "daemonPid": effect["daemonPid"],
1656
+ "nativeNotSubmitted": {
1657
+ "stage": proof["stage"], "reason": proof["reason"],
1658
+ "reasonSha256": proof["reasonSha256"],
1659
+ "nativeRequestWritten": False,
1660
+ "appServerPid": proof["appServerPid"],
1661
+ },
1662
+ })):
1663
+ raise StorageIntegrityError("local refusal proof malformed/contradictory")
1664
+ evidence = _digest({
1665
+ "workerId": wid, "generation": self.generation,
1666
+ "createSha256": record["createSha256"],
1667
+ "source": source, "submissionId": token,
1668
+ "nativeNotSubmitted": proof,
1669
+ })
1670
+ if activation["state"] in ("confirmed", "rejected"):
1671
+ raise CreationConflict("provider outcome contradicts local non-submission")
1672
+ if activation["state"] == "local-refused":
1673
+ if (activation.get("nativeNotSubmitted") != proof
1674
+ or activation.get("nativeEvidenceSha256") != evidence):
1675
+ raise CreationConflict("terminal local refusal proof cannot change")
1676
+ else:
1677
+ update = dict(claim)
1678
+ update["nativeActivation"] = {
1679
+ **activation, "state": "local-refused",
1680
+ "nativeEvidenceSha256": evidence,
1681
+ "nativeNotSubmitted": dict(proof),
1682
+ }
1683
+ self._write(("claims",), wid + ".json", update)
1684
+ return {
1685
+ "localRefused": True, "workerId": wid, "engine": "codex",
1686
+ "source": source, "submissionId": token,
1687
+ "reason": proof["reason"], "stage": proof["stage"],
1688
+ "nativeEvidenceSha256": evidence,
1689
+ }
1690
+
1691
+ def bind_confirmed_native(self, target: str) -> dict:
1692
+ """Publish exact positive native owner binding without stale record write.
1693
+
1694
+ Native creation claim confirmation may have been fsynced just before a
1695
+ process crash. This separate under-lock reconciliation is idempotent:
1696
+ only the original confirmed session/thread/source may be installed in
1697
+ the immutable native record. Never invent a thread from missing IDs,
1698
+ and never bind an unconfirmed/unknown submission.
1699
+ """
1700
+ parsed = parse_managed_target(target)
1701
+ with self.lease():
1702
+ with self._claim_lock(parsed.worker_id):
1703
+ with self._record_lock(parsed.worker_id):
1704
+ owner, record = self._owned(target)
1705
+ claim = self._claim(parsed.worker_id)
1706
+ activation = (claim.get("nativeActivation")
1707
+ if isinstance(claim, dict) else None)
1708
+ if (not isinstance(activation, dict)
1709
+ or activation.get("state") != "confirmed"
1710
+ or not isinstance(activation.get("nativeEvidenceSha256"), str)
1711
+ or not _HASH.fullmatch(activation["nativeEvidenceSha256"])):
1712
+ raise CreationIndeterminate(
1713
+ "positive immutable original native activation not confirmed"
1714
+ )
1715
+ expected = activation.get("nativeBinding")
1716
+ if not isinstance(expected, dict):
1717
+ raise CreationIndeterminate(
1718
+ "confirmed native binding lacks original source proof"
1719
+ )
1720
+ original = record.get("native")
1721
+ if not isinstance(original, dict):
1722
+ raise StorageIntegrityError("worker has no separate native binding")
1723
+ if owner.engine == "claude":
1724
+ session_id = expected.get("sessionId")
1725
+ if (not isinstance(session_id, str)
1726
+ or original.get("sessionId") != session_id
1727
+ or activation.get("source") != f"claude-session:{session_id}"):
1728
+ raise CreationConflict("confirmed native Claude session differs")
1729
+ name = "session-bound"
1730
+ binding = {"sessionId": session_id}
1731
+ else:
1732
+ thread_id = expected.get("threadId")
1733
+ if (not isinstance(thread_id, str) or not thread_id
1734
+ or original.get("threadId") not in (None, thread_id)
1735
+ or activation.get("source")
1736
+ != f"ccx-worker:{owner.worker_id}"):
1737
+ raise CreationConflict("confirmed native Codex source/thread differs")
1738
+ name = "thread-bound"
1739
+ binding = {"threadId": thread_id,
1740
+ "threadSource": activation["source"]}
1741
+ # A formerly terminal-only/migrated worker never reaches
1742
+ # this path: it has no nativeActivation originally created
1743
+ # by the new-v2 owner and matching claim.
1744
+ if record.get("terminalOnly"):
1745
+ raise NativeActionDenied(
1746
+ "terminal-only worker may not be rebound through creation"
1747
+ )
1748
+ newer = dict(record)
1749
+ newer_native = dict(original)
1750
+ newer_native.update(binding)
1751
+ newer_native["nativeState"] = name
1752
+ if newer_native == original:
1753
+ return record
1754
+ newer["native"] = newer_native
1755
+ revision = newer.get("recordRevision", 0)
1756
+ if type(revision) is not int or revision < 0:
1757
+ raise StorageIntegrityError("bad record revision during native binding")
1758
+ newer["recordRevision"] = revision + 1
1759
+ self._write(
1760
+ ("workers", owner.engine, owner.worker_id),
1761
+ "record.json", newer,
1762
+ )
1763
+ return newer
1764
+
1765
+ def authorize_native_action(self, target: str, action: str) -> WorkerOwner:
1766
+ """Read-only capability gate before any provider/client/daemon call.
1767
+
1768
+ Direct worker methods and durable-intent paths must pass this gate;
1769
+ it does not start a provider.
1770
+ """
1771
+ with self.lease():
1772
+ owner, source = self._owned(target)
1773
+ assert source is not None
1774
+ self._native_guard(source, action)
1775
+ return owner
1776
+
1777
+ @contextmanager
1778
+ def _record_lock(self, worker_id: str) -> Iterator[None]:
1779
+ """Per-full-UUID stable lock; only synchronous local RMW, no native waits."""
1780
+ with self._gen("locks", "records", create=True) as fd:
1781
+ with _flock_at(fd, worker_id + ".lock", exclusive=True, create=True):
1782
+ yield
1783
+
1784
+ @staticmethod
1785
+ def _record_field_path(path: str) -> tuple[str, ...]:
1786
+ """Restrict semantic mutation to mutable CCX-owned fields.
1787
+
1788
+ Never allow a generic stale record writer to modify immutable
1789
+ workerId/engine, native identity, original create/fork binding,
1790
+ receipt identity or recordRevision.
1791
+ """
1792
+ if not isinstance(path, str):
1793
+ raise StorageIntegrityError("record patch key must be a string")
1794
+ parts = tuple(path.split("."))
1795
+ if (len(parts) == 1 and parts[0] in (
1796
+ "name", "waitingFor", "activeCap", "lastTurnAt",
1797
+ "lastSettledAt", "lastSettledTurn",
1798
+ "backgroundTaskSummary",
1799
+ "status", "lastTurn", "lastReport", "activeTurnError",
1800
+ "pendingApprovals", "context", "activeCompact", "lastCompact",
1801
+ "compactCount", "evidence", "tokenUsage", "historyKnownEmpty",
1802
+ "nativeArchive", "resolvedModel", "parentNotifyFences",
1803
+ "lastTurnError", "pendingSend", "lastConversation", "nativeTurnStart",
1804
+ "quotaStop",
1805
+ )):
1806
+ return parts
1807
+ if (len(parts) == 2 and parts[0] in (
1808
+ "lifecycle", "createArgs", "stats"
1809
+ ) and parts[1].isidentifier() and not parts[1].startswith("_")):
1810
+ return parts
1811
+ raise StorageIntegrityError("immutable or unsupported worker record patch field")
1812
+
1813
+ @staticmethod
1814
+ def _at_field(doc: Mapping, path: tuple[str, ...]) -> object:
1815
+ value: object = doc
1816
+ for part in path:
1817
+ if not isinstance(value, Mapping):
1818
+ return None
1819
+ value = value.get(part)
1820
+ return value
1821
+
1822
+ @staticmethod
1823
+ def _set_field(doc: dict, path: tuple[str, ...], value: object) -> None:
1824
+ if len(path) == 1:
1825
+ doc[path[0]] = value
1826
+ return
1827
+ branch = doc.get(path[0])
1828
+ if branch is None:
1829
+ branch = {}
1830
+ doc[path[0]] = branch
1831
+ if not isinstance(branch, dict):
1832
+ raise StorageIntegrityError("record field parent is not an owned JSON object")
1833
+ branch[path[1]] = value
1834
+
1835
+ _SNAPSHOT_MUTABLE_FIELDS = frozenset({
1836
+ # All are CCX-owned mutable projection fields. Immutable worker
1837
+ # identity/native binding/fork/creation evidence and provider-owned
1838
+ # transcript fields are excluded intentionally.
1839
+ "name", "status", "waitingFor", "updatedAt",
1840
+ "lastTurnAt", "lastSettledAt", "lastSettledTurn",
1841
+ "backgroundTaskSummary",
1842
+ "lastTurn", "lastReport", "activeTurnError",
1843
+ "lastTurnError", "pendingApprovals", "context",
1844
+ "activeCompact", "lastCompact", "compactCount",
1845
+ "lifecycle", "createArgs", "stats",
1846
+ "nativeArchive", "lastConversation", "pendingSend",
1847
+ })
1848
+
1849
+ def replace_record_if_revision(
1850
+ self, target: str, record: Mapping, *,
1851
+ expected_revision: int,
1852
+ ) -> dict:
1853
+ """Exact full-ID fresh on-disk CAS for old host/codexd snapshot writers.
1854
+
1855
+ Unlike locking an arbitrarily stale whole-record write, this refuses
1856
+ a snapshot whose explicit monotonic revision is no longer current.
1857
+ It never changes immutable provider/native/owner/fork identity, never
1858
+ deletes unseen fields, and preserves independent modifications by
1859
+ rejecting stale contenders instead of losing data. Prefer the
1860
+ semantic field-patch API for liveness/config; this CAS is
1861
+ for audited operations that truly update several related fields.
1862
+ """
1863
+ if (not isinstance(record, Mapping) or type(expected_revision) is not int
1864
+ or expected_revision < 0
1865
+ or type(record.get("recordRevision")) is not int
1866
+ or record["recordRevision"] != expected_revision):
1867
+ raise RecordConflict(
1868
+ "explicit current whole-record revision proof is required"
1869
+ )
1870
+ parsed = parse_managed_target(target)
1871
+ snapshot = json.loads(json.dumps(dict(record), allow_nan=False))
1872
+ with self.lease():
1873
+ # Invalid, foreign and quarantined IDs must not leave even an
1874
+ # otherwise harmless full-UUID lock file behind.
1875
+ self._owned(target)
1876
+ with self._record_lock(parsed.worker_id):
1877
+ owner, latest = self._owned(target)
1878
+ assert latest is not None
1879
+ if (snapshot.get("workerId") != owner.worker_id
1880
+ or snapshot.get("engine") != owner.engine):
1881
+ raise StorageIntegrityError("CAS snapshot belongs to foreign full worker")
1882
+ actual_revision = latest.get("recordRevision", 0)
1883
+ if type(actual_revision) is not int or actual_revision < 0:
1884
+ raise StorageIntegrityError("durable worker revision malformed")
1885
+ if actual_revision != expected_revision:
1886
+ raise RecordConflict(
1887
+ "stale whole-record writer would overwrite another process"
1888
+ )
1889
+ if not set(latest).issubset(snapshot):
1890
+ raise StorageIntegrityError(
1891
+ "CAS snapshot cannot silently remove existing record fields"
1892
+ )
1893
+ modified = {
1894
+ key for key in set(snapshot) | set(latest)
1895
+ if snapshot.get(key) != latest.get(key)
1896
+ }
1897
+ if modified - self._SNAPSHOT_MUTABLE_FIELDS:
1898
+ raise StorageIntegrityError(
1899
+ "CAS snapshot cannot change native, owner, original "
1900
+ "creation/fork binding or unknown record fields"
1901
+ )
1902
+ for parent in ("lifecycle", "createArgs", "stats",
1903
+ "nativeArchive"):
1904
+ previous = latest.get(parent)
1905
+ now = snapshot.get(parent)
1906
+ if previous is not None:
1907
+ if not isinstance(previous, dict) or not isinstance(now, dict):
1908
+ raise StorageIntegrityError(
1909
+ "CAS cannot replace a structured worker record field"
1910
+ )
1911
+ if not set(previous).issubset(now):
1912
+ raise StorageIntegrityError(
1913
+ "CAS cannot drop existing nested record fields"
1914
+ )
1915
+ if not modified:
1916
+ return latest
1917
+ snapshot["recordRevision"] = expected_revision + 1
1918
+ snapshot["updatedAt"] = int(time.time() * 1000)
1919
+ self._write(("workers", owner.engine, owner.worker_id),
1920
+ "record.json", snapshot)
1921
+ return snapshot
1922
+
1923
+ def patch_lifecycle(self, target: str, *, changes: Mapping) -> dict:
1924
+ """Fresh per-worker atomic lifecycle field patch (not stale snapshot)."""
1925
+ if not isinstance(changes, Mapping) or not changes:
1926
+ raise StorageIntegrityError("nonempty lifecycle changes required")
1927
+ if any(not isinstance(k, str) or not k.isidentifier()
1928
+ or k.startswith("_") for k in changes):
1929
+ raise StorageIntegrityError("invalid lifecycle patch key")
1930
+ patches = {f"lifecycle.{k}": value for k, value in changes.items()}
1931
+ if "state" in changes:
1932
+ patches["lifecycle.since"] = int(time.time() * 1000)
1933
+ return self.mutate_record(target, patches=patches)
1934
+
1935
+ def rename_worker(self, target: str, *, engine: str, name: str | None) -> dict:
1936
+ """One canonical full-ID semantic rename for CLI and Python callers.
1937
+
1938
+ Never accept a native/session display alias; no host restart and no
1939
+ existing stale full-record snapshot write. Only the name field is
1940
+ patched under the exact worker's per-UUID fresh-disk CAS lock.
1941
+ """
1942
+ from ccx.store.records import sanitize_name
1943
+ if engine not in ENGINES:
1944
+ raise StorageIntegrityError("unsupported engine for managed rename")
1945
+ owner = self.resolve(target)
1946
+ if owner.engine != engine:
1947
+ raise EngineMismatch("target worker belongs to a different engine")
1948
+ new_name = sanitize_name(name) or (
1949
+ ("ccx-" if engine == "claude" else "codex-") + owner.short
1950
+ )
1951
+ return self.mutate_record(owner.worker, patches={"name": new_name})
1952
+
1953
+ def mutate_record(
1954
+ self, target: str, *, patches: Mapping[str, object] | None = None,
1955
+ increments: Mapping[str, int] | None = None,
1956
+ expect: Mapping[str, object] | None = None,
1957
+ expected_revision: int | None = None,
1958
+ removals: tuple[str, ...] = (),
1959
+ ) -> dict:
1960
+ """Fresh on-disk READ→semantic PATCH/CAS→fsync WRITE under owner lock.
1961
+
1962
+ Never read an old in-memory whole record and then overwrite another
1963
+ process's independent changes. A field-specific expectation gives CAS
1964
+ without globally serializing unrelated config/lifecycle.
1965
+ Monotonic integer increments cannot lose updates. This method must be
1966
+ called by host, daemon and CLI writers to preserve field-level CAS.
1967
+ """
1968
+ changes = dict(patches or {})
1969
+ additions = dict(increments or {})
1970
+ comparisons = dict(expect or {})
1971
+ # Only the two Claude host-owned transient fields may be REMOVED. This
1972
+ # is not a general deletion mechanism for owner/claim/native evidence.
1973
+ if (not isinstance(removals, tuple)
1974
+ or any(type(field) is not str for field in removals)
1975
+ or len(removals) != len(set(removals))
1976
+ or set(removals) - {"activeCap", "waitingFor"}):
1977
+ raise StorageIntegrityError("only Claude host transient fields may be removed")
1978
+ if not changes and not additions and not removals:
1979
+ raise StorageIntegrityError("record semantic mutation requires field changes")
1980
+ if (set(changes) & (set(additions) | set(removals))
1981
+ or set(additions) & set(removals)):
1982
+ raise StorageIntegrityError("record semantic field mutated more than once")
1983
+ if len(changes) + len(additions) > 64:
1984
+ raise StorageIntegrityError("record patch exceeds bounded field count")
1985
+ for path in (*changes, *additions, *comparisons):
1986
+ self._record_field_path(path)
1987
+ if expected_revision is not None and (
1988
+ type(expected_revision) is not int or expected_revision < 0):
1989
+ raise StorageIntegrityError("invalid expected record revision")
1990
+ for path, amount in additions.items():
1991
+ if (not path.startswith("stats.")
1992
+ or type(amount) is not int or amount < 0):
1993
+ raise StorageIntegrityError("record increments are nonnegative stats only")
1994
+ parsed = parse_managed_target(target)
1995
+ with self.lease():
1996
+ # Ownership validation precedes any incidental lock-file mkdir.
1997
+ # We repeat it after acquiring the stable per-full-UUID lock.
1998
+ self._owned(target)
1999
+ with self._record_lock(parsed.worker_id):
2000
+ # THIS fresh read under both locks, never a stale cached
2001
+ # record from before another CLI/host write.
2002
+ owner, current = self._owned(target)
2003
+ assert current is not None
2004
+ revision = current.get("recordRevision", 0)
2005
+ if type(revision) is not int or revision < 0:
2006
+ raise StorageIntegrityError("bad worker recordRevision")
2007
+ if expected_revision is not None and revision != expected_revision:
2008
+ raise RecordConflict("stale expected whole-record revision")
2009
+ for field, expected in comparisons.items():
2010
+ if self._at_field(current, self._record_field_path(field)) != expected:
2011
+ raise RecordConflict("record semantic field changed before CAS")
2012
+ # JSON roundtrip makes a detached copy and rejects objects not
2013
+ # serializable in the durable record (no arbitrary Python).
2014
+ candidate = json.loads(json.dumps(current, allow_nan=False))
2015
+ for field, value in changes.items():
2016
+ self._set_field(candidate, self._record_field_path(field), value)
2017
+ for field, delta in additions.items():
2018
+ path = self._record_field_path(field)
2019
+ previous = self._at_field(candidate, path)
2020
+ if type(previous) is not int:
2021
+ raise RecordConflict("stat increment requires existing integer counter")
2022
+ self._set_field(candidate, path, previous + delta)
2023
+ if removals:
2024
+ if owner.engine != "claude":
2025
+ raise StorageIntegrityError("host transient removal requires Claude owner")
2026
+ for field in removals:
2027
+ candidate.pop(field, None)
2028
+ if candidate == current and removals and not changes and not additions:
2029
+ # Deleting an already-absent host transient is idempotent.
2030
+ # Keep previous revision semantics for ordinary equal-value
2031
+ # patch/increment callers outside this small host slice.
2032
+ return current
2033
+ candidate["recordRevision"] = revision + 1
2034
+ # immutable worker identity/native fields always survive.
2035
+ for frozen in ("workerId", "engine", "native",
2036
+ "nativeCreate", "terminalOnly", "forkedFrom", "spawnedBy"):
2037
+ if candidate.get(frozen) != current.get(frozen):
2038
+ raise StorageIntegrityError("immutable native/owner field changed")
2039
+ self._write(("workers", owner.engine, owner.worker_id), "record.json", candidate)
2040
+ return candidate
2041
+
2042
+ def _native_guard(self, source: Mapping, action: str) -> None:
2043
+ if action not in _NATIVE_WRITES:
2044
+ raise NativeActionDenied("unknown native operation action")
2045
+ if action == "start":
2046
+ raise NativeActionDenied(
2047
+ "generic native thread/session start is forbidden;"
2048
+ " original create/activation proof must be reconciled separately"
2049
+ )
2050
+ native = source.get("native")
2051
+ if not isinstance(native, dict):
2052
+ raise NativeActionDenied("native binding not present; forensic read only")
2053
+ # New v2 workers may have a first record and *even a Claude native
2054
+ # sessionId* before a single native start was submitted. Neither
2055
+ # record existence nor a native UUID proves provider activation.
2056
+ # All native mutators must consult the durable original activation
2057
+ # receipt. Unknown/missing/temporary states never authorize send.
2058
+ if source.get("fingerprintVersion") != 2:
2059
+ raise NativeActionDenied("current full-UUID native binding requires v2")
2060
+ wid = canonical_worker_id(source.get("workerId"))
2061
+ owner, claim = self._owner(wid), self._claim(wid)
2062
+ activation = claim.get("nativeActivation") if isinstance(claim, dict) else None
2063
+ if (not isinstance(owner, dict) or not isinstance(claim, dict)
2064
+ or owner.get("status") != "worker"
2065
+ or owner.get("workerId") != wid
2066
+ or owner.get("engine") != source.get("engine")
2067
+ or owner.get("generation") != self.generation
2068
+ or claim.get("workerId") != wid
2069
+ or claim.get("engine") != source.get("engine")
2070
+ or claim.get("generation") != self.generation
2071
+ or claim.get("fingerprintVersion") != 2
2072
+ or not isinstance(activation, dict)
2073
+ or activation.get("state") != "confirmed"
2074
+ or not isinstance(activation.get("nativeEvidenceSha256"), str)
2075
+ or not _HASH.fullmatch(activation["nativeEvidenceSha256"])):
2076
+ raise NativeActionDenied("full-UUID native activation unconfirmed or unowned")
2077
+ if owner.get("provenance") == "ccx-convert":
2078
+ _converted_binding(owner, claim, source)
2079
+ elif owner.get("provenance") not in {"ccx-new", "ccx-import"}:
2080
+ raise NativeActionDenied("unsupported original native owner provenance")
2081
+ original_binding = activation.get("nativeBinding")
2082
+ if not isinstance(original_binding, dict):
2083
+ raise NativeActionDenied("confirmed claim has no original provider binding")
2084
+ if source["engine"] == "claude":
2085
+ if (native.get("sessionId") != original_binding.get("sessionId")
2086
+ or native.get("nativeState") != "session-bound"
2087
+ or activation.get("source")
2088
+ != f"claude-session:{native.get('sessionId')}"):
2089
+ raise NativeActionDenied("Claude provider identity is not bound to original receipt")
2090
+ else:
2091
+ if (native.get("threadId") != original_binding.get("threadId")
2092
+ or native.get("nativeState") != "thread-bound"
2093
+ or native.get("threadSource") != activation.get("source")
2094
+ or (owner.get("provenance") != "ccx-convert"
2095
+ and activation.get("source") != f"ccx-worker:{wid}")):
2096
+ raise NativeActionDenied("Codex provider thread is not bound to original receipt")
2097
+ if source.get("terminalOnly") or native.get("nativeState") in (
2098
+ "unbound-unknown", "submitted-or-unknown",
2099
+ ):
2100
+ raise NativeActionDenied("terminal-only or pending native binding: never start/resume/send")
2101
+ if source.get("engine") == "codex" and not native.get("threadId"):
2102
+ raise NativeActionDenied("Codex has no proven native thread binding")
2103
+ if source.get("engine") == "claude" and not native.get("sessionId"):
2104
+ raise NativeActionDenied("Claude has no proven native session binding")
2105
+ if (isinstance(source.get("nativeCreate"), dict)
2106
+ and source["nativeCreate"].get("state") in ("submitted", "unknown")):
2107
+ raise NativeActionDenied("native creation pending original receipt/source reconciliation")
2108
+
2109
+ def codex_source(self, target: str) -> str:
2110
+ """Privately prove original bound Codex source, never infer it from UUID."""
2111
+ with self.lease():
2112
+ owner, record = self._owned(target)
2113
+ if owner.engine != "codex" or record is None:
2114
+ raise NativeActionDenied("Codex full-UUID owner required")
2115
+ self._native_guard(record, "send")
2116
+ return self._claim(owner.worker_id)["nativeActivation"]["source"]
2117
+
2118
+ def begin_native_intent(self, target: str, *, action: str, family: str,
2119
+ operation_id: str, intent_sha256: str) -> OperationIntent:
2120
+ """Persist semantic operation *prepared* before any possible native call.
2121
+
2122
+ A sealed accepted/rejected receipt is replayed as a public terminal
2123
+ outcome, NOT dispatched again. An unknown submitted operation refuses.
2124
+ An unsubmitted prepared intent may be reclaimed by same spec: replacing
2125
+ its random permit invalidates the original caller under the receipt lock.
2126
+ Only dispatch_native_intent_once can reach the native transport.
2127
+ """
2128
+ if (family not in _FAMILIES or not isinstance(operation_id, str)
2129
+ or not operation_id or len(operation_id) > 512 or "\\x00" in operation_id):
2130
+ raise StorageIntegrityError("invalid semantic operation identity")
2131
+ if not isinstance(intent_sha256, str) or not _HASH.fullmatch(intent_sha256):
2132
+ raise StorageIntegrityError("invalid operation intent digest")
2133
+ with self.lease():
2134
+ owner, source = self._owned(target)
2135
+ assert source is not None
2136
+ key = hashlib.sha256(operation_id.encode("utf-8")).hexdigest()
2137
+ parts = ("receipts", family, owner.engine, owner.worker_id)
2138
+ # Already terminal receipts are read-only replay evidence even
2139
+ # when the worker has since become terminalOnly/unbound. No native
2140
+ # capability is required to show an accepted/rejected original
2141
+ # outcome, and no receipt directory is created on that path.
2142
+ try:
2143
+ with self._gen(*parts) as read_fd:
2144
+ prior_terminal = _read_json(read_fd, key + ".json")
2145
+ except FileNotFoundError:
2146
+ prior_terminal = None
2147
+ if prior_terminal is not None and prior_terminal.get("state") in ("accepted", "rejected"):
2148
+ if (prior_terminal.get("workerId") != owner.worker_id
2149
+ or prior_terminal.get("engine") != owner.engine
2150
+ or prior_terminal.get("family") != family
2151
+ or prior_terminal.get("action") != action
2152
+ or prior_terminal.get("operationId") != operation_id
2153
+ or prior_terminal.get("intentSha256") != intent_sha256
2154
+ or prior_terminal.get("generation") != self.generation):
2155
+ raise CreationConflict("terminal semantic receipt identity mismatch")
2156
+ return OperationIntent(
2157
+ owner.worker_id, owner.engine, family, operation_id,
2158
+ intent_sha256, prior_terminal["state"], self.generation,
2159
+ replayed=True,
2160
+ native_evidence_sha256=prior_terminal.get("nativeEvidenceSha256"),
2161
+ )
2162
+ self._native_guard(source, action)
2163
+ with self._gen(*parts, create=True) as fd:
2164
+ with _flock_at(fd, key + ".lock", exclusive=True, create=True):
2165
+ self.revalidate()
2166
+ original = _read_json(fd, key + ".json")
2167
+ if original is not None:
2168
+ if (original.get("workerId") != owner.worker_id
2169
+ or original.get("engine") != owner.engine
2170
+ or original.get("family") != family
2171
+ or original.get("action") != action
2172
+ or original.get("operationId") != operation_id
2173
+ or original.get("intentSha256") != intent_sha256
2174
+ or original.get("generation") != self.generation):
2175
+ raise CreationConflict("operation token already bound to different owner/intent")
2176
+ state = original.get("state")
2177
+ if state in ("accepted", "rejected"):
2178
+ return OperationIntent(
2179
+ owner.worker_id, owner.engine, family, operation_id,
2180
+ intent_sha256, state, self.generation,
2181
+ replayed=True,
2182
+ native_evidence_sha256=original.get("nativeEvidenceSha256"),
2183
+ )
2184
+ if state != "prepared":
2185
+ raise NativeEffectIndeterminate(
2186
+ "submitted native operation has unknown result; no replay"
2187
+ )
2188
+ # Prepared means all new-code native dispatches are
2189
+ # impossible without first durably recording submitted.
2190
+ permit = uuid4().hex
2191
+ pending = {
2192
+ "schemaVersion": _RECEIPT_SCHEMA,
2193
+ "workerId": owner.worker_id, "engine": owner.engine,
2194
+ "generation": self.generation, "family": family,
2195
+ "action": action, "operationId": operation_id,
2196
+ "intentSha256": intent_sha256, "state": "prepared",
2197
+ "dispatchPermitSha256": hashlib.sha256(
2198
+ permit.encode("ascii")).hexdigest(),
2199
+ "nativeEvidenceSha256": None,
2200
+ }
2201
+ _write_json(fd, key + ".json", pending)
2202
+ return OperationIntent(
2203
+ owner.worker_id, owner.engine, family, operation_id,
2204
+ intent_sha256, "prepared", self.generation,
2205
+ dispatch_token=permit,
2206
+ )
2207
+
2208
+ def dispatch_native_intent_once(self, prepared: OperationIntent, *,
2209
+ bounded_submit: Callable[[float], Mapping | None],
2210
+ dispatch_budget_s: float = 1.0,
2211
+ _failpoint: Callable[[str], None] | None = None) -> dict:
2212
+ """Same critical section rechecks ACTIVE + marks submitted before native IPC.
2213
+
2214
+ The provided transport MUST honor the supplied single-request timeout,
2215
+ cannot wait for a provider turn, and cannot run arbitrary user Python.
2216
+ No pointer can flip under a legitimate runner's exclusive lock during
2217
+ bounded send; a pointer changed outside that protocol still triggers
2218
+ pre-dispatch refusal. Unknown results leave a durable replay fence.
2219
+ """
2220
+ if not 0 < dispatch_budget_s <= 2:
2221
+ raise GenerationError("native receipt dispatch must have deadline <= 2 seconds")
2222
+ if prepared.replayed or prepared.state != "prepared" or not prepared.dispatch_token:
2223
+ raise NativeEffectIndeterminate("terminal or unknown receipt cannot dispatch natively")
2224
+ if prepared.generation != self.generation:
2225
+ raise StaleGeneration("receipt prepared under different generation")
2226
+ with self.lease(timeout=min(dispatch_budget_s, 2)):
2227
+ owner, source = self._owned(f"{prepared.engine}:{prepared.worker_id}")
2228
+ assert source is not None
2229
+ with self._gen("receipts", prepared.family, owner.engine,
2230
+ owner.worker_id) as fd:
2231
+ key = hashlib.sha256(prepared.operation_id.encode("utf-8")).hexdigest()
2232
+ with _flock_at(fd, key + ".lock", exclusive=True):
2233
+ self.revalidate()
2234
+ record = _read_json(fd, key + ".json")
2235
+ if record is None:
2236
+ raise StorageIntegrityError("missing original prepared receipt")
2237
+ self._native_guard(source, record.get("action", ""))
2238
+ if (record.get("workerId") != owner.worker_id
2239
+ or record.get("engine") != prepared.engine
2240
+ or record.get("family") != prepared.family
2241
+ or record.get("operationId") != prepared.operation_id
2242
+ or record.get("intentSha256") != prepared.intent_sha256
2243
+ or record.get("generation") != self.generation):
2244
+ raise StorageIntegrityError("native dispatch claim owner/intent mismatch")
2245
+ if record.get("state") != "prepared":
2246
+ raise NativeEffectIndeterminate("native dispatch already submitted or terminal")
2247
+ expected = hashlib.sha256(
2248
+ prepared.dispatch_token.encode("ascii")).hexdigest()
2249
+ if record.get("dispatchPermitSha256") != expected:
2250
+ raise NativeEffectIndeterminate("superseded receipt dispatch permit")
2251
+ if _failpoint:
2252
+ _failpoint("before-submitted")
2253
+ self.revalidate()
2254
+ unknown = dict(record)
2255
+ unknown["state"] = "submitted-unknown"
2256
+ unknown["dispatchPermitSha256"] = None
2257
+ _write_json(fd, key + ".json", unknown)
2258
+ if _failpoint:
2259
+ _failpoint("after-submitted")
2260
+ self.revalidate()
2261
+ try:
2262
+ answer = bounded_submit(dispatch_budget_s)
2263
+ except Exception as exc:
2264
+ raise NativeEffectIndeterminate(
2265
+ "native receipt response unknown; do not replay"
2266
+ ) from exc
2267
+ if not isinstance(answer, Mapping):
2268
+ raise NativeEffectIndeterminate(
2269
+ "native receipt missing exact acceptance evidence"
2270
+ )
2271
+ return dict(answer)
2272
+
2273
+ def settle_native_intent(self, attempt: OperationIntent, *, state: str,
2274
+ native_evidence_sha256: str | None) -> dict:
2275
+ """Only terminal with durable positive evidence; changed pointer refuses.
2276
+
2277
+ If the native acknowledgment was lost, leave submitted-unknown fenced
2278
+ and use a separately reviewed exact-native-history reconciliation.
2279
+ """
2280
+ if state not in ("accepted", "rejected", "unknown"):
2281
+ raise StorageIntegrityError("invalid native settlement")
2282
+ if state != "unknown" and (not isinstance(native_evidence_sha256, str)
2283
+ or not _HASH.fullmatch(native_evidence_sha256)):
2284
+ raise NativeEffectIndeterminate("cannot assert native terminal without exact evidence")
2285
+ if attempt.generation != self.generation:
2286
+ raise StaleGeneration("attempt from a different generation")
2287
+ with self.lease():
2288
+ owner, _source = self._owned(f"{attempt.engine}:{attempt.worker_id}")
2289
+ if owner.worker_id != attempt.worker_id:
2290
+ raise StorageIntegrityError("receipt owner changed")
2291
+ key = hashlib.sha256(attempt.operation_id.encode("utf-8")).hexdigest()
2292
+ with self._gen("receipts", attempt.family, attempt.engine, attempt.worker_id) as fd:
2293
+ with _flock_at(fd, key + ".lock", exclusive=True):
2294
+ prior = _read_json(fd, key + ".json")
2295
+ if not prior or (prior.get("operationId"), prior.get("intentSha256"),
2296
+ prior.get("workerId"), prior.get("generation")) != (
2297
+ attempt.operation_id, attempt.intent_sha256,
2298
+ attempt.worker_id, self.generation,
2299
+ ):
2300
+ raise StorageIntegrityError("missing/mismatched original receipt fence")
2301
+ if prior.get("state") in ("accepted", "rejected"):
2302
+ if (prior["state"], prior.get("nativeEvidenceSha256")) != (state, native_evidence_sha256):
2303
+ raise CreationConflict("terminal native proof is immutable")
2304
+ return prior
2305
+ if state == "unknown":
2306
+ return prior
2307
+ if prior.get("state") != "submitted-unknown":
2308
+ raise NativeEffectIndeterminate(
2309
+ "cannot assert accepted/rejected before durable native dispatch"
2310
+ )
2311
+ result = dict(prior)
2312
+ result.update(state=state, nativeEvidenceSha256=native_evidence_sha256)
2313
+ _write_json(fd, key + ".json", result)
2314
+ return result
2315
+
2316
+ def read_receipt(self, target: str, family: str, operation_id: str) -> dict | None:
2317
+ path = self.receipt_path(target, family, operation_id)
2318
+ with self.lease():
2319
+ owner, _ = self._owned(target)
2320
+ key = path.name
2321
+ try:
2322
+ with self._gen("receipts", family, owner.engine, owner.worker_id) as fd:
2323
+ value = _read_json(fd, key)
2324
+ except FileNotFoundError:
2325
+ return None
2326
+ if value is not None and (
2327
+ value.get("workerId") != owner.worker_id or value.get("engine") != owner.engine
2328
+ or value.get("operationId") != operation_id
2329
+ or value.get("generation") != self.generation
2330
+ ):
2331
+ raise StorageIntegrityError("semantic receipt identity mismatch")
2332
+ return value