ccx-native 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +241 -2
- package/bin/ccx +56 -0
- package/ccx/__init__.py +163 -0
- package/ccx/__main__.py +28 -0
- package/ccx/claude/__init__.py +0 -0
- package/ccx/claude/analytics.py +466 -0
- package/ccx/claude/attach.py +762 -0
- package/ccx/claude/context.py +465 -0
- package/ccx/claude/delivery.py +806 -0
- package/ccx/claude/external.py +253 -0
- package/ccx/claude/host/__init__.py +0 -0
- package/ccx/claude/host/events.py +297 -0
- package/ccx/claude/host/hooks.py +438 -0
- package/ccx/claude/host/pty.py +250 -0
- package/ccx/claude/host/session.py +5184 -0
- package/ccx/claude/host/subagents.py +169 -0
- package/ccx/claude/host/successor.py +267 -0
- package/ccx/claude/managed_turns.py +240 -0
- package/ccx/claude/oauth.py +176 -0
- package/ccx/claude/settings.py +339 -0
- package/ccx/claude/spawn.py +557 -0
- package/ccx/claude/tasks.py +72 -0
- package/ccx/claude/telemetry.py +282 -0
- package/ccx/claude/transcript.py +2281 -0
- package/ccx/claude/usage.py +837 -0
- package/ccx/claude/usage_control.py +152 -0
- package/ccx/cli/__init__.py +0 -0
- package/ccx/cli/codex.py +1009 -0
- package/ccx/cli/commands/__init__.py +0 -0
- package/ccx/cli/commands/_common.py +407 -0
- package/ccx/cli/commands/control.py +161 -0
- package/ccx/cli/commands/create.py +669 -0
- package/ccx/cli/commands/read.py +1020 -0
- package/ccx/cli/commands/turns.py +1081 -0
- package/ccx/cli/controller.py +757 -0
- package/ccx/cli/main.py +301 -0
- package/ccx/cli/parser.py +1324 -0
- package/ccx/cli/skills.py +215 -0
- package/ccx/cli/statusline.py +160 -0
- package/ccx/client.py +1900 -0
- package/ccx/codex/__init__.py +0 -0
- package/ccx/codex/attach_tty.py +267 -0
- package/ccx/codex/client.py +732 -0
- package/ccx/codex/completion.py +121 -0
- package/ccx/codex/control.py +288 -0
- package/ccx/codex/daemon/__init__.py +0 -0
- package/ccx/codex/daemon/__main__.py +4 -0
- package/ccx/codex/daemon/daemon.py +1556 -0
- package/ccx/codex/daemon/models.py +140 -0
- package/ccx/codex/daemon/native.py +1590 -0
- package/ccx/codex/daemon/release.py +367 -0
- package/ccx/codex/daemon/worker.py +2396 -0
- package/ccx/codex/delivery.py +758 -0
- package/ccx/codex/delivery_recovery.py +232 -0
- package/ccx/codex/evidence.py +827 -0
- package/ccx/codex/history.py +158 -0
- package/ccx/codex/notify_history.py +140 -0
- package/ccx/codex/ops.py +666 -0
- package/ccx/codex/paths.py +70 -0
- package/ccx/codex/protocol.py +805 -0
- package/ccx/codex/provenance.py +105 -0
- package/ccx/codex/read.py +29 -0
- package/ccx/codex/records.py +51 -0
- package/ccx/codex/reports.py +177 -0
- package/ccx/codex/runtime.py +1412 -0
- package/ccx/codex/settle.py +363 -0
- package/ccx/codex/telemetry.py +792 -0
- package/ccx/codex/types.py +659 -0
- package/ccx/codex/worker_environment.py +25 -0
- package/ccx/constants.py +288 -0
- package/ccx/controllers/__init__.py +0 -0
- package/ccx/controllers/controller.py +1449 -0
- package/ccx/controllers/live_vars.py +1706 -0
- package/ccx/controllers/live_vars_runner.py +216 -0
- package/ccx/controllers/notify.py +213 -0
- package/ccx/controllers/orchestrator.py +193 -0
- package/ccx/controllers/policy.py +57 -0
- package/ccx/controllers/store.py +681 -0
- package/ccx/errors.py +634 -0
- package/ccx/notify/__init__.py +0 -0
- package/ccx/notify/__main__.py +5 -0
- package/ccx/notify/client.py +116 -0
- package/ccx/notify/daemon.py +480 -0
- package/ccx/notify/deliver.py +304 -0
- package/ccx/notify/paths.py +51 -0
- package/ccx/notify/registry.py +649 -0
- package/ccx/notify/select.py +750 -0
- package/ccx/notify/settle.py +684 -0
- package/ccx/ops/__init__.py +0 -0
- package/ccx/ops/background.py +115 -0
- package/ccx/ops/cancel.py +289 -0
- package/ccx/ops/codex_import.py +96 -0
- package/ccx/ops/common.py +397 -0
- package/ccx/ops/compact.py +337 -0
- package/ccx/ops/compact_ops.py +550 -0
- package/ccx/ops/context_events.py +48 -0
- package/ccx/ops/conversation.py +1540 -0
- package/ccx/ops/conversation_paging.py +1338 -0
- package/ccx/ops/create.py +56 -0
- package/ccx/ops/creation.py +952 -0
- package/ccx/ops/creation_steps.py +579 -0
- package/ccx/ops/event_feed.py +710 -0
- package/ccx/ops/forking.py +711 -0
- package/ccx/ops/input.py +556 -0
- package/ccx/ops/maintenance.py +295 -0
- package/ccx/ops/report.py +770 -0
- package/ccx/ops/semantics.py +490 -0
- package/ccx/ops/send.py +961 -0
- package/ccx/ops/steer_ops.py +867 -0
- package/ccx/ops/terminate.py +383 -0
- package/ccx/ops/turn_reports.py +306 -0
- package/ccx/ops/wait.py +762 -0
- package/ccx/ops/worker_events.py +636 -0
- package/ccx/rpc/__init__.py +0 -0
- package/ccx/rpc/local_rpc.py +1602 -0
- package/ccx/rpc/rpc.py +559 -0
- package/ccx/rpc/serve.py +374 -0
- package/ccx/rpc/transport.py +409 -0
- package/ccx/rpc/view.py +125 -0
- package/ccx/store/__init__.py +0 -0
- package/ccx/store/discovery.py +170 -0
- package/ccx/store/identity.py +105 -0
- package/ccx/store/journal_io.py +79 -0
- package/ccx/store/liveness.py +194 -0
- package/ccx/store/paths.py +254 -0
- package/ccx/store/record_cache.py +40 -0
- package/ccx/store/records.py +309 -0
- package/ccx/store/root.py +804 -0
- package/ccx/store/runtime_presence.py +137 -0
- package/ccx/store/store.py +2332 -0
- package/ccx/store/worker_state.py +86 -0
- package/ccx/system/__init__.py +0 -0
- package/ccx/system/bounded.py +296 -0
- package/ccx/system/cwd_paths.py +25 -0
- package/ccx/system/engine_binary.py +59 -0
- package/ccx/system/executable.py +50 -0
- package/ccx/system/oslayer.py +667 -0
- package/ccx/system/pipes.py +46 -0
- package/ccx/system/sanitize.py +43 -0
- package/ccx/types.py +705 -0
- package/ccx/version.py +29 -0
- package/ccx/workers.py +2081 -0
- package/ccx.py +9 -0
- package/package.json +40 -4
- package/pyproject.toml +87 -0
- package/skills/ccx/SKILL.md +155 -0
- package/skills/ccx/references/controllers.md +244 -0
- package/skills/ccx/references/troubleshooting.md +94 -0
- package/skills/ccx/references/variables.md +62 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"""Durable caller-known delivery receipts for Codex turn starts.
|
|
2
|
+
|
|
3
|
+
A receipt is persisted before a turn can be submitted. The daemon moves the
|
|
4
|
+
receipt to submitted immediately before the irreversible native turn/start
|
|
5
|
+
call, and to accepted only after a native turn id is known. A receipt observed
|
|
6
|
+
in submitted is never re-submitted.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import contextlib
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
import time
|
|
16
|
+
import uuid
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any, Iterator
|
|
19
|
+
|
|
20
|
+
from ccx.codex.provenance import new_client_message_id
|
|
21
|
+
|
|
22
|
+
SCHEMA_VERSION = 2
|
|
23
|
+
MAX_DELIVERY_ID = 128
|
|
24
|
+
STATES = frozenset({"prepared", "owned", "submitted", "accepted", "rejected"})
|
|
25
|
+
TERMINAL_STATES = frozenset({"accepted", "rejected"})
|
|
26
|
+
MODES = frozenset({"new_turn_only", "auto", "steer_only"})
|
|
27
|
+
ROUTES = frozenset({"new_turn", "steer"})
|
|
28
|
+
PLACEMENTS = frozenset({"new_turn", "current_turn"})
|
|
29
|
+
_CALLER_METADATA_UNSET = object()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DeliveryReceiptError(RuntimeError):
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class DeliveryReceiptConflict(DeliveryReceiptError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def now_ms() -> int:
|
|
41
|
+
return int(time.time() * 1000)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def new_delivery_id() -> str:
|
|
45
|
+
return str(uuid.uuid4())
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def valid_delivery_id(value: object) -> bool:
|
|
49
|
+
return (
|
|
50
|
+
isinstance(value, str)
|
|
51
|
+
and bool(value)
|
|
52
|
+
and len(value) <= MAX_DELIVERY_ID
|
|
53
|
+
and "\x00" not in value
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _store():
|
|
58
|
+
from ccx.constants import CCX_ROOT
|
|
59
|
+
from ccx.store.root import require_ccx_active
|
|
60
|
+
return require_ccx_active(CCX_ROOT)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _owner(store, target: str):
|
|
64
|
+
owner = store.resolve(target)
|
|
65
|
+
if owner.engine != "codex":
|
|
66
|
+
from ccx.store.identity import EngineMismatch
|
|
67
|
+
raise EngineMismatch("Delivery requires a Codex worker handle")
|
|
68
|
+
return owner
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _key(delivery_id: str) -> str:
|
|
72
|
+
return hashlib.sha256(delivery_id.encode("utf-8")).hexdigest()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def receipt_path(target: str, delivery_id: str) -> Path:
|
|
76
|
+
"""Projection for diagnostics only; never opened by actual receipt I/O."""
|
|
77
|
+
store = _store()
|
|
78
|
+
owner = _owner(store, target)
|
|
79
|
+
return (store.pointer.generation_root / "semantic-deliveries"
|
|
80
|
+
/ "codex" / owner.worker_id / (_key(delivery_id) + ".json"))
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _prompt_identity(text: str) -> tuple[str, int]:
|
|
84
|
+
raw = text.encode("utf-8")
|
|
85
|
+
return hashlib.sha256(raw).hexdigest(), len(raw)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@contextlib.contextmanager
|
|
89
|
+
def receipt_lock(short: str, delivery_id: str) -> Iterator[None]:
|
|
90
|
+
"""Original Codex per-delivery lock on a stable full-UUID generation inode."""
|
|
91
|
+
from ccx.store.root import _flock_at
|
|
92
|
+
if not valid_delivery_id(delivery_id):
|
|
93
|
+
raise DeliveryReceiptError("invalid delivery id")
|
|
94
|
+
store = _store()
|
|
95
|
+
owner = _owner(store, short)
|
|
96
|
+
with store.lease():
|
|
97
|
+
with store._gen("semantic-deliveries", "codex",
|
|
98
|
+
owner.worker_id, create=True) as fd:
|
|
99
|
+
with _flock_at(fd, _key(delivery_id) + ".lock",
|
|
100
|
+
exclusive=True, create=True, timeout=.5):
|
|
101
|
+
yield
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _validate(value: Any, *, short: str, delivery_id: str) -> dict[str, Any]:
|
|
105
|
+
if not isinstance(value, dict):
|
|
106
|
+
raise DeliveryReceiptError("Codex send receipt has invalid shape")
|
|
107
|
+
if value.get("schemaVersion") != SCHEMA_VERSION:
|
|
108
|
+
raise DeliveryReceiptError("unsupported Codex send receipt version")
|
|
109
|
+
store = _store()
|
|
110
|
+
owner = _owner(store, short)
|
|
111
|
+
if (value.get("workerId") != owner.worker_id
|
|
112
|
+
or value.get("engine") != "codex"
|
|
113
|
+
or value.get("generation") != store.generation
|
|
114
|
+
or value.get("deliveryId") != delivery_id):
|
|
115
|
+
raise DeliveryReceiptError("Codex delivery belongs to another worker; check its ID")
|
|
116
|
+
if value.get("state") not in STATES:
|
|
117
|
+
raise DeliveryReceiptError("Codex send receipt invalid state")
|
|
118
|
+
return value
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def read_receipt(short: str, delivery_id: str) -> dict[str, Any] | None:
|
|
122
|
+
from ccx.store.root import _read_json
|
|
123
|
+
if not valid_delivery_id(delivery_id):
|
|
124
|
+
raise DeliveryReceiptError("invalid delivery id")
|
|
125
|
+
store = _store()
|
|
126
|
+
owner = _owner(store, short)
|
|
127
|
+
with store.lease():
|
|
128
|
+
try:
|
|
129
|
+
with store._gen("semantic-deliveries", "codex",
|
|
130
|
+
owner.worker_id) as fd:
|
|
131
|
+
value = _read_json(fd, _key(delivery_id) + ".json")
|
|
132
|
+
except FileNotFoundError:
|
|
133
|
+
value = None
|
|
134
|
+
return _validate(value, short=short, delivery_id=delivery_id) if value is not None else None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _write(short: str, delivery_id: str, value: dict[str, Any]) -> None:
|
|
138
|
+
from ccx.store.root import _write_json
|
|
139
|
+
store = _store()
|
|
140
|
+
owner = _owner(store, short)
|
|
141
|
+
if "workerShort" in value:
|
|
142
|
+
raise DeliveryReceiptError("legacy workerShort authority forbidden")
|
|
143
|
+
value["updatedAtMs"] = now_ms()
|
|
144
|
+
value["workerId"] = owner.worker_id
|
|
145
|
+
value["engine"] = "codex"
|
|
146
|
+
value["generation"] = store.generation
|
|
147
|
+
with store.lease():
|
|
148
|
+
with store._gen("semantic-deliveries", "codex",
|
|
149
|
+
owner.worker_id, create=True) as fd:
|
|
150
|
+
_write_json(fd, _key(delivery_id) + ".json", value)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def requested_mode(entry: dict[str, Any]) -> str:
|
|
154
|
+
"""Old receipts predate modes and are strict new-turn deliveries."""
|
|
155
|
+
value = entry.get("requestedMode")
|
|
156
|
+
return value if value in MODES else "new_turn_only"
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def intent_spec(entry: dict[str, Any]) -> dict[str, Any]:
|
|
160
|
+
"""Immutable pre-route native identity: auto/steer choose their client id later."""
|
|
161
|
+
mode = requested_mode(entry)
|
|
162
|
+
return {
|
|
163
|
+
"deliveryId": entry["deliveryId"],
|
|
164
|
+
"promptSha256": entry["promptSha256"],
|
|
165
|
+
"promptByteCount": entry["promptByteCount"],
|
|
166
|
+
"requestedMode": mode,
|
|
167
|
+
"callerMetadata": entry.get("callerMetadata"),
|
|
168
|
+
"cap": entry.get("cap"),
|
|
169
|
+
"clientUserMessageId": (
|
|
170
|
+
entry["clientUserMessageId"] if mode == "new_turn_only" else None
|
|
171
|
+
),
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def route(entry: dict[str, Any]) -> str | None:
|
|
176
|
+
value = entry.get("route")
|
|
177
|
+
if value in ROUTES:
|
|
178
|
+
return value
|
|
179
|
+
# A submitted/accepted old receipt could only have opened a new turn.
|
|
180
|
+
if entry.get("state") in {"submitted", "accepted"}:
|
|
181
|
+
return "new_turn"
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def placement(entry: dict[str, Any]) -> str | None:
|
|
186
|
+
value = entry.get("placement")
|
|
187
|
+
if value in PLACEMENTS:
|
|
188
|
+
return value
|
|
189
|
+
if entry.get("state") == "accepted":
|
|
190
|
+
return "current_turn" if route(entry) == "steer" else "new_turn"
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def turn_ref_for(turn_id: str | None) -> str | None:
|
|
195
|
+
if not isinstance(turn_id, str) or not turn_id:
|
|
196
|
+
return None
|
|
197
|
+
return f"ccx-turn:codex:v1:{turn_id}"
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _assert_semantics(
|
|
201
|
+
entry: dict[str, Any],
|
|
202
|
+
text: str,
|
|
203
|
+
mode: str,
|
|
204
|
+
*,
|
|
205
|
+
caller_metadata: Any = _CALLER_METADATA_UNSET,
|
|
206
|
+
cap: dict | None = None,
|
|
207
|
+
) -> None:
|
|
208
|
+
digest, byte_count = _prompt_identity(text)
|
|
209
|
+
metadata_conflict = (
|
|
210
|
+
caller_metadata is not _CALLER_METADATA_UNSET
|
|
211
|
+
and entry.get("callerMetadata") != caller_metadata
|
|
212
|
+
)
|
|
213
|
+
if (
|
|
214
|
+
entry.get("promptSha256") != digest
|
|
215
|
+
or entry.get("promptByteCount") != byte_count
|
|
216
|
+
or requested_mode(entry) != mode
|
|
217
|
+
or metadata_conflict
|
|
218
|
+
or entry.get("cap") != cap
|
|
219
|
+
):
|
|
220
|
+
raise DeliveryReceiptConflict(
|
|
221
|
+
"delivery_id is already owned by different prompt content, mode, "
|
|
222
|
+
"caller metadata, or cap"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def prepare(
|
|
227
|
+
short: str,
|
|
228
|
+
delivery_id: str,
|
|
229
|
+
text: str,
|
|
230
|
+
*,
|
|
231
|
+
mode: str = "new_turn_only",
|
|
232
|
+
caller_metadata: Any = _CALLER_METADATA_UNSET,
|
|
233
|
+
cap: dict | None = None,
|
|
234
|
+
) -> tuple[dict[str, Any], bool]:
|
|
235
|
+
if not valid_delivery_id(delivery_id):
|
|
236
|
+
raise DeliveryReceiptError(
|
|
237
|
+
"delivery_id must be a non-empty string of at most 128 characters"
|
|
238
|
+
)
|
|
239
|
+
if not isinstance(text, str) or not text.strip():
|
|
240
|
+
raise DeliveryReceiptError("prompt text is required")
|
|
241
|
+
if mode not in MODES:
|
|
242
|
+
raise DeliveryReceiptError(f"unsupported delivery mode: {mode!r}")
|
|
243
|
+
with receipt_lock(short, delivery_id):
|
|
244
|
+
prior = read_receipt(short, delivery_id)
|
|
245
|
+
if prior is not None:
|
|
246
|
+
_assert_semantics(
|
|
247
|
+
prior,
|
|
248
|
+
text,
|
|
249
|
+
mode,
|
|
250
|
+
caller_metadata=caller_metadata,
|
|
251
|
+
cap=cap,
|
|
252
|
+
)
|
|
253
|
+
return prior, False
|
|
254
|
+
digest, byte_count = _prompt_identity(text)
|
|
255
|
+
created = now_ms()
|
|
256
|
+
value = {
|
|
257
|
+
"schemaVersion": SCHEMA_VERSION,
|
|
258
|
+
"workerId": _owner(_store(), short).worker_id,
|
|
259
|
+
"engine": "codex",
|
|
260
|
+
"generation": _store().generation,
|
|
261
|
+
"deliveryId": delivery_id,
|
|
262
|
+
"promptSha256": digest,
|
|
263
|
+
"promptByteCount": byte_count,
|
|
264
|
+
"requestedMode": mode,
|
|
265
|
+
"cap": cap,
|
|
266
|
+
"callerMetadata": (
|
|
267
|
+
None
|
|
268
|
+
if caller_metadata is _CALLER_METADATA_UNSET
|
|
269
|
+
else caller_metadata
|
|
270
|
+
),
|
|
271
|
+
# Preserve the landed strict-send contract: new_turn_only already
|
|
272
|
+
# knows its only possible provenance kind at prepare time. Auto must
|
|
273
|
+
# defer until codexd freezes start versus steer.
|
|
274
|
+
"clientUserMessageId": (
|
|
275
|
+
new_client_message_id(_owner(_store(), short).worker_id, "start")
|
|
276
|
+
if mode == "new_turn_only"
|
|
277
|
+
else None
|
|
278
|
+
),
|
|
279
|
+
"state": "prepared",
|
|
280
|
+
"route": None,
|
|
281
|
+
"placement": None,
|
|
282
|
+
"targetTurnId": None,
|
|
283
|
+
"turnRef": None,
|
|
284
|
+
"ownedAtMs": None,
|
|
285
|
+
"createdAtMs": created,
|
|
286
|
+
"updatedAtMs": created,
|
|
287
|
+
"submittedAtMs": None,
|
|
288
|
+
"acceptedAtMs": None,
|
|
289
|
+
"rejectedAtMs": None,
|
|
290
|
+
"turnId": None,
|
|
291
|
+
"rejectionCode": None,
|
|
292
|
+
"rejectionMessage": None,
|
|
293
|
+
"lastRefusalCode": None,
|
|
294
|
+
"lastRefusalMessage": None,
|
|
295
|
+
"lastRefusalState": None,
|
|
296
|
+
"lastRefusalAtMs": None,
|
|
297
|
+
}
|
|
298
|
+
_write(short, delivery_id, value)
|
|
299
|
+
return dict(value), True
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def require_matching(
|
|
303
|
+
short: str,
|
|
304
|
+
delivery_id: str,
|
|
305
|
+
text: str,
|
|
306
|
+
*,
|
|
307
|
+
mode: str = "new_turn_only",
|
|
308
|
+
caller_metadata: Any = _CALLER_METADATA_UNSET,
|
|
309
|
+
cap: dict | None = None,
|
|
310
|
+
) -> dict[str, Any]:
|
|
311
|
+
value = read_receipt(short, delivery_id)
|
|
312
|
+
if value is None:
|
|
313
|
+
raise DeliveryReceiptError(
|
|
314
|
+
"Codex send receipt is missing; prepare the delivery before submission"
|
|
315
|
+
)
|
|
316
|
+
_assert_semantics(
|
|
317
|
+
value,
|
|
318
|
+
text,
|
|
319
|
+
mode,
|
|
320
|
+
caller_metadata=caller_metadata,
|
|
321
|
+
cap=cap,
|
|
322
|
+
)
|
|
323
|
+
return value
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _transition(
|
|
327
|
+
short: str,
|
|
328
|
+
delivery_id: str,
|
|
329
|
+
allowed: set[str],
|
|
330
|
+
state: str,
|
|
331
|
+
**patch: Any,
|
|
332
|
+
) -> dict[str, Any]:
|
|
333
|
+
with receipt_lock(short, delivery_id):
|
|
334
|
+
value = read_receipt(short, delivery_id)
|
|
335
|
+
if value is None:
|
|
336
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
337
|
+
current = str(value.get("state"))
|
|
338
|
+
if current == state:
|
|
339
|
+
return value
|
|
340
|
+
if current not in allowed:
|
|
341
|
+
raise DeliveryReceiptError(
|
|
342
|
+
f"invalid Codex delivery transition {current!r} -> {state!r}"
|
|
343
|
+
)
|
|
344
|
+
updated = {**value, **patch, "state": state}
|
|
345
|
+
_write(short, delivery_id, updated)
|
|
346
|
+
return updated
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def mark_owned(
|
|
350
|
+
short: str,
|
|
351
|
+
delivery_id: str,
|
|
352
|
+
*,
|
|
353
|
+
route_kind: str,
|
|
354
|
+
target_turn_id: str | None = None,
|
|
355
|
+
) -> dict[str, Any]:
|
|
356
|
+
if route_kind not in ROUTES:
|
|
357
|
+
raise DeliveryReceiptError(f"invalid delivery route: {route_kind!r}")
|
|
358
|
+
if route_kind == "steer" and not target_turn_id:
|
|
359
|
+
raise DeliveryReceiptError("steer route requires target turn id")
|
|
360
|
+
with receipt_lock(short, delivery_id):
|
|
361
|
+
value = read_receipt(short, delivery_id)
|
|
362
|
+
if value is None:
|
|
363
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
364
|
+
current = value.get("state")
|
|
365
|
+
if current in {"owned", "submitted", "accepted"}:
|
|
366
|
+
if route(value) != route_kind:
|
|
367
|
+
raise DeliveryReceiptConflict("owned delivery route mismatch")
|
|
368
|
+
if route_kind == "steer" and value.get("targetTurnId") != target_turn_id:
|
|
369
|
+
raise DeliveryReceiptConflict("owned delivery target-turn mismatch")
|
|
370
|
+
return value
|
|
371
|
+
if current != "prepared":
|
|
372
|
+
return value
|
|
373
|
+
existing_client = value.get("clientUserMessageId")
|
|
374
|
+
client_id = (
|
|
375
|
+
existing_client
|
|
376
|
+
if isinstance(existing_client, str) and existing_client
|
|
377
|
+
else new_client_message_id(
|
|
378
|
+
_owner(_store(), short).worker_id,
|
|
379
|
+
"steer" if route_kind == "steer" else "start"
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
updated = {
|
|
383
|
+
**value,
|
|
384
|
+
"state": "owned",
|
|
385
|
+
"route": route_kind,
|
|
386
|
+
"targetTurnId": target_turn_id,
|
|
387
|
+
"clientUserMessageId": client_id,
|
|
388
|
+
"ownedAtMs": now_ms(),
|
|
389
|
+
"lastRefusalCode": None,
|
|
390
|
+
"lastRefusalMessage": None,
|
|
391
|
+
"lastRefusalState": None,
|
|
392
|
+
"lastRefusalAtMs": None,
|
|
393
|
+
}
|
|
394
|
+
_write(short, delivery_id, updated)
|
|
395
|
+
return updated
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def rollback_retryable(
|
|
399
|
+
short: str,
|
|
400
|
+
delivery_id: str,
|
|
401
|
+
*,
|
|
402
|
+
code: str,
|
|
403
|
+
message: str,
|
|
404
|
+
state: str | None = None,
|
|
405
|
+
) -> dict[str, Any]:
|
|
406
|
+
"""Return an explicitly non-mutated owned/submitted delivery to prepared."""
|
|
407
|
+
with receipt_lock(short, delivery_id):
|
|
408
|
+
value = read_receipt(short, delivery_id)
|
|
409
|
+
if value is None:
|
|
410
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
411
|
+
if value.get("state") not in {"owned", "submitted"}:
|
|
412
|
+
return value
|
|
413
|
+
updated = _prepared_again(value, code=code, message=message, state=state)
|
|
414
|
+
_write(short, delivery_id, updated)
|
|
415
|
+
return updated
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _prepared_again(
|
|
419
|
+
value: dict[str, Any], *, code: str, message: str, state: str | None
|
|
420
|
+
) -> dict[str, Any]:
|
|
421
|
+
return {
|
|
422
|
+
**value,
|
|
423
|
+
"state": "prepared",
|
|
424
|
+
"route": None,
|
|
425
|
+
"placement": None,
|
|
426
|
+
"targetTurnId": None,
|
|
427
|
+
"turnRef": None,
|
|
428
|
+
"clientUserMessageId": None,
|
|
429
|
+
"ownedAtMs": None,
|
|
430
|
+
"submittedAtMs": None,
|
|
431
|
+
"submittedRuntimeId": None,
|
|
432
|
+
"lastRefusalCode": code,
|
|
433
|
+
"lastRefusalMessage": message,
|
|
434
|
+
"lastRefusalState": state,
|
|
435
|
+
"lastRefusalAtMs": now_ms(),
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def mark_submitted(
|
|
440
|
+
short: str, delivery_id: str, *, runtime_id: str | None = None
|
|
441
|
+
) -> dict[str, Any]:
|
|
442
|
+
"""Freeze the delivery immediately before its native request is written.
|
|
443
|
+
|
|
444
|
+
``runtime_id`` names the codexd lifetime (and so the one app-server
|
|
445
|
+
connection) that may still complete the request. It is diagnostic
|
|
446
|
+
provenance only: a vanished runtime never proves the request did not land.
|
|
447
|
+
"""
|
|
448
|
+
with receipt_lock(short, delivery_id):
|
|
449
|
+
value = read_receipt(short, delivery_id)
|
|
450
|
+
if value is None:
|
|
451
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
452
|
+
current = value.get("state")
|
|
453
|
+
if current == "submitted":
|
|
454
|
+
return value
|
|
455
|
+
if current == "prepared":
|
|
456
|
+
if requested_mode(value) != "new_turn_only":
|
|
457
|
+
raise DeliveryReceiptError(
|
|
458
|
+
"Message has no destination; wait for a worker turn"
|
|
459
|
+
)
|
|
460
|
+
client_id = value.get("clientUserMessageId")
|
|
461
|
+
if not isinstance(client_id, str) or not client_id:
|
|
462
|
+
client_id = new_client_message_id(_owner(_store(), short).worker_id, "start")
|
|
463
|
+
value = {
|
|
464
|
+
**value,
|
|
465
|
+
"state": "owned",
|
|
466
|
+
"route": "new_turn",
|
|
467
|
+
"targetTurnId": None,
|
|
468
|
+
"clientUserMessageId": client_id,
|
|
469
|
+
"ownedAtMs": now_ms(),
|
|
470
|
+
}
|
|
471
|
+
if value.get("state") != "owned":
|
|
472
|
+
raise DeliveryReceiptError(
|
|
473
|
+
f"invalid Codex delivery transition {value.get('state')!r} -> 'submitted'"
|
|
474
|
+
)
|
|
475
|
+
updated = {
|
|
476
|
+
**value,
|
|
477
|
+
"state": "submitted",
|
|
478
|
+
"submittedAtMs": now_ms(),
|
|
479
|
+
"submittedRuntimeId": runtime_id,
|
|
480
|
+
"lastRefusalCode": None,
|
|
481
|
+
"lastRefusalMessage": None,
|
|
482
|
+
"lastRefusalState": None,
|
|
483
|
+
"lastRefusalAtMs": None,
|
|
484
|
+
}
|
|
485
|
+
_write(short, delivery_id, updated)
|
|
486
|
+
return updated
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def mark_retryable_refusal(
|
|
490
|
+
short: str,
|
|
491
|
+
delivery_id: str,
|
|
492
|
+
*,
|
|
493
|
+
code: str,
|
|
494
|
+
message: str,
|
|
495
|
+
state: str | None = None,
|
|
496
|
+
) -> dict[str, Any]:
|
|
497
|
+
"""Persist one definitive pre-submission refusal without burning the id."""
|
|
498
|
+
with receipt_lock(short, delivery_id):
|
|
499
|
+
value = read_receipt(short, delivery_id)
|
|
500
|
+
if value is None:
|
|
501
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
502
|
+
if value.get("state") != "prepared":
|
|
503
|
+
return value
|
|
504
|
+
updated = {
|
|
505
|
+
**value,
|
|
506
|
+
"lastRefusalCode": code,
|
|
507
|
+
"lastRefusalMessage": message,
|
|
508
|
+
"lastRefusalState": state,
|
|
509
|
+
"lastRefusalAtMs": now_ms(),
|
|
510
|
+
}
|
|
511
|
+
_write(short, delivery_id, updated)
|
|
512
|
+
return updated
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def mark_accepted(
|
|
516
|
+
short: str, delivery_id: str, *, turn_id: str
|
|
517
|
+
) -> dict[str, Any]:
|
|
518
|
+
if not isinstance(turn_id, str) or not turn_id:
|
|
519
|
+
raise DeliveryReceiptError("Accepted Codex message has no turn ID; inspect the delivery")
|
|
520
|
+
with receipt_lock(short, delivery_id):
|
|
521
|
+
value = read_receipt(short, delivery_id)
|
|
522
|
+
if value is None:
|
|
523
|
+
raise DeliveryReceiptError(f"unknown Codex delivery: {delivery_id}")
|
|
524
|
+
if value.get("state") == "accepted":
|
|
525
|
+
if value.get("turnId") != turn_id:
|
|
526
|
+
raise DeliveryReceiptConflict("accepted delivery turn mismatch")
|
|
527
|
+
return value
|
|
528
|
+
if value.get("state") != "submitted":
|
|
529
|
+
raise DeliveryReceiptError(
|
|
530
|
+
f"cannot accept Codex delivery from state={value.get('state')!r}"
|
|
531
|
+
)
|
|
532
|
+
actual_route = route(value) or "new_turn"
|
|
533
|
+
updated = {
|
|
534
|
+
**value,
|
|
535
|
+
"state": "accepted",
|
|
536
|
+
"acceptedAtMs": now_ms(),
|
|
537
|
+
"turnId": turn_id,
|
|
538
|
+
"placement": "current_turn" if actual_route == "steer" else "new_turn",
|
|
539
|
+
"turnRef": turn_ref_for(turn_id),
|
|
540
|
+
"rejectionCode": None,
|
|
541
|
+
"rejectionMessage": None,
|
|
542
|
+
}
|
|
543
|
+
_write(short, delivery_id, updated)
|
|
544
|
+
return updated
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def mark_settled(
|
|
548
|
+
short: str, delivery_id: str, *, turn_id: str,
|
|
549
|
+
original_positive_completion: dict[str, Any],
|
|
550
|
+
) -> dict[str, Any]:
|
|
551
|
+
"""Immutably attach original native turn/completed evidence to accepted ID."""
|
|
552
|
+
from ccx.store.root import _HASH
|
|
553
|
+
if (not isinstance(original_positive_completion, dict)
|
|
554
|
+
or original_positive_completion.get("turnId") != turn_id
|
|
555
|
+
or original_positive_completion.get("status") != "completed"
|
|
556
|
+
or not isinstance(original_positive_completion.get("text"), str)
|
|
557
|
+
or not isinstance(original_positive_completion.get("nativeEvidenceSha256"), str)
|
|
558
|
+
or not _HASH.fullmatch(original_positive_completion["nativeEvidenceSha256"])):
|
|
559
|
+
raise DeliveryReceiptError("Codex turn completion is unconfirmed; inspect the delivery")
|
|
560
|
+
with receipt_lock(short, delivery_id):
|
|
561
|
+
value = read_receipt(short, delivery_id)
|
|
562
|
+
if (value is None or value.get("state") != "accepted"
|
|
563
|
+
or value.get("turnId") != turn_id):
|
|
564
|
+
raise DeliveryReceiptError("Codex turn was not accepted; inspect the delivery")
|
|
565
|
+
prior = value.get("settlement")
|
|
566
|
+
if prior is not None:
|
|
567
|
+
# Live item/completed and source-fenced thread/read can render a
|
|
568
|
+
# different answer (and therefore a different evidence digest) for
|
|
569
|
+
# the very same completed native turn. The first proof is immutable;
|
|
570
|
+
# only a different original identity is a conflicting observation.
|
|
571
|
+
identity = ("workerId", "engine", "nativeSource", "threadId",
|
|
572
|
+
"turnId", "deliveryId", "status")
|
|
573
|
+
if (not isinstance(prior, dict) or any(
|
|
574
|
+
prior.get(field) != original_positive_completion.get(field)
|
|
575
|
+
for field in identity
|
|
576
|
+
)):
|
|
577
|
+
raise DeliveryReceiptConflict("Codex completion record changed; inspect the worker")
|
|
578
|
+
return value
|
|
579
|
+
result = dict(value)
|
|
580
|
+
result["settlement"] = dict(original_positive_completion)
|
|
581
|
+
result["settledAtMs"] = now_ms()
|
|
582
|
+
_write(short, delivery_id, result)
|
|
583
|
+
return result
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def mark_rejected(
|
|
587
|
+
short: str,
|
|
588
|
+
delivery_id: str,
|
|
589
|
+
*,
|
|
590
|
+
code: str,
|
|
591
|
+
message: str,
|
|
592
|
+
) -> dict[str, Any]:
|
|
593
|
+
return _transition(
|
|
594
|
+
short,
|
|
595
|
+
delivery_id,
|
|
596
|
+
{"prepared", "submitted"},
|
|
597
|
+
"rejected",
|
|
598
|
+
rejectedAtMs=now_ms(),
|
|
599
|
+
rejectionCode=code,
|
|
600
|
+
rejectionMessage=message,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _receipt_rows(short: str, *, strict: bool) -> Iterator[dict[str, Any]]:
|
|
605
|
+
"""Reuse original Codex integrity distinction: status tolerates damage,
|
|
606
|
+
native-turn ownership decisions must see and refuse unrelated corruption.
|
|
607
|
+
"""
|
|
608
|
+
from ccx.store.root import _read_json, StorageIntegrityError
|
|
609
|
+
store = _store()
|
|
610
|
+
owner = _owner(store, short)
|
|
611
|
+
with store.lease():
|
|
612
|
+
try:
|
|
613
|
+
with store._gen("semantic-deliveries", "codex",
|
|
614
|
+
owner.worker_id) as fd:
|
|
615
|
+
with os.scandir(fd) as entries:
|
|
616
|
+
names = sorted(entry.name for entry in entries
|
|
617
|
+
if entry.name.endswith(".json"))
|
|
618
|
+
if len(names) > 100000:
|
|
619
|
+
raise DeliveryReceiptError("Too many Codex delivery records; narrow the request")
|
|
620
|
+
rows = []
|
|
621
|
+
for name in names:
|
|
622
|
+
try:
|
|
623
|
+
rows.append(_read_json(fd, name))
|
|
624
|
+
except StorageIntegrityError as exc:
|
|
625
|
+
if strict:
|
|
626
|
+
raise DeliveryReceiptError(
|
|
627
|
+
"Cannot read Codex turn record "
|
|
628
|
+
+ name
|
|
629
|
+
) from exc
|
|
630
|
+
except FileNotFoundError:
|
|
631
|
+
rows = []
|
|
632
|
+
for value in rows:
|
|
633
|
+
try:
|
|
634
|
+
if not isinstance(value, dict):
|
|
635
|
+
raise DeliveryReceiptError("Codex semantic receipt invalid shape")
|
|
636
|
+
delivery_id = value.get("deliveryId")
|
|
637
|
+
if not valid_delivery_id(delivery_id):
|
|
638
|
+
raise DeliveryReceiptError("Codex delivery ID is invalid; use the saved delivery ID")
|
|
639
|
+
yield _validate(value, short=short, delivery_id=delivery_id)
|
|
640
|
+
except DeliveryReceiptError:
|
|
641
|
+
if strict:
|
|
642
|
+
raise
|
|
643
|
+
|
|
644
|
+
|
|
645
|
+
def iter_receipts(short: str) -> Iterator[dict[str, Any]]:
|
|
646
|
+
"""Original best-effort status listing; no native ownership decisions."""
|
|
647
|
+
yield from _receipt_rows(short, strict=False)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def receipt_for_turn(short: str, turn_id: str) -> dict[str, Any] | None:
|
|
651
|
+
"""Resolve one native turn to its semantic delivery without hiding damage.
|
|
652
|
+
|
|
653
|
+
General receipt enumeration is intentionally best-effort so status/reporting
|
|
654
|
+
can survive an unrelated corrupt sidecar. Native-turn reconciliation is an
|
|
655
|
+
identity decision, though: silently skipping corruption there could erase
|
|
656
|
+
delivery provenance or conceal a second receipt claiming the same turn.
|
|
657
|
+
"""
|
|
658
|
+
matches = [
|
|
659
|
+
value for value in _receipt_rows(short, strict=True)
|
|
660
|
+
if value.get("turnId") == turn_id
|
|
661
|
+
]
|
|
662
|
+
# Steered deliveries legitimately share the turn they joined, so only the
|
|
663
|
+
# delivery that opened the turn identifies it; two openers is damage.
|
|
664
|
+
openers = [value for value in matches if route(value) != "steer"]
|
|
665
|
+
if len(openers) > 1:
|
|
666
|
+
raise DeliveryReceiptError(
|
|
667
|
+
f"multiple Codex deliveries claim to have opened native turn {turn_id}"
|
|
668
|
+
)
|
|
669
|
+
if openers:
|
|
670
|
+
return openers[0]
|
|
671
|
+
# Opened by input without a receipt (a raw send or a parent notification):
|
|
672
|
+
# the first delivery steered into it is the earliest semantic evidence.
|
|
673
|
+
matches.sort(key=lambda value: (value.get("acceptedAtMs") or 0,
|
|
674
|
+
str(value.get("deliveryId"))))
|
|
675
|
+
return matches[0] if matches else None
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
def public_receipt(value: dict[str, Any] | None) -> dict[str, Any] | None:
|
|
679
|
+
if value is None:
|
|
680
|
+
return None
|
|
681
|
+
state = value.get("state")
|
|
682
|
+
mode = requested_mode(value)
|
|
683
|
+
actual_route = route(value)
|
|
684
|
+
actual_placement = placement(value)
|
|
685
|
+
turn_id = value.get("turnId")
|
|
686
|
+
if state in {"prepared", "owned"}:
|
|
687
|
+
action = "retry_same_id"
|
|
688
|
+
elif state == "submitted":
|
|
689
|
+
action = "wait"
|
|
690
|
+
else:
|
|
691
|
+
action = "none"
|
|
692
|
+
return {
|
|
693
|
+
"delivery_id": value.get("deliveryId"),
|
|
694
|
+
# Detailed durable receipt state; The public protocol normalizes its own
|
|
695
|
+
# top-level reconciliation vocabulary separately.
|
|
696
|
+
"status": state,
|
|
697
|
+
"requested_mode": mode,
|
|
698
|
+
"caller_metadata": value.get("callerMetadata"),
|
|
699
|
+
"cap": value.get("cap"),
|
|
700
|
+
"owned": state in {"owned", "submitted", "accepted"},
|
|
701
|
+
"recommended_action": action,
|
|
702
|
+
"route": actual_route,
|
|
703
|
+
"placement": actual_placement,
|
|
704
|
+
"target_turn_id": value.get("targetTurnId"),
|
|
705
|
+
"turn_ref": value.get("turnRef") or (
|
|
706
|
+
turn_ref_for(turn_id) if state == "accepted" else None
|
|
707
|
+
),
|
|
708
|
+
"turn_id": turn_id,
|
|
709
|
+
"client_user_message_id": value.get("clientUserMessageId"),
|
|
710
|
+
"created_at_ms": value.get("createdAtMs"),
|
|
711
|
+
"updated_at_ms": value.get("updatedAtMs"),
|
|
712
|
+
"owned_at_ms": value.get("ownedAtMs"),
|
|
713
|
+
"submitted_at_ms": value.get("submittedAtMs"),
|
|
714
|
+
"accepted_at_ms": value.get("acceptedAtMs"),
|
|
715
|
+
"settled_at_ms": value.get("settledAtMs"),
|
|
716
|
+
"settlement": value.get("settlement"),
|
|
717
|
+
"rejected_at_ms": value.get("rejectedAtMs"),
|
|
718
|
+
"rejection_code": value.get("rejectionCode"),
|
|
719
|
+
"rejection_message": value.get("rejectionMessage"),
|
|
720
|
+
"last_refusal": (
|
|
721
|
+
{
|
|
722
|
+
"code": value.get("lastRefusalCode"),
|
|
723
|
+
"message": value.get("lastRefusalMessage"),
|
|
724
|
+
"state": value.get("lastRefusalState"),
|
|
725
|
+
"at_ms": value.get("lastRefusalAtMs"),
|
|
726
|
+
}
|
|
727
|
+
if value.get("lastRefusalCode") or value.get("lastRefusalMessage")
|
|
728
|
+
else None
|
|
729
|
+
),
|
|
730
|
+
"pending": state in {"prepared", "owned", "submitted"},
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
__all__ = [
|
|
736
|
+
"DeliveryReceiptConflict",
|
|
737
|
+
"DeliveryReceiptError",
|
|
738
|
+
"MAX_DELIVERY_ID",
|
|
739
|
+
"TERMINAL_STATES",
|
|
740
|
+
"iter_receipts",
|
|
741
|
+
"mark_accepted",
|
|
742
|
+
"mark_owned",
|
|
743
|
+
"mark_rejected",
|
|
744
|
+
"mark_retryable_refusal",
|
|
745
|
+
"rollback_retryable",
|
|
746
|
+
"mark_submitted",
|
|
747
|
+
"new_delivery_id",
|
|
748
|
+
"prepare",
|
|
749
|
+
"placement",
|
|
750
|
+
"public_receipt",
|
|
751
|
+
"requested_mode",
|
|
752
|
+
"route",
|
|
753
|
+
"read_receipt",
|
|
754
|
+
"receipt_for_turn",
|
|
755
|
+
"require_matching",
|
|
756
|
+
"turn_ref_for",
|
|
757
|
+
"valid_delivery_id",
|
|
758
|
+
]
|