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,337 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import time
|
|
3
|
+
from typing import Any
|
|
4
|
+
from ccx.client import _compact_failed
|
|
5
|
+
from ccx.constants import COMPACT_WAIT_DEFAULT_S
|
|
6
|
+
from ccx.errors import (
|
|
7
|
+
CompactIndeterminate,
|
|
8
|
+
DeliveryRejected,
|
|
9
|
+
InvalidRequest,
|
|
10
|
+
SessionNotFound,
|
|
11
|
+
SessionUnavailable,
|
|
12
|
+
)
|
|
13
|
+
from ccx.ops.common import split_target
|
|
14
|
+
from ccx.ops.send import ensure_ready
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _original_claude_compact_view(store, owner, rec: dict, operation_id: str,
|
|
18
|
+
*, publish: bool = True) -> dict | None:
|
|
19
|
+
"""One source-verified projection for both mutating and pure observations."""
|
|
20
|
+
from ccx.ops.compact_ops import observe_original_operation
|
|
21
|
+
|
|
22
|
+
operation = observe_original_operation(
|
|
23
|
+
store, owner, rec, operation_id, publish=publish,
|
|
24
|
+
)
|
|
25
|
+
if operation is None:
|
|
26
|
+
return None
|
|
27
|
+
result = operation.get("result") or {}
|
|
28
|
+
status = operation.get("status") or "unknown"
|
|
29
|
+
latest = store.record(owner.worker)
|
|
30
|
+
return {
|
|
31
|
+
"short": owner.short, "worker_id": owner.worker_id,
|
|
32
|
+
"state": (latest.get("lifecycle") or {}).get("state"),
|
|
33
|
+
"compacted": status == "succeeded",
|
|
34
|
+
"message": result.get("message"),
|
|
35
|
+
"pre_tokens": result.get("pre_tokens"),
|
|
36
|
+
"post_tokens": result.get("post_tokens"),
|
|
37
|
+
"duration_ms": result.get("duration_ms"),
|
|
38
|
+
"operation_id": operation_id, "operation_status": status,
|
|
39
|
+
"evidence_uuid": operation.get("evidence_uuid"),
|
|
40
|
+
"reason": operation.get("reason"),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def compact_status(target: str, *, operation_id: str, timeout: float = 0.75,
|
|
45
|
+
publish: bool = True) -> dict[str, Any]:
|
|
46
|
+
"""Read original Claude compact; pure if publish=False, never native."""
|
|
47
|
+
from ccx import constants
|
|
48
|
+
from ccx.ops.compact_ops import get_operation, compact_intent_digest, CompactJournalError
|
|
49
|
+
from ccx.store.root import require_ccx_active
|
|
50
|
+
from ccx.store.root import GenerationError
|
|
51
|
+
|
|
52
|
+
handle, engine, worker_id = split_target(target)
|
|
53
|
+
if engine != "claude":
|
|
54
|
+
raise SessionUnavailable("Codex compaction is unavailable for this worker")
|
|
55
|
+
if (not isinstance(operation_id, str) or not operation_id
|
|
56
|
+
or len(operation_id) > 128 or "\x00" in operation_id):
|
|
57
|
+
raise InvalidRequest("compact_status needs a nonempty operation_id of at most 128 characters")
|
|
58
|
+
store = require_ccx_active(constants.CCX_ROOT)
|
|
59
|
+
owner = store.resolve(handle)
|
|
60
|
+
rec = store.record(handle)
|
|
61
|
+
native = rec.get("native") or {}
|
|
62
|
+
session_id = native.get("sessionId") if isinstance(native, dict) else None
|
|
63
|
+
try:
|
|
64
|
+
prior = get_operation(owner.worker, operation_id)
|
|
65
|
+
receipt = store.read_receipt(owner.worker, "compact", operation_id)
|
|
66
|
+
if prior is None:
|
|
67
|
+
if receipt is None:
|
|
68
|
+
raise SessionNotFound(f"No compaction found for operation {operation_id!r}", short=owner.short)
|
|
69
|
+
raise CompactIndeterminate(
|
|
70
|
+
"Compaction result cannot be verified; inspect its operation",
|
|
71
|
+
short=owner.short, operation_id=operation_id, operation_status="unknown",
|
|
72
|
+
)
|
|
73
|
+
digest = compact_intent_digest(
|
|
74
|
+
owner.worker_id, generation=store.generation,
|
|
75
|
+
native_session_id=session_id, operation_id=operation_id,
|
|
76
|
+
)
|
|
77
|
+
if (not receipt or receipt.get("state") not in (
|
|
78
|
+
"submitted-unknown", "accepted", "rejected")
|
|
79
|
+
or receipt.get("intentSha256") != digest
|
|
80
|
+
or receipt.get("workerId") != owner.worker_id
|
|
81
|
+
or receipt.get("engine") != "claude"
|
|
82
|
+
or receipt.get("generation") != store.generation
|
|
83
|
+
or receipt.get("family") != "compact"
|
|
84
|
+
or receipt.get("action") != "compact"):
|
|
85
|
+
raise CompactIndeterminate(
|
|
86
|
+
"Compaction confirmation is missing; inspect its operation",
|
|
87
|
+
short=owner.short, operation_id=operation_id, operation_status="unknown",
|
|
88
|
+
)
|
|
89
|
+
viewed = _original_claude_compact_view(
|
|
90
|
+
store, owner, rec, operation_id, publish=publish,
|
|
91
|
+
)
|
|
92
|
+
assert viewed is not None
|
|
93
|
+
return viewed
|
|
94
|
+
except (GenerationError, CompactJournalError, OSError) as exc:
|
|
95
|
+
raise CompactIndeterminate(
|
|
96
|
+
f"Cannot read compaction status; retry: {exc}",
|
|
97
|
+
short=owner.short, operation_id=operation_id, operation_status="unknown",
|
|
98
|
+
) from exc
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def compact_worker(
|
|
102
|
+
target: str,
|
|
103
|
+
*,
|
|
104
|
+
operation_id: str,
|
|
105
|
+
timeout: float | None = 90,
|
|
106
|
+
wait: bool = True,
|
|
107
|
+
execution_timeout: float | None = None,
|
|
108
|
+
) -> dict[str, Any]:
|
|
109
|
+
"""One receipt-fenced native Claude /compact, then same-ID journal observation."""
|
|
110
|
+
from ccx import constants
|
|
111
|
+
from ccx.ops.compact_ops import compact_intent_digest, get_operation, CompactJournalError
|
|
112
|
+
from ccx.constants import COMPACT_POLL_S
|
|
113
|
+
import math
|
|
114
|
+
from ccx.store.root import GenerationError, NativeEffectIndeterminate, require_ccx_active
|
|
115
|
+
from ccx.store.store import _digest
|
|
116
|
+
from ccx.rpc.rpc import _rpc
|
|
117
|
+
|
|
118
|
+
handle, engine, worker_id = split_target(target)
|
|
119
|
+
if engine != "claude":
|
|
120
|
+
raise SessionUnavailable("Codex compaction is unavailable for this worker")
|
|
121
|
+
if (not isinstance(operation_id, str) or not operation_id
|
|
122
|
+
or len(operation_id) > 128 or "\x00" in operation_id):
|
|
123
|
+
raise InvalidRequest("compact operation_id must be a nonempty string of at most 128 characters")
|
|
124
|
+
if timeout is not None and (type(timeout) not in (float, int)
|
|
125
|
+
or not math.isfinite(timeout) or timeout < 0):
|
|
126
|
+
raise InvalidRequest("compact observation timeout must be finite, nonnegative or None")
|
|
127
|
+
if (execution_timeout is not None
|
|
128
|
+
and (type(execution_timeout) not in (int, float)
|
|
129
|
+
or not math.isfinite(execution_timeout) or execution_timeout <= 0)):
|
|
130
|
+
raise InvalidRequest("compact execution_timeout must be finite and positive or None")
|
|
131
|
+
deadline = None if timeout is None else time.monotonic() + timeout
|
|
132
|
+
store = require_ccx_active(constants.CCX_ROOT)
|
|
133
|
+
owner = store.resolve(handle)
|
|
134
|
+
rec = store.record(handle)
|
|
135
|
+
native = rec.get("native") or {}
|
|
136
|
+
session_id = native.get("sessionId") if isinstance(native, dict) else None
|
|
137
|
+
if not isinstance(session_id, str) or not session_id:
|
|
138
|
+
raise SessionUnavailable("original Claude native session ID is unavailable", short=owner.short)
|
|
139
|
+
digest = compact_intent_digest(
|
|
140
|
+
owner.worker_id, generation=store.generation,
|
|
141
|
+
native_session_id=session_id, operation_id=operation_id,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def indeterminate(reason: str, *, status: str = "unknown") -> CompactIndeterminate:
|
|
145
|
+
return CompactIndeterminate(
|
|
146
|
+
reason, short=owner.short,
|
|
147
|
+
operation_id=operation_id, operation_status=status,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Original operation reconciliation precedes any host revival, claim or IPC.
|
|
151
|
+
try:
|
|
152
|
+
prior = get_operation(handle, operation_id)
|
|
153
|
+
effect = store.read_receipt(handle, "compact", operation_id)
|
|
154
|
+
except (GenerationError, CompactJournalError, OSError) as exc:
|
|
155
|
+
raise indeterminate(f"cannot inspect original compact receipt/journal: {exc}") from exc
|
|
156
|
+
if effect is not None and (
|
|
157
|
+
effect.get("intentSha256") != digest
|
|
158
|
+
or effect.get("workerId") != owner.worker_id
|
|
159
|
+
or effect.get("engine") != "claude"
|
|
160
|
+
or effect.get("generation") != store.generation
|
|
161
|
+
or effect.get("action") != "compact"
|
|
162
|
+
or effect.get("family") != "compact"
|
|
163
|
+
):
|
|
164
|
+
raise indeterminate("original compact receipt identity does not match")
|
|
165
|
+
if prior is not None and (effect is None or effect.get("state") == "prepared"):
|
|
166
|
+
raise indeterminate("original compact journal lacks a submitted native effect receipt")
|
|
167
|
+
if effect and effect.get("state") == "rejected":
|
|
168
|
+
raise DeliveryRejected(
|
|
169
|
+
"native compact was rejected; request a new operation_id to try again",
|
|
170
|
+
short=owner.short, operation_id=operation_id,
|
|
171
|
+
refusal="compact-rejected",
|
|
172
|
+
)
|
|
173
|
+
submitted = effect is not None and effect.get("state") in ("submitted-unknown", "accepted")
|
|
174
|
+
if submitted and prior is None:
|
|
175
|
+
raise indeterminate("submitted native compact has no original journal; never resubmit")
|
|
176
|
+
if effect is not None and effect.get("state") not in (
|
|
177
|
+
"prepared", "submitted-unknown", "accepted",
|
|
178
|
+
):
|
|
179
|
+
raise indeterminate("unsupported original compact intent state")
|
|
180
|
+
if not submitted:
|
|
181
|
+
left = None if deadline is None else max(0., deadline - time.monotonic())
|
|
182
|
+
if left is not None and left <= 0:
|
|
183
|
+
raise indeterminate("compact observation budget expired before native admission")
|
|
184
|
+
ensure_ready(
|
|
185
|
+
handle,
|
|
186
|
+
# Revive has a separate bounded original-native timeout; a
|
|
187
|
+
# longer user observation window must remain legal.
|
|
188
|
+
timeout=min(600., left) if left is not None else 180,
|
|
189
|
+
expected_generation=store.generation,
|
|
190
|
+
)
|
|
191
|
+
rec = store.record(handle)
|
|
192
|
+
store.authorize_native_action(handle, "compact")
|
|
193
|
+
attempt = store.begin_native_intent(
|
|
194
|
+
handle, action="compact", family="compact",
|
|
195
|
+
operation_id=operation_id, intent_sha256=digest,
|
|
196
|
+
)
|
|
197
|
+
if attempt.replayed:
|
|
198
|
+
raise indeterminate("original compact receipt already submitted; no replay")
|
|
199
|
+
budget = 2. if deadline is None else min(2., max(.001, deadline - time.monotonic()))
|
|
200
|
+
if deadline is not None and deadline - time.monotonic() <= 0:
|
|
201
|
+
raise indeterminate("compact observation budget expired before native dispatch")
|
|
202
|
+
native_budget = (
|
|
203
|
+
execution_timeout if execution_timeout is not None
|
|
204
|
+
else COMPACT_WAIT_DEFAULT_S
|
|
205
|
+
)
|
|
206
|
+
try:
|
|
207
|
+
reply = store.dispatch_native_intent_once(
|
|
208
|
+
attempt, dispatch_budget_s=budget,
|
|
209
|
+
bounded_submit=lambda seconds: _rpc(
|
|
210
|
+
handle,
|
|
211
|
+
{"op": "compact", "operation_id": operation_id,
|
|
212
|
+
"timeout": native_budget},
|
|
213
|
+
timeout=seconds,
|
|
214
|
+
),
|
|
215
|
+
)
|
|
216
|
+
except NativeEffectIndeterminate:
|
|
217
|
+
# A lost reply is NOT a proved rejection, and this token must
|
|
218
|
+
# never be sent to the host a second time.
|
|
219
|
+
reply = None
|
|
220
|
+
if isinstance(reply, dict) and reply.get("ok") is True:
|
|
221
|
+
journal_reply = reply.get("operation")
|
|
222
|
+
if (reply.get("workerId") != owner.worker_id
|
|
223
|
+
or reply.get("engine") != "claude"
|
|
224
|
+
or reply.get("generation") != store.generation
|
|
225
|
+
or reply.get("nativeSessionId") != session_id
|
|
226
|
+
or reply.get("operationId") != operation_id
|
|
227
|
+
or not isinstance(journal_reply, dict)
|
|
228
|
+
or journal_reply.get("operation_id") != operation_id
|
|
229
|
+
or journal_reply.get("status") not in (
|
|
230
|
+
"accepted", "pasted", "submitted", "succeeded", "noop", "failed", "unknown",
|
|
231
|
+
)):
|
|
232
|
+
raise indeterminate("original compact host ACK identity or journal differs")
|
|
233
|
+
store.settle_native_intent(
|
|
234
|
+
attempt, state="accepted",
|
|
235
|
+
native_evidence_sha256=_digest({
|
|
236
|
+
"workerId": owner.worker_id, "generation": store.generation,
|
|
237
|
+
"operationId": operation_id, "journal": journal_reply,
|
|
238
|
+
}),
|
|
239
|
+
)
|
|
240
|
+
else:
|
|
241
|
+
# Only an authenticated host refusal (its own state field)
|
|
242
|
+
# PLUS a fresh trusted absent journal proves nothing reached Ink.
|
|
243
|
+
try:
|
|
244
|
+
prior = get_operation(handle, operation_id)
|
|
245
|
+
except (GenerationError, CompactJournalError, OSError) as exc:
|
|
246
|
+
raise indeterminate("compact journal unreadable after native IPC") from exc
|
|
247
|
+
if (isinstance(reply, dict) and reply.get("ok") is False
|
|
248
|
+
and isinstance(reply.get("state"), str)
|
|
249
|
+
and prior is None):
|
|
250
|
+
store.settle_native_intent(
|
|
251
|
+
attempt, state="rejected",
|
|
252
|
+
native_evidence_sha256=_digest({
|
|
253
|
+
"workerId": owner.worker_id, "operationId": operation_id,
|
|
254
|
+
"nativeRefusal": reply.get("error"),
|
|
255
|
+
"nativeState": reply["state"],
|
|
256
|
+
}),
|
|
257
|
+
)
|
|
258
|
+
raise DeliveryRejected(
|
|
259
|
+
"native compact refused: " + str(reply.get("error"))
|
|
260
|
+
+ "; use a new operation_id to retry",
|
|
261
|
+
short=owner.short, state=reply["state"],
|
|
262
|
+
operation_id=operation_id,
|
|
263
|
+
)
|
|
264
|
+
if prior is None:
|
|
265
|
+
raise indeterminate("native compact ACK unavailable; submitted token fenced")
|
|
266
|
+
# Journal presence is physical acceptance, *never* terminal success.
|
|
267
|
+
store.revalidate()
|
|
268
|
+
if not wait:
|
|
269
|
+
# The compaction journal proves durable native acceptance even before the
|
|
270
|
+
# provider has written a readable transcript. It is NOT a completion.
|
|
271
|
+
accepted = get_operation(handle, operation_id)
|
|
272
|
+
if accepted is None:
|
|
273
|
+
raise indeterminate("original native compact acceptance journal missing")
|
|
274
|
+
latest = store.record(handle)
|
|
275
|
+
return {
|
|
276
|
+
"short": owner.short, "worker_id": owner.worker_id,
|
|
277
|
+
"state": (latest.get("lifecycle") or {}).get("state"),
|
|
278
|
+
"operation_id": operation_id,
|
|
279
|
+
"operation_status": accepted.get("status") or "unknown",
|
|
280
|
+
"compacted": False, "pre_tokens": None, "post_tokens": None,
|
|
281
|
+
"duration_ms": None, "message": None,
|
|
282
|
+
"evidence_uuid": None, "reason": accepted.get("reason"),
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
def admission_released(row: dict) -> bool:
|
|
286
|
+
# A native compact boundary can precede the host's IDLE transition (or
|
|
287
|
+
# the Stop for a native turn queued behind /compact). The synchronous
|
|
288
|
+
# operation must include that lifecycle settlement, not just the journal.
|
|
289
|
+
if row.get("state") not in {
|
|
290
|
+
"idle", "idle-killed", "crashed", "dead", "budget-stopped",
|
|
291
|
+
}:
|
|
292
|
+
return False
|
|
293
|
+
current = store.record(handle)
|
|
294
|
+
lc = current.get("lifecycle") or {}
|
|
295
|
+
return (
|
|
296
|
+
lc.get("state") in {
|
|
297
|
+
"idle", "idle-killed", "crashed", "dead", "budget-stopped",
|
|
298
|
+
}
|
|
299
|
+
and not lc.get("activeTurnId")
|
|
300
|
+
and not current.get("activeCompact")
|
|
301
|
+
and not current.get("pendingSend")
|
|
302
|
+
and not current.get("waitingFor")
|
|
303
|
+
and not current.get("pendingApprovals")
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
while True:
|
|
307
|
+
row = compact_status(handle, operation_id=operation_id)
|
|
308
|
+
status = row.get("operation_status")
|
|
309
|
+
if status in ("succeeded", "noop") and admission_released(row):
|
|
310
|
+
return row
|
|
311
|
+
if status == "failed":
|
|
312
|
+
raise _compact_failed(
|
|
313
|
+
get_operation(handle, operation_id),
|
|
314
|
+
short=owner.short, state=row.get("state"),
|
|
315
|
+
operation_id=operation_id,
|
|
316
|
+
)
|
|
317
|
+
if status == "unknown":
|
|
318
|
+
raise indeterminate(
|
|
319
|
+
row.get("reason") or "native compact outcome indeterminate",
|
|
320
|
+
status="unknown",
|
|
321
|
+
)
|
|
322
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
323
|
+
# Final evidence at the deadline wins over a simultaneous timeout.
|
|
324
|
+
last = compact_status(handle, operation_id=operation_id)
|
|
325
|
+
if (last["operation_status"] in ("succeeded", "noop")
|
|
326
|
+
and admission_released(last)):
|
|
327
|
+
return last
|
|
328
|
+
if last["operation_status"] == "failed":
|
|
329
|
+
raise _compact_failed(
|
|
330
|
+
get_operation(handle, operation_id), short=owner.short,
|
|
331
|
+
state=last.get("state"), operation_id=operation_id,
|
|
332
|
+
)
|
|
333
|
+
raise indeterminate(
|
|
334
|
+
"compact outcome or worker input settlement remains pending at observation timeout",
|
|
335
|
+
status=last.get("operation_status") or "unknown",
|
|
336
|
+
)
|
|
337
|
+
time.sleep(COMPACT_POLL_S)
|