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,348 @@
1
+ """Exact managed-turn identity and report reads shared by public adapters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from ccx.codex import records as codex_records
8
+ from ccx.codex.reports import native_timestamp_ms, report_from_turn
9
+ from ccx.errors import InvalidRequest, OperationConflict, SessionNotFound, SessionUnavailable
10
+ from ccx.claude.managed_turns import report_window_for_turn_ref, settlement_for_turn_ref, turn_settlements
11
+ from ccx.store.records import read_record as read_claude_record
12
+ from ccx.claude.transcript import exact_assistant_text
13
+
14
+ _CLAUDE_PREFIX = "ccx-turn:claude:v1:"
15
+ _CODEX_PREFIX = "ccx-turn:codex:v1:"
16
+
17
+
18
+ def _ref_id(turn_ref: str, *, engine: str) -> str:
19
+ prefix = _CLAUDE_PREFIX if engine == "claude" else _CODEX_PREFIX
20
+ if not isinstance(turn_ref, str) or not turn_ref.startswith(prefix):
21
+ raise InvalidRequest(
22
+ f"{engine.capitalize()} turn_ref must use {prefix}<native-turn-id>"
23
+ if engine == "codex"
24
+ else f"Claude turn_ref must use {prefix}<start-id>"
25
+ )
26
+ value = turn_ref[len(prefix):]
27
+ if not value:
28
+ raise InvalidRequest(f"turn_ref must contain an id after {prefix}")
29
+ return value
30
+
31
+
32
+ def codex_settled_turn(
33
+ value: dict[str, Any] | None,
34
+ *,
35
+ fallback_completed_at: int | None = None,
36
+ ) -> dict[str, Any] | None:
37
+ """Normalize one exact native Codex terminal projection."""
38
+ if not isinstance(value, dict):
39
+ return None
40
+ turn_id = value.get("turnId")
41
+ if not isinstance(turn_id, str) or not turn_id:
42
+ return None
43
+ completed_at = value.get("completedAtMs")
44
+ if not isinstance(completed_at, int) or isinstance(completed_at, bool):
45
+ completed_at = fallback_completed_at
46
+ return {
47
+ "turn_ref": f"{_CODEX_PREFIX}{turn_id}",
48
+ "turn_id": turn_id,
49
+ "outcome": str(value.get("status") or "unknown"),
50
+ "completed_at": completed_at,
51
+ "context": ({**value["context"], "as_of_turn_ref": f"{_CODEX_PREFIX}{turn_id}"}
52
+ if isinstance(value.get("context"), dict) else None),
53
+ }
54
+
55
+
56
+ def _claude_settlement(value: Any) -> dict[str, Any] | None:
57
+ """One usable ``{turnRef, outcome, settledAtMs}`` settlement, else None."""
58
+ if not isinstance(value, dict):
59
+ return None
60
+ turn_ref = value.get("turnRef")
61
+ settled_at = value.get("settledAtMs")
62
+ if not isinstance(turn_ref, str) or not turn_ref.startswith(_CLAUDE_PREFIX):
63
+ return None
64
+ if not isinstance(settled_at, int) or isinstance(settled_at, bool):
65
+ return None
66
+ return value
67
+
68
+
69
+ def _latest_claude_settlement(record: dict[str, Any], short: str) -> dict[str, Any] | None:
70
+ """Exact latest managed settlement, or None when it is unknown.
71
+
72
+ The host stores the latest summary in the record (``lastSettledTurn``)
73
+ together with ``lastSettledAt``. Records written before that field fall
74
+ back to the latest retained sidecar terminal. A retained terminal older
75
+ than the stored summary never replaces it, and one older than the record's
76
+ ``lastSettledAt`` is known stale (rotated out, or not yet broadcast), so
77
+ the latest settlement is unknown rather than that older ref.
78
+ """
79
+ stored = _claude_settlement(record.get("lastSettledTurn"))
80
+ values = turn_settlements(short) or []
81
+ retained = _claude_settlement(values[-1]) if values else None
82
+ latest = stored
83
+ if retained is not None and (
84
+ stored is None
85
+ or (
86
+ retained["turnRef"] != stored["turnRef"]
87
+ and retained["settledAtMs"] > stored["settledAtMs"]
88
+ )
89
+ ):
90
+ # Only a strictly newer, different terminal (e.g. broadcast by a host
91
+ # that predates the stored summary) supersedes the record.
92
+ latest = retained
93
+ if latest is None:
94
+ return None
95
+ last_settled_at = record.get("lastSettledAt")
96
+ if (
97
+ isinstance(last_settled_at, int)
98
+ and not isinstance(last_settled_at, bool)
99
+ and latest["settledAtMs"] < last_settled_at
100
+ ):
101
+ return None
102
+ return latest
103
+
104
+
105
+ def last_settled_turn(engine: str, short: str) -> dict[str, Any] | None:
106
+ """Normalized exact latest settled-turn identity for one owned worker.
107
+
108
+ ``None`` means unknown, never "no newer turn": a caller comparing
109
+ checkpoints must treat it as unavailable evidence.
110
+ """
111
+ if engine == "claude":
112
+ record = read_claude_record(short)
113
+ if record is None:
114
+ return None
115
+ value = _latest_claude_settlement(record, short)
116
+ if value is None:
117
+ return None
118
+ return {
119
+ "turn_ref": value["turnRef"],
120
+ "turn_id": None,
121
+ "outcome": str(value.get("outcome") or "unknown"),
122
+ "completed_at": value["settledAtMs"],
123
+ "context": ({**value["context"], "as_of_turn_ref": value["turnRef"]}
124
+ if isinstance(value.get("context"), dict) else None),
125
+ }
126
+
127
+ if engine == "codex":
128
+ record = codex_records.read_record(short)
129
+ if record is None:
130
+ return None
131
+ return codex_settled_turn(
132
+ record.get("lastTurn"),
133
+ fallback_completed_at=record.get("lastTurnAt"),
134
+ )
135
+
136
+ raise InvalidRequest(f"unknown engine: {engine!r}")
137
+
138
+
139
+ def codex_native_turn_evidence(
140
+ child: str, record: dict, thread_id: str, turn_id: str,
141
+ ) -> dict[str, Any] | None:
142
+ """Existing original-owned Codex journal terminal proof; no native read."""
143
+ from ccx.codex.evidence import build_index, read_journal
144
+
145
+ if (not turn_id or (record.get("activeCompact") or {}).get("turnId") == turn_id
146
+ or (record.get("lastCompact") or {}).get("turnId") == turn_id):
147
+ return None
148
+ journal = read_journal(child)
149
+ index = build_index(journal, health=record.get("evidence"))
150
+ turn = index["turns"].get(turn_id)
151
+ if (not index["complete"] or not turn or not turn["sawTurnStarted"]
152
+ or any(item.get("itemType") == "contextCompaction"
153
+ for item in index["items"].get(turn_id, {}).values())):
154
+ return None
155
+ events = [event for event in journal.events if event.get("turnId") == turn_id]
156
+ if not events or any(event.get("threadId") != thread_id for event in events):
157
+ return None
158
+ terminals = [(event.get("params") or {}).get("turn") for event in events
159
+ if event.get("method") == "turn/completed"]
160
+ if not terminals:
161
+ return {"status": None, "error": None}
162
+ if (any(not isinstance(turn, dict) or turn.get("id") != turn_id
163
+ or turn.get("status") not in {"completed", "failed", "interrupted", "cancelled"}
164
+ for turn in terminals)
165
+ or any(turn != terminals[0] for turn in terminals[1:])):
166
+ return None
167
+ return {"status": terminals[0]["status"], "error": terminals[0].get("error")}
168
+
169
+
170
+ def _codex_journal_contradicts(
171
+ child: str, record: dict, thread_id: str, turn_id: str,
172
+ ) -> bool:
173
+ """Does the local journal say anything against R being a completed turn?
174
+
175
+ Missing evidence (a gap elsewhere in the journal, or none for R) is not a
176
+ contradiction; another thread, a compaction or a non-completed terminal is.
177
+ """
178
+ from ccx.codex.evidence import build_index, read_journal
179
+
180
+ if ((record.get("activeCompact") or {}).get("turnId") == turn_id
181
+ or (record.get("lastCompact") or {}).get("turnId") == turn_id):
182
+ return True
183
+ journal = read_journal(child)
184
+ index = build_index(journal, health=record.get("evidence"))
185
+ if any(item.get("itemType") == "contextCompaction"
186
+ for item in index["items"].get(turn_id, {}).values()):
187
+ return True
188
+ events = [event for event in journal.events if event.get("turnId") == turn_id]
189
+ if any(event.get("threadId") != thread_id for event in events):
190
+ return True
191
+ return any(
192
+ not isinstance(turn, dict) or turn.get("id") != turn_id
193
+ or turn.get("status") != "completed"
194
+ for turn in ((event.get("params") or {}).get("turn") for event in events
195
+ if event.get("method") == "turn/completed"))
196
+
197
+
198
+ def codex_source_report(
199
+ thread: dict, worker_id: str, thread_id: str, turn_id: str,
200
+ *, expected_source: str,
201
+ ) -> dict[str, Any]:
202
+ """Select only the completed original native R from caller-fetched history."""
203
+ if (not isinstance(thread, dict) or thread.get("id") != thread_id
204
+ or thread.get("threadSource") != expected_source):
205
+ raise SessionUnavailable("Codex conversation belongs to another worker; check the worker")
206
+ turns = [turn for turn in (thread.get("turns") or [])
207
+ if isinstance(turn, dict) and turn.get("id") == turn_id]
208
+ if (len(turns) != 1 or turns[0].get("status") != "completed"
209
+ or any(isinstance(item, dict) and item.get("type") == "contextCompaction"
210
+ for item in turns[0].get("items") or [])):
211
+ raise SessionUnavailable("Completed Codex turn is unavailable; inspect worker status")
212
+ selected = report_from_turn(turns[0])
213
+ return {
214
+ "turn_ref": f"{_CODEX_PREFIX}{turn_id}", "turn_id": turn_id,
215
+ "turn": None, "item_id": selected.get("itemId") if selected else None,
216
+ "phase": selected.get("phase") if selected else None,
217
+ "text": str(selected["text"]) if selected else "",
218
+ "has_final_message": selected is not None,
219
+ "completed_at": (selected.get("completedAt") if selected
220
+ else native_timestamp_ms(turns[0].get("completedAt"))),
221
+ }
222
+
223
+
224
+ def claude_source_report(
225
+ store, child: str, worker_id: str, ref: str,
226
+ window: dict[str, Any],
227
+ ) -> dict[str, Any]:
228
+ """Pinned original-source Claude R window, including accepted EOF recheck.
229
+
230
+ Unlike notification presentation, a verified empty final is "" rather than
231
+ unavailable. All source/window failures are typed instead of coerced to None.
232
+ """
233
+ from ccx.claude.transcript import open_original_claude_source
234
+ from ccx.store.root import StorageIntegrityError
235
+
236
+ owner = store.resolve(child)
237
+ if owner.engine != "claude" or owner.worker_id != worker_id:
238
+ raise SessionUnavailable("Claude exact report owner/source mismatch")
239
+ record = store.record(child)
240
+ start, stop = window.get("startOffset"), window.get("endOffset")
241
+ if (window.get("turnRef") != ref or not window.get("endOffsetKnown")
242
+ or type(start) is not int or start < 0
243
+ or (stop is not None and (type(stop) is not int or stop < start))):
244
+ raise SessionUnavailable("Claude turn history is incomplete; inspect the worker")
245
+ try:
246
+ with open_original_claude_source(store, owner, record) as source:
247
+ if source is None:
248
+ raise SessionUnavailable("Claude history file is missing; check worker status")
249
+ pinned, _stat, eof, _original_path, _directory_fd = source
250
+ if start > eof or (stop is not None and stop > eof):
251
+ raise SessionUnavailable("Claude turn history changed during reading; retry")
252
+ text = exact_assistant_text(
253
+ pinned, after_offset=start,
254
+ before_offset=stop if stop is not None else eof,
255
+ )
256
+ if stop is None:
257
+ # Lazy to avoid turn_reports -> protocol_ops -> turn_reports cycle.
258
+ from ccx.ops.wait import wait_worker
259
+ later, available = report_window_for_turn_ref(worker_id, ref)
260
+ snapshot = wait_worker(
261
+ child, timeout=.3, expected_generation=store.generation,
262
+ )
263
+ if (not available or later != window
264
+ or (snapshot.get("last_settled_turn") or {}).get("turn_ref") != ref
265
+ or snapshot.get("active_turn_ref") not in {None, ref}):
266
+ raise SessionUnavailable(
267
+ "Claude original report EOF could include a successor turn"
268
+ )
269
+ store.revalidate()
270
+ except (StorageIntegrityError, OSError, ValueError) as exc:
271
+ raise SessionUnavailable("Claude conversation is unavailable; inspect worker status") from exc
272
+ return {
273
+ "turn_ref": ref, "turn_id": None, "turn": None,
274
+ "item_id": None, "phase": None, "text": text,
275
+ "has_final_message": bool(text), "completed_at": None,
276
+ }
277
+
278
+
279
+ def exact_report(
280
+ store, owner, turn_ref: str, *, thread: dict | None = None,
281
+ ) -> dict[str, Any]:
282
+ """Exact CCX-owned native R; caller owns Codex history read transport."""
283
+ turn_id = _ref_id(turn_ref, engine=owner.engine)
284
+ child = owner.worker
285
+ record = store.record(child)
286
+ native = record.get("native") or {}
287
+ activation = store.native_activation(child)
288
+ original = native.get("sessionId" if owner.engine == "claude" else "threadId")
289
+ source = (f"claude-session:{original}" if owner.engine == "claude"
290
+ else store.codex_source(owner.worker))
291
+ if (not isinstance(original, str) or not original
292
+ or activation.get("state") != "confirmed"
293
+ or activation.get("source") != source
294
+ or (activation.get("nativeBinding") or {}).get(
295
+ "sessionId" if owner.engine == "claude" else "threadId"
296
+ ) != original
297
+ or (owner.engine == "codex" and native.get("threadSource") != source)):
298
+ raise SessionUnavailable("Conversation source could not be verified; inspect the worker")
299
+ if owner.engine == "claude":
300
+ terminal, available = settlement_for_turn_ref(owner.worker_id, turn_ref)
301
+ window, window_available = report_window_for_turn_ref(owner.worker_id, turn_ref)
302
+ if not available or not window_available:
303
+ raise SessionUnavailable("Claude turn history is unavailable; inspect the worker")
304
+ if terminal is None:
305
+ if window is not None:
306
+ raise OperationConflict("Claude turn is still running; wait for it to finish")
307
+ raise SessionNotFound("Claude turn is no longer saved; inspect recent history")
308
+ if (terminal.get("eventType") != "turn_complete"
309
+ or terminal.get("outcome") != "completed"):
310
+ raise SessionUnavailable("Claude turn is not complete; check worker status")
311
+ if window is None or window.get("turnRef") != turn_ref:
312
+ raise SessionUnavailable("Claude turn history is unavailable; retry later")
313
+ result = claude_source_report(
314
+ store, child, owner.worker_id, turn_ref, window,
315
+ )
316
+ result["turn"] = terminal.get("turn")
317
+ result["completed_at"] = terminal.get("settledAtMs")
318
+ elif owner.engine == "codex":
319
+ proof = codex_native_turn_evidence(child, record, original, turn_id)
320
+ if proof is None or proof["status"] is None:
321
+ # The local journal cannot prove R: a gap elsewhere in it, or R's
322
+ # completion was missed. Unless it contradicts R, the exact
323
+ # source-verified native history decides: codex_source_report
324
+ # needs R itself completed there, and says so when it is not.
325
+ started = proof is not None
326
+ native_done = isinstance(thread, dict) and any(
327
+ isinstance(turn, dict) and turn.get("id") == turn_id
328
+ and turn.get("status") == "completed"
329
+ for turn in thread.get("turns") or [])
330
+ if (thread is None or (started and not native_done)
331
+ or _codex_journal_contradicts(child, record, original, turn_id)):
332
+ if started:
333
+ raise OperationConflict("Codex turn is not complete; wait before reporting")
334
+ raise SessionUnavailable("Codex completion record is missing; check status")
335
+ proof = {"status": "completed"}
336
+ if proof["status"] != "completed":
337
+ raise SessionUnavailable("Codex turn ended without completion; inspect worker status")
338
+ if thread is None:
339
+ raise SessionUnavailable("Codex history is unavailable; retry this report")
340
+ result = codex_source_report(
341
+ thread, owner.worker_id, original, turn_id, expected_source=source,
342
+ )
343
+ else:
344
+ raise InvalidRequest(f"unsupported engine {owner.engine!r}")
345
+ store.revalidate()
346
+ return result
347
+
348
+ __all__ = ["codex_settled_turn", "exact_report", "last_settled_turn"]