diff --git a/benchmark/README.md b/benchmark/README.md
index 61a27b4067..0e3a64c281 100644
--- a/benchmark/README.md
+++ b/benchmark/README.md
@@ -348,22 +348,24 @@ python -m benchmark.edgebench.checkpoint_judge --checkpoint-capture \
--tasks-dir /path/to/tasks --log-dir /path/to/private-run-logs
```
-This adapter's supported SForge source is the public compatibility fork at
-[`0e20fb499ee248f012d6467c00b3dec00a29fe85`](https://github.com/huangruiteng/EdgeBench/commit/0e20fb499ee248f012d6467c00b3dec00a29fe85).
-It exposes the existing native judge/admission owner; the upstream `sforge==1.1.0`
-distribution does not provide that seam. Enabling capture against an unsupported
-provider fails before adding routes. This pin qualifies the capture prerequisite,
-not an upstream release or restored admission. From this LoopX checkout, prepare
-an isolated Python 3.13 environment and verify the actual imported source:
+The supported public checkpoint provider is the compatibility fork at
+[`071a884012754127f28f5b84be10f533a42db527`](https://github.com/loopx-agent/EdgeBench/commit/071a884012754127f28f5b84be10f533a42db527).
+It contains `sforge.harness.checkpoint` and the native
+`create_app(..., checkpoints=True)` seam used by the controller below. The earlier
+`0e20fb499ee248f012d6467c00b3dec00a29fe85` compatibility pin and upstream
+`sforge==1.1.0` do not contain that API and cannot qualify joint continuation.
+Enabling capture against an unsupported provider fails before adding routes.
+This is pinned proposed source, not an upstream release. From this LoopX checkout,
+prepare an isolated Python 3.12 environment and verify the imported API:
```sh
CKPT_PROVIDER_ROOT=$(mktemp -d)
-git clone https://github.com/huangruiteng/EdgeBench.git "$CKPT_PROVIDER_ROOT/provider"
-git -C "$CKPT_PROVIDER_ROOT/provider" checkout --detach 0e20fb499ee248f012d6467c00b3dec00a29fe85
-uv venv --python 3.13 "$CKPT_PROVIDER_ROOT/venv"
+git clone https://github.com/loopx-agent/EdgeBench.git "$CKPT_PROVIDER_ROOT/provider"
+git -C "$CKPT_PROVIDER_ROOT/provider" checkout --detach 071a884012754127f28f5b84be10f533a42db527
+uv venv --python 3.12 "$CKPT_PROVIDER_ROOT/venv"
uv pip install --python "$CKPT_PROVIDER_ROOT/venv/bin/python" \
-e '.[test]' -e "$CKPT_PROVIDER_ROOT/provider" 'httpx>=0.28,<1'
-"$CKPT_PROVIDER_ROOT/venv/bin/python" -c 'import sforge,loopx; print(sforge.__file__); print(loopx.__file__)'
+"$CKPT_PROVIDER_ROOT/venv/bin/python" -c 'import inspect,sforge,loopx,sforge.harness.checkpoint; from sforge.harness.judge_server import create_app; assert "checkpoints" in inspect.signature(create_app).parameters; print(sforge.__file__); print(loopx.__file__)'
"$CKPT_PROVIDER_ROOT/venv/bin/python" -m pytest -q benchmark/tests/test_checkpoint_judge.py
# Linux with an owned disposable Docker daemon; grades only synthetic fixtures.
LOOPX_EDGEBENCH_DOCKER_SMOKE=1 "$CKPT_PROVIDER_ROOT/venv/bin/python" -m pytest -q \
@@ -517,10 +519,10 @@ continued research.
### Controller-only native capsule transport
The read-only participant above does **not** implement the newer native capsule
-API. The separate controller adapter targets
-[EdgeBench PR #23](https://github.com/ByteDance-Seed/EdgeBench/pull/23), pinned at
-`57dce5dd1fded9d5d5cbd44fa59c59d27a068d62`; this is proposed source, not a released
-upstream version. Start that pinned native judge with `sforge serve --checkpoints`.
+API. The separate controller adapter targets the API proposed in
+[EdgeBench PR #23](https://github.com/ByteDance-Seed/EdgeBench/pull/23), using the
+public compatibility pin `071a884012754127f28f5b84be10f533a42db527` above.
+Start that pinned native judge with `sforge serve --checkpoints`.
Its private controller configuration file has exactly `url`, `admin_secret` and
`bank_root` (the native log directory's `checkpoints` directory). Keep this file,
the bank and controller process outside every solver's filesystem/network authority.
@@ -636,6 +638,70 @@ Omit the controller file and `controller_handoff` on a new run to disable automa
controller capture; removing the checkpoint request disables capture entirely.
Existing artifacts remain inert, and the source trial is not restarted by disabling it.
+To reproduce **joint source capture and both fresh-guest receivers**, use
+[`qualify_checkpoint_joint.py`](tests/qualify_checkpoint_joint.py) from the current
+clean dedicated LoopX checkout; keep it frozen for the whole command. Run the
+controller on Linux as the ordinary owner of an explicitly
+owned disposable Docker daemon. Prepare the provider/venv above, install this
+checkout's Node dependencies with `npm ci`, and put a compatible **Host** Node on
+`PATH`. Separately supply Linux **amd64** binaries for `codex-cli 0.160.0` and Node
+`24.21.0`, and a caller-prepared `python:3.12-slim` image. These inputs are explicit;
+the qualification does not download a native CLI, pull an image or use model
+credentials. The receiver also imports `harbor==0.20.0` to exercise the actual
+phase-command builder; it does not launch a Harbor job. Build Python 3.12 guest
+dependencies from the pinned provider plus this qualification prerequisite:
+
+```sh
+set -eu
+"$CKPT_PROVIDER_ROOT/venv/bin/python" -c 'import sys,tomllib; from pathlib import Path; d=tomllib.loads((Path(sys.argv[1])/"pyproject.toml").read_text()); print("\n".join(d["project"]["dependencies"]+d["dependency-groups"]["dev"]+["harbor==0.20.0"]))' \
+ "$CKPT_PROVIDER_ROOT/provider" > "$CKPT_PROVIDER_ROOT/guest-requirements.txt"
+mkdir "$CKPT_PROVIDER_ROOT/guest-deps"
+docker image inspect python:3.12-slim >/dev/null
+docker run --rm --platform linux/amd64 --user "$(id -u):$(id -g)" \
+ -v "$CKPT_PROVIDER_ROOT:/qualification" python:3.12-slim \
+ python -m pip install --target /qualification/guest-deps \
+ -r /qualification/guest-requirements.txt
+# Set these to the explicitly prepared Linux amd64 binaries.
+CKPT_GUEST_CODEX=/absolute/path/to/codex
+CKPT_GUEST_NODE=/absolute/path/to/node
+"$CKPT_PROVIDER_ROOT/venv/bin/python" -m benchmark.tests.qualify_checkpoint_joint \
+ --provider-source "$CKPT_PROVIDER_ROOT/provider" \
+ --native-binary "$CKPT_GUEST_CODEX" --guest-node "$CKPT_GUEST_NODE" \
+ --guest-dependencies "$CKPT_PROVIDER_ROOT/guest-deps" \
+ --output "$CKPT_PROVIDER_ROOT/joint-result"
+```
+
+The public fixture runs six actual native work wakes, captures the pending
+pre-decision guard and continues the source; the ordinary and stride receivers
+then use separate fresh guests with no mounted source authority. It includes a
+whole declared workspace directory and a read-only Git member. Receivers run as
+the ordinary non-root owner, select the workspace by its sealed source path and
+materialize it through the typed artifact owner. Copied source registry/state
+files remain historical: current authority uses a fresh external registry/runtime,
+and the old Goal cannot be admitted through it. Only the model
+response stream and grading/backend fixtures are synthetic; the native CLI,
+canonical SQLite authority, worker, supervisor and capsule transport are real.
+Check `result.json`, `source.log`, `ordinary.log` and `stride.log` in the private
+output directory; failure is an error, including missing prerequisites, and a
+skipped test cannot produce a passed receipt. A second attempt needs a new output
+directory. Keep its controller config, native
+stores, reports and logs private. Cleanup removes only containers owned by this
+invocation. This reproduces mechanical continuation; the receipt remains
+`research_point_qualified=false`. A real later research point, scientific policy
+benefit and the packaged frontend restore journey still need separate acceptance.
+
+An enabled checkpoint rendezvous returns an explicit failed quota readback to
+its original caller without interrupting the writer or attempting capture.
+The original failure and recovery requirements remain unchanged; delivery of
+that error grants no work or settlement authority. Successful, foreign and
+malformed readbacks retain the existing typed capture checks. This contains
+optional capture failure; it does not repair the underlying quota error.
+
+The qualifier exports only the source's digest-bound native configuration through
+the existing Docker archive transport into a controller-owned private directory.
+It works with a root source guest and ordinary controller/receivers, preserves
+source permissions, and never exports the source authentication files.
+
An explicit **SForge phase-clock participant** preserves a declared Host file's
remaining duration alongside native opportunities. Add
`--phase-deadline-file /private/source-phase-deadline` to capture, and
@@ -706,7 +772,7 @@ runtime receipt 绑定摘要;programmatic adapter 也可指定已放入 guest
已放入 guest 的声明文件丢失、权限失败或损坏只记录捕获失败,原工作仍按
完整的原始 quota guard 继续;准入 owner 不可用仍停止。首次 replan 捕获
失败后,修复声明不得把后来的状态补记成同一原点。评测参与者使用上文明确的
-公开 SForge fork/pin 和 Python 3.13;普通 upstream 安装缺少该 seam 时明确
+公开 SForge fork/pin 和 Python 3.12;普通 upstream 安装缺少该 seam 时明确
拒绝。Codex 原生会话的真实分页/compaction 与隔离 fork 须单独验证。
评测机会及 incumbent 参与者也分配未占用的 ID,并在封存的
`point_metadata.evaluation_opportunity` 中记录实际映射;无 incumbent 时对应值为 null。
@@ -733,8 +799,15 @@ worker 保留原 guard,controller 暂停全部容器写入者,核对仍有
文件 witness,再封存原生评测机会、incumbent 及剩余预算。源变化、过期或失去
交接会拒绝;可恢复的捕获失败不取消原研究,也不得改用稍后的状态。解除暂停后,
源会话继续使用原 packet 和 deadline。
-真实 native worker、Docker 与 native HTTP 的合成验证覆盖了联合捕获和源接续;
-产物仍是 `research_point_qualified=false`。独立目标环境的新权限准入、有效工作、
-联合丢回复恢复及后续评测尚待一体验证,不能据此重启研究实验。新运行省略
+上文的公开复现命令使用固定 provider `071a884`、Python 3.12、显式 Linux amd64
+Codex/Node 及调用者准备的 Docker 镜像。非 root controller 从六个真实工作 wake
+形成模型决策前捕获,保留完整声明的目录及只读 Git 对象;普通与 stride receiver
+以非 root 身份在两个全新 guest 按封存源路径定位任务目录,由类型化 owner 物化;
+原注册表/状态只是历史,新的外置 registry/runtime 不授予原 Goal 权限。
+分别验证新权限准入、完整历史、剩余预算/冷却及实际接续。模型响应和评分为合成
+fixture,原生 CLI、worker、supervisor、SQLite 和 capsule transport 使用真实路径。
+结果及三份日志只存私有输出,失败或缺少前置条件明确报错,重试须使用新目录;
+产物仍是 `research_point_qualified=false`。真实中后期研究点、策略净收益及安装后的
+frontend restore 旅程仍须另行验收。新运行省略
controller 文件及 handoff 开关即可关闭;移除 checkpoint 请求关闭全部捕获,
已有产物保持惰性。
diff --git a/benchmark/runtime/checkpoint.py b/benchmark/runtime/checkpoint.py
index 8cb5d6b494..669531f38a 100644
--- a/benchmark/runtime/checkpoint.py
+++ b/benchmark/runtime/checkpoint.py
@@ -295,6 +295,13 @@ def capture_before_replan(env: dict[str, str], receipt: dict[str, Any], body: st
"action": "select_replan", "quota": guard, "goal_id": env["LOOPX_GOAL_ID"],
"agent_id": env["LOOPX_AGENT_ID"], "turn_instance_id": receipt["turn_id"],
})
+ from .checkpoint_continuation import restored_capture_gate
+ restore_gate = restored_capture_gate(env, intent.get("obligation_id"))
+ receipt["checkpoint_restore"] = {k: v for k, v in restore_gate.items() if k != "instructions"}
+ preloaded_body = restore_gate.get("instructions", "") + preloaded_body
+ if not restore_gate["capture_allowed"]:
+ receipt["checkpoint"]["reason"] = restore_gate["reason"]
+ return preloaded_body
root = Path(env["LOOPX_WAKE_LOG_DIR"]).parent / "checkpoints"
if declaration_error is not None:
if intent["capture"]:
@@ -341,10 +348,11 @@ def capture_before_replan(env: dict[str, str], receipt: dict[str, Any], body: st
"point_metadata": {**request["point_metadata"], "replan": intent,
"capture_order": "before_model_replan_decision"}}
effective_request.pop("trigger")
- capture_after_wake(env, prior, supplied_request=(effective_request, raw))
+ capture_after_wake(env, prior, supplied_request=(effective_request, raw), pending_guard=guard_path)
receipt["checkpoint"].update(prior["checkpoint"], phase="before_replan")
receipt["checkpoint"]["source_turn_id"] = prior["turn_id"]
except Exception as error:
+ receipt["checkpoint_restore"] = {"capture_allowed": False, "reason": "capture_basis_unavailable"}
receipt["checkpoint"].update(reason="capture_failed", error_kind=type(error).__name__, error=str(error)[:500])
finally:
receipt["checkpoint"]["elapsed_seconds"] = time.monotonic() - started
@@ -354,7 +362,8 @@ def capture_before_replan(env: dict[str, str], receipt: dict[str, Any], body: st
def capture_after_wake(env: dict[str, str], receipt: dict[str, Any], *,
- supplied_request: tuple[dict[str, Any], bytes] | None = None) -> None:
+ supplied_request: tuple[dict[str, Any], bytes] | None = None,
+ pending_guard: Path | None = None) -> None:
request_path = env.get("LOOPX_CHECKPOINT_REQUEST")
if not request_path:
return # Feature-off path performs no additional read or write.
@@ -381,6 +390,12 @@ def capture_after_wake(env: dict[str, str], receipt: dict[str, Any], *,
request, raw = read_checkpoint_request(request_path)
else:
request, raw = supplied_request
+ if supplied_request is None:
+ from .checkpoint_continuation import restored_capture_gate
+ restore_gate = restored_capture_gate(env)
+ if not restore_gate["capture_allowed"]:
+ receipt["checkpoint"]["reason"] = restore_gate["reason"]
+ return
if request.get("trigger") == "fresh_replan":
receipt["checkpoint"]["reason"] = "pre_replan_hook_required"
return
@@ -414,6 +429,12 @@ def capture_after_wake(env: dict[str, str], receipt: dict[str, Any], *,
"request_sha256": hashlib.sha256(raw).hexdigest(),
}
components = request["components"]
+ if pending_guard is not None:
+ decision_id = _participant_id(components, "held_decision")
+ components = [*components, {"id": decision_id, "source_path": str(pending_guard)}]
+ metadata.update(held_decision_component_id=decision_id,
+ decision_sha256=hashlib.sha256(pending_guard.read_bytes()).hexdigest(),
+ replan=request["point_metadata"]["replan"], capture_order="before_model_replan_decision")
if request.get("loopx_state"):
evidence = Path(env["LOOPX_WAKE_LOG_DIR"]) / receipt["turn_id"] / "checkpoint-research-context.json"
components, research = research_participants(env, components, metadata, destination, evidence, deadline)
@@ -555,19 +576,27 @@ def remaining():
if hashlib.sha256(raw).hexdigest() != message["sha256"]:
raise ValueError("decision handoff digest mismatch")
quota = json.loads(raw)
+ # This is transport dispatch, not capture admission: an explicit quota
+ # failure still belongs to its original caller's recovery path. Do not
+ # signal that writer or submit its failure to the successful-readback
+ # selector. Successful and foreign/malformed packets retain typed checks.
+ failed_readback = isinstance(quota, dict) and quota.get("ok") is False and quota.get("goal_id") == goal_id
interrupted = None
- if native_process is not None and interrupt_when is None:
+ if not failed_readback and native_process is not None and interrupt_when is None:
from .worker import interrupt_checkpoint_process
# Interrupt before the typed selector: even this read can outlive a
# native tool yield. Keep original bytes for same-identity recovery.
interrupted = interrupt_checkpoint_process(native_process, timeout=min(interrupt_timeout, remaining()))
- intent = effect_runtime_result("runtime.checkpoint_artifacts", {
- "action": "select_replan", "quota": quota, "goal_id": goal_id,
- "agent_id": agent_id, "turn_instance_id": (turn_instance_id if turn_instance_id is not None
- else (quota.get("heartbeat_receipt") or {}).get("turn_instance_id")),
- }, timeout=remaining())
- if native_process is not None and interrupt_when is not None and interrupt_when(intent):
+ if failed_readback:
+ intent = {"capture": False, "reason": "quota_readback_failed", "execution_authority_granted": False}
+ else:
+ intent = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "select_replan", "quota": quota, "goal_id": goal_id,
+ "agent_id": agent_id, "turn_instance_id": (turn_instance_id if turn_instance_id is not None
+ else (quota.get("heartbeat_receipt") or {}).get("turn_instance_id")),
+ }, timeout=remaining())
+ if not failed_readback and native_process is not None and interrupt_when is not None and interrupt_when(intent):
from .worker import interrupt_checkpoint_process
interrupted = interrupt_checkpoint_process(native_process, timeout=min(interrupt_timeout, remaining()))
diff --git a/benchmark/runtime/checkpoint_continuation.py b/benchmark/runtime/checkpoint_continuation.py
new file mode 100644
index 0000000000..4f470cadd2
--- /dev/null
+++ b/benchmark/runtime/checkpoint_continuation.py
@@ -0,0 +1,104 @@
+"""Codex checkpoint continuation evidence for both existing capture hooks.
+
+The session owner retains immutable source provenance; the artifact/settlement
+owners decide capture eligibility. This Host adapter never restores old authority.
+"""
+from __future__ import annotations
+
+import hashlib
+import json
+from pathlib import Path
+
+from loopx.control_plane.effect_runtime import effect_runtime_result
+from loopx.control_plane.quota.settlement import read_heartbeat_settlement
+from loopx.control_plane.turn_driver.codex_sessions import load_codex_cli_session
+
+
+def restored_capture_gate(env, current_obligation_id=None):
+ root = Path(env["LOOPX_RUNTIME_ROOT"])
+ lineage = {"goal_id": env["LOOPX_GOAL_ID"], "agent_id": env["LOOPX_AGENT_ID"]}
+ binding = load_codex_cli_session(root, lineage=lineage, session_scope="agent")
+ if not binding or not binding.get("checkpoint_artifact_sha256"):
+ return {"capture_allowed": True}
+ from .codex import Execution
+ from .sessions import BenchmarkSessionWake
+ from .native_checkpoint import native_session_checkpoint
+
+ wake = BenchmarkSessionWake(env, Execution(context="resume",
+ sandbox=env.get("LOOPX_CODEX_SANDBOX", "danger-full-access")), {})
+ if wake.session_id != binding["session_id"]:
+ raise ValueError("restored checkpoint conversation binding differs")
+ sha = binding["checkpoint_artifact_sha256"]
+ source = binding.get("checkpoint_artifact_root")
+ if not source:
+ raise ValueError("restored checkpoint lacks its immutable source; capture remains disabled")
+ unit = Path(source)
+ manifest = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "read_basis", "root": str(unit), "expected_sha256": sha,
+ })
+ # Admission already verified all artifact bytes. Here only the immutable
+ # declaration, original pending packet and native history are inputs; do
+ # not rehash large code/incumbent archives on every ordinary work wake.
+ metadata = manifest["point_metadata"]
+ pending = metadata.get("replan") or metadata.get("operator_metadata", {}).get("replan")
+ packet = None
+ if pending:
+ component = metadata.get("held_decision_component_id")
+ if component not in {entry["id"] for entry in manifest["components"]}:
+ raise ValueError("restored replan point lacks its original pending packet; capture remains disabled")
+ path = unit / "components" / component
+ if path.resolve(strict=True) != path:
+ raise ValueError("restored pending packet must be a physical artifact member")
+ raw = path.read_bytes()
+ if hashlib.sha256(raw).hexdigest() != metadata.get("decision_sha256"):
+ raise ValueError("restored pending replan packet digest differs")
+ packet = json.loads(raw)
+ intent = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "select_replan", "quota": packet,
+ "goal_id": metadata["loopx_state"]["goal_id"], "agent_id": metadata["loopx_state"]["agent_id"],
+ "turn_instance_id": packet["heartbeat_receipt"]["turn_instance_id"],
+ })
+ if intent != pending or intent.get("capture") is not True:
+ raise ValueError("restored original replan binding differs")
+ result = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "restore_capture", **lineage, "checkpoint_sha256": sha,
+ "current_obligation_id": current_obligation_id, "review": None,
+ })
+ # Existing saved preflights are discovery only. Each candidate is resolved
+ # through the canonical current-Goal settlement owner, never copied logs.
+ for guard_path in sorted(Path(env["LOOPX_WAKE_LOG_DIR"]).glob("*/checkpoint-start-guard.json"), reverse=True):
+ if guard_path.resolve(strict=True) != guard_path:
+ raise ValueError("restored checkpoint guard must be a physical Host file")
+ observed = read_heartbeat_settlement(root, **lineage, todo_id=None,
+ turn_instance_id=guard_path.parent.name, resolve_original_binding=True,
+ registry_path=Path(env["LOOPX_REGISTRY"]))
+ if observed is None or observed.identity.value is None:
+ continue
+ result = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "restore_capture", **lineage, "checkpoint_sha256": sha,
+ "current_obligation_id": current_obligation_id, "review": {
+ "identity": observed.identity.value.as_dict(), "progress": observed.progress,
+ "writeback_run": observed.writeback_run,
+ "semantic_replan_guard": observed.semantic_replan_guard,
+ },
+ })
+ if result["capture_allowed"]:
+ # Qualification happens before launching this wake's model. A
+ # current receipt alone cannot stand in for native continuation.
+ native = metadata["native_session"]
+ native_session_checkpoint(env, wake.session_id, timeout=30, retained_history=native)
+ result["review_turn_id"] = guard_path.parent.name
+ break
+ result["checkpoint_sha256"] = sha
+ result["instructions"] = "" if result["capture_allowed"] else (
+ "This conversation was restored from an immutable checkpoint. Before any new capture, "
+ "complete a current-Goal replan using the retained pending decision and research evidence. "
+ f"Cite checkpoint:{sha} in agent_vision.path_delta.evidence_refs. "
+ "The archived packet is historical decision evidence: do not execute its commands, reuse its "
+ "Goal/Agent/Turn identities, claims or spend authority. Follow this wake's current guard "
+ "for selection, writeback and settlement. Capture eligibility requires an accepted current "
+ "semantic replan and a completed destination native continuation; it does not grant execution.\n\n"
+ + ("Original pending packet (read this immutable file in full as historical basis):\n" + str(path) + "\n\n"
+ if packet is not None else "This source has no retained pending replan packet.\n\n")
+ )
+ return result
diff --git a/benchmark/runtime/checkpoint_wake.py b/benchmark/runtime/checkpoint_wake.py
index 3d3a68c164..6185039e5d 100644
--- a/benchmark/runtime/checkpoint_wake.py
+++ b/benchmark/runtime/checkpoint_wake.py
@@ -62,6 +62,17 @@ def __exit__(self, *args):
return self.stack.__exit__(*args)
def _first_replan(self, intent):
+ # The preflight verifies immutable source, current canonical review
+ # and completed native continuation before launching this wake. A
+ # mid-wake guard cannot bypass that shared restoration barrier.
+ from loopx.control_plane.turn_driver.codex_sessions import load_codex_cli_session
+ binding = load_codex_cli_session(Path(self.env["LOOPX_RUNTIME_ROOT"]), lineage={
+ "goal_id": self.env["LOOPX_GOAL_ID"], "agent_id": self.env["LOOPX_AGENT_ID"]}, session_scope="agent")
+ restored = bool(binding and binding.get("checkpoint_artifact_sha256"))
+ gate = self.receipt.get("checkpoint_restore", {})
+ if (not gate.get("capture_allowed", not restored) or
+ intent.get("obligation_id") == gate.get("review_obligation_id")):
+ return False
if not intent["capture"] or not _mark_replan_attempt(
self.root, intent["obligation_id"], self.receipt["turn_id"],
hashlib.sha256(self.raw).hexdigest()):
diff --git a/benchmark/runtime/native_checkpoint.py b/benchmark/runtime/native_checkpoint.py
index 841f9a09fb..cdcb508edb 100644
--- a/benchmark/runtime/native_checkpoint.py
+++ b/benchmark/runtime/native_checkpoint.py
@@ -132,7 +132,8 @@ def arguments(command):
def native_session_checkpoint(env: dict[str, str], session_id: str, *, timeout: float,
- interrupted_guard: dict | None = None) -> dict[str, Any]:
+ interrupted_guard: dict | None = None,
+ retained_history: dict | None = None) -> dict[str, Any]:
home = Path(env["CODEX_HOME"])
if not session_id or timeout <= 0:
raise ValueError("native checkpoint requires a session and an open capture window")
@@ -164,6 +165,12 @@ def request(method, params):
raise ValueError("native checkpoint session workspace differs from the declared code basis")
turns = _complete_history(request, session_id, interrupted_guard=interrupted_guard)
ids = [t["id"] for t in turns]
+ if retained_history is not None:
+ original = retained_history["turn_ids"]
+ prefix = json.dumps(turns[:len(original)], sort_keys=True, separators=(",", ":")).encode()
+ if (len(ids) <= len(original) or ids[:len(original)] != original or
+ hashlib.sha256(prefix).hexdigest() != retained_history["visible_history_sha256"]):
+ raise ValueError("native continuation lost or changed retained checkpoint history")
members = []
for entry in sorted(home.iterdir()):
if entry.name in {"sessions", "archived_sessions", "session_index.jsonl"} or re.fullmatch(
@@ -434,6 +441,8 @@ def admit_native_checkpoint_session(env: dict[str, str], unit: Path, expected_sh
goal_admission=wake.admission)
if not binding or binding.get("checkpoint_artifact_sha256") != expected_sha256:
raise ValueError("destination session binding already exists without this checkpoint admission")
+ if binding.get("checkpoint_artifact_root") not in (None, str(unit)):
+ raise ValueError("destination checkpoint source root differs from the original admission")
if wake.digest != source_profile:
raise ValueError("checkpoint source session profile changed")
native = metadata["native_session"]
@@ -461,7 +470,7 @@ def admit_native_checkpoint_session(env: dict[str, str], unit: Path, expected_sh
raise ValueError("destination Goal configuration changed during native checkpoint reconstruction")
receipt: dict[str, Any] = {}
wake = BenchmarkSessionWake(env, execution, receipt)
- wake._bind_verified_fork(result["session_id"], source_profile, expected_sha256)
+ wake._bind_verified_fork(result["session_id"], source_profile, expected_sha256, str(unit))
return {**result, "destination_goal_ref": wake.goal_ref, "destination_agent_id": lineage["agent_id"],
"session_binding_admitted": True, "session_profile_digest": wake.digest}
diff --git a/benchmark/runtime/sessions.py b/benchmark/runtime/sessions.py
index 131c60f149..af67715c8b 100644
--- a/benchmark/runtime/sessions.py
+++ b/benchmark/runtime/sessions.py
@@ -117,7 +117,7 @@ def observe(self, path: Path) -> None:
self.receipt["session"]["session_id"] = observed
def _bind_verified_fork(self, session_id: str, source_profile_digest: str,
- checkpoint_artifact_sha256: str) -> None:
+ checkpoint_artifact_sha256: str, checkpoint_artifact_root: str) -> None:
"""Bind a controller-verified native fork through the existing session owner.
This admits the conversation binding only. It does not claim work,
@@ -137,6 +137,7 @@ def _bind_verified_fork(self, session_id: str, source_profile_digest: str,
session_profile_digest=self.digest,
require_absent=True,
checkpoint_artifact_sha256=checkpoint_artifact_sha256,
+ checkpoint_artifact_root=checkpoint_artifact_root,
)
)
self.session_id = session_id
diff --git a/benchmark/tests/qualify_checkpoint_joint.py b/benchmark/tests/qualify_checkpoint_joint.py
new file mode 100644
index 0000000000..07e05ea52a
--- /dev/null
+++ b/benchmark/tests/qualify_checkpoint_joint.py
@@ -0,0 +1,191 @@
+"""Reproduce the existing synthetic native source and fresh-guest receivers.
+
+Requires an explicitly owned Docker daemon and Linux binaries/dependencies.
+No real model endpoint, benchmark task, active Goal or live bank is used.
+"""
+from __future__ import annotations
+
+import argparse
+import hashlib
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+from xml.etree import ElementTree
+
+
+PROVIDER_REVISION = "071a884012754127f28f5b84be10f533a42db527"
+
+
+def require_one_pass(report):
+ cases = ElementTree.parse(report).findall(".//testcase")
+ if len(cases) != 1 or any(
+ c.find(tag) is not None for c in cases for tag in ("skipped", "failure", "error")
+ ):
+ raise ValueError(f"Qualification requires one actual pass and no skip; inspect {report}")
+
+
+def _stage_source_configuration(container, native, destination):
+ from benchmark.edgebench.checkpoint_worker import _archive
+
+ # The source guest can be root while its controller is an ordinary user.
+ # Read only the bound config through the existing daemon archive seam;
+ # source UID/mode and auth files remain untouched.
+ config = _archive(container, str(Path(native["guest_home"]) / "config.toml"), destination)
+ config.chmod(0o600)
+ if hashlib.sha256(config.read_bytes()).hexdigest() != native["config_sha256"]:
+ raise ValueError("Source native configuration differs from the sealed checkpoint")
+ return config
+
+
+def main():
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument("--provider-source", type=Path, required=True)
+ parser.add_argument("--native-binary", type=Path, required=True)
+ parser.add_argument("--guest-node", type=Path, required=True)
+ parser.add_argument("--guest-dependencies", type=Path, required=True)
+ parser.add_argument("--output", type=Path, required=True)
+ parser.add_argument("--image", default="python:3.12-slim")
+ args = parser.parse_args()
+ if sys.platform != "linux" or os.getuid() == 0:
+ parser.error("Run on Linux as the ordinary owner of a disposable Docker daemon")
+ source = Path(__file__).resolve().parents[2]
+ source_revision = subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip()
+ if subprocess.check_output(["git", "-C", str(source), "status", "--porcelain"], text=True).strip():
+ parser.error("Use a clean dedicated source checkout and keep it frozen during qualification")
+ provider = args.provider_source.resolve(strict=True)
+ if subprocess.check_output(["git", "-C", str(provider), "rev-parse", "HEAD"], text=True).strip() != PROVIDER_REVISION:
+ parser.error("Provider must be the documented immutable checkpoint revision")
+ if subprocess.check_output(["git", "-C", str(provider), "status", "--porcelain"], text=True).strip():
+ parser.error("Provider source must be clean")
+ binaries = {"native": args.native_binary.resolve(strict=True),
+ "node": args.guest_node.resolve(strict=True)}
+ dependencies = args.guest_dependencies.resolve(strict=True)
+ output = args.output.absolute()
+ output.parent.resolve(strict=True)
+ if output.resolve() != output or output.exists():
+ parser.error("Output must be a fresh physical directory; prior evidence is never overwritten")
+ output.mkdir(mode=0o700)
+ sys.path.insert(0, str(provider))
+ import docker
+ import pytest
+ import sforge
+ from benchmark.edgebench.checkpoint_supervisor import CheckpointSupervisor
+ from benchmark.tests.test_checkpoint_controller import controllers, register, submit
+
+ if Path(sforge.__file__).resolve().parent != provider / "sforge":
+ parser.error("Imported provider does not match the declared source")
+ base = {str(source): {"bind": "/source", "mode": "ro"},
+ str(provider): {"bind": "/provider", "mode": "ro"},
+ str(dependencies): {"bind": "/deps", "mode": "ro"},
+ str(binaries["native"]): {"bind": "/native/codex", "mode": "ro"},
+ str(binaries["node"]): {"bind": "/usr/local/bin/node", "mode": "ro"}}
+ # Some development worktrees share node_modules through a physical symlink.
+ # Preserve that existing dependency resolution in the isolated guest too.
+ modules = source / "node_modules"
+ if modules.is_symlink():
+ physical = modules.resolve(strict=True)
+ base[str(physical)] = {"bind": str(physical), "mode": "ro"}
+ environment = {"PYTHONPATH": "/source:/provider:/deps", "LOOPX_USAGE_PING": "0",
+ "LOOPX_NATIVE_CHECKPOINT_BIN": "/native/codex"}
+ client = docker.from_env(timeout=30)
+ image = client.images.get(args.image).id # Caller-prepared image, no implicit pull.
+
+ def guest(command, volumes, env, log):
+ container = client.containers.create(image, command, platform="linux/amd64",
+ network_disabled=True, volumes={**base, **volumes}, working_dir="/source",
+ user=f"{os.getuid()}:{os.getgid()}", environment={**environment, **env})
+ try:
+ container.start()
+ status = container.wait(timeout=300)
+ log.write_bytes(container.logs())
+ assert status["StatusCode"] == 0, f"Qualification failed; inspect {log}"
+ require_one_pass(log.parent / log.stem / "junit.xml")
+ finally:
+ container.remove(force=True)
+
+ controller = output / "controller"
+ controller.mkdir()
+ source_files = output / "source-files"
+ source_files.mkdir()
+ patch = pytest.MonkeyPatch()
+ fixture = controllers.__wrapped__(controller, patch)
+ supervisor = container = None
+ try:
+ app, _, url, _, files, grades = next(fixture)
+ token = register(app, url, submission_cooldown=3600, max_agent_submissions=18)
+ assert submit(url, token, 7).status_code == 200
+ script = ("from pathlib import Path; import time,pytest; "
+ "p=Path('/opt/loopx-benchmark/control'); p.mkdir(parents=True); "
+ "(p/'phase-deadline').write_text(str(int(time.time())+7200)); "
+ "raise SystemExit(pytest.main(["
+ "'/source/benchmark/tests/test_native_checkpoint_handoff.py::"
+ "test_actual_worker_captures_pending_replan_before_native_model[planning]',"
+ "'-q','-rs','-p','no:cacheprovider','--basetemp=/qualification/source-tmp',"
+ "'--junitxml=/qualification/source.xml']))")
+ container = client.containers.create(image, ["python", "-c", script],
+ platform="linux/amd64", network_disabled=True,
+ volumes={**base, str(source_files): {"bind": "/qualification", "mode": "rw"}},
+ working_dir="/source", environment={**environment, "NODE_OPTIONS": "--experimental-sqlite",
+ "LOOPX_CHECKPOINT_EXTERNAL_CONTROLLER": "1"})
+ supervisor = CheckpointSupervisor(files[0], output / "points")
+ supervisor.bind({"SFORGE_TOKEN": token})
+ container.start()
+ supervisor.start(container)
+ status = container.wait(timeout=300)
+ (output / "source.log").write_bytes(container.logs())
+ supervisor.close()
+ assert status["StatusCode"] == 0, f"Inspect {output / 'source.log'}"
+ require_one_pass(source_files / "source.xml")
+ points = [p for p in (output / "points").iterdir() if p.is_dir() and not p.name.startswith(".")]
+ assert len(points) == 1 and len(grades) == 1
+ point = points[0]
+ packet = json.loads((point / "checkpoint.json").read_bytes())
+ metadata = packet["point_metadata"]
+ assert metadata["held_decision_component_id"] and metadata["decision_sha256"]
+ git_object = point / "components/task_workspace/.git/objects/ab/retained-object"
+ assert git_object.read_text() == "Readonly source evidence" and git_object.stat().st_mode & 0o777 == 0o444
+ config = _stage_source_configuration(container, metadata["native_session"], output / "native-config")
+ for stride in (False, True):
+ name = "stride" if stride else "ordinary"
+ results = output / name
+ results.mkdir()
+ guest_workspace = results / "workspace-root"
+ guest_workspace.mkdir()
+ guest(["python", "-m", "pytest", "benchmark/tests/test_checkpoint_joint_receiver.py",
+ "-q", "-rs", "-p", "no:cacheprovider", "--basetemp=/results/test",
+ "--junitxml=/results/junit.xml"],
+ {str(point): {"bind": "/point", "mode": "ro"},
+ str(config): {"bind": "/captured-config.toml", "mode": "ro"},
+ str(guest_workspace): {"bind": "/qualification", "mode": "rw"},
+ str(results): {"bind": "/results", "mode": "rw"}},
+ {"LOOPX_JOINT_RECEIVER_POINT": "/point", "LOOPX_JOINT_RECEIVER_CONFIG": "/captured-config.toml",
+ "LOOPX_RUNTIME_ROOT": "/results/transport-runtime",
+ **({"LOOPX_JOINT_RECEIVER_STRIDE": "1"} if stride else {})}, output / f"{name}.log")
+ assert subprocess.check_output(["git", "-C", str(source), "rev-parse", "HEAD"], text=True).strip() == source_revision, "Source changed during qualification"
+ assert not subprocess.check_output(["git", "-C", str(source), "status", "--porcelain"], text=True).strip(), "Source changed during qualification"
+ receipt = {"passed": True, "controller_uid": os.getuid(), "provider_revision": PROVIDER_REVISION,
+ "source_revision": source_revision, "source_tree_dirty": False,
+ "qualification_sha256": {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in
+ [Path(__file__), source / "benchmark/tests/test_native_checkpoint_handoff.py",
+ source / "benchmark/tests/test_checkpoint_joint_receiver.py"]},
+ "image_id": image, "point_sha256": packet["sha256"],
+ "binary_sha256": {k: hashlib.sha256(p.read_bytes()).hexdigest() for k, p in binaries.items()},
+ "ordinary_and_stride": True, "research_point_qualified": False}
+ (output / "result.json").write_text(json.dumps(receipt, indent=2))
+ print(json.dumps(receipt))
+ finally:
+ try:
+ if supervisor is not None:
+ supervisor.close()
+ finally:
+ if container is not None:
+ container.remove(force=True)
+ client.close()
+ fixture.close()
+ patch.undo()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/benchmark/tests/test_checkpoint_handoff.py b/benchmark/tests/test_checkpoint_handoff.py
index 0e2dacc8fa..e487184ba6 100644
--- a/benchmark/tests/test_checkpoint_handoff.py
+++ b/benchmark/tests/test_checkpoint_handoff.py
@@ -1,6 +1,8 @@
"""Private source rendezvous never substitutes a late or foreign point."""
import hashlib
import json
+import os
+import sys
import threading
import time
@@ -55,3 +57,106 @@ def test_surviving_request_is_not_overwritten(tmp_path):
await_controller_capture(tmp_path / "checkpoints", [], {},
{"source_sha256": "later", "source_witness": {}}, time.time() + 1)
assert ready.read_bytes() == b"original uncertain request"
+
+
+@pytest.mark.skipif(sys.platform != "linux", reason="Qualifies the Linux writer process group")
+@pytest.mark.parametrize("use_selector", [False, True])
+def test_failed_guard_returns_to_original_writer_without_interrupt(tmp_path, use_selector):
+ """Optional capture must not abort the caller's quota failure recovery."""
+ from pathlib import Path
+ import socket
+ import sys
+ import tempfile
+
+ from benchmark.runtime.checkpoint import held_quota_decision
+ from benchmark.runtime.worker import child_process
+ project = tmp_path / "source"
+ project.mkdir()
+ capture_root = project / "captures"
+ capture_root.mkdir(mode=0o700)
+ env = {**os.environ, "PYTHONPATH": str(Path(__file__).resolve().parents[2])}
+ with tempfile.TemporaryDirectory(prefix="failed-guard-", dir="/tmp") as short:
+ socket_path = Path(short).resolve() / "host.sock"
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as listener:
+ listener.bind(str(socket_path))
+ listener.listen(1)
+ listener.settimeout(30)
+ failed = {"ok": False, "mode": "should-run", "goal_id": "failed-source",
+ "decision": "skip", "should_run": False,
+ "error_code": "quota_unexpected_collection_error"}
+ packet = capture_root / "original"
+ packet.mkdir()
+ decision = packet / "decision.json"
+ raw = json.dumps(failed).encode()
+ decision.write_bytes(raw)
+ # The transport must release this failed packet without invoking
+ # capture admission or interrupting the independent original writer.
+ code = (
+ "import socket,json; from pathlib import Path; "
+ "s=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM); "
+ f"s.connect({str(socket_path)!r}); "
+ f"s.sendall({(json.dumps({'path': str(decision), 'sha256': hashlib.sha256(raw).hexdigest()}).encode() + bytes([10]))!r}); "
+ "d=json.loads(s.makefile('rb').readline()); assert d['released'] is True; "
+ f"assert d['sha256']=={hashlib.sha256(raw).hexdigest()!r}; "
+ f"Path('observed-failure.json').write_bytes(Path({str(decision)!r}).read_bytes()); "
+ "Path('source-work').write_text('original writer recovered')"
+ )
+ with child_process([sys.executable, "-c", code], env={**env, "LOOPX_PROJECT": str(project)},
+ stdout=None, stderr=None) as process:
+ process.stdin.close()
+ with held_quota_decision(listener, capture_root, goal_id="failed-source",
+ agent_id="failed-agent", turn_instance_id="failed-guard",
+ native_process=process,
+ interrupt_when=(lambda _: False) if use_selector else None) as held:
+ assert held["quota"]["ok"] is False
+ assert held["intent"]["capture"] is False
+ assert held["intent"]["execution_authority_granted"] is False
+ assert "native_process_return_code" not in held
+ assert process.poll() is None and not (project / "source-work").exists()
+ assert process.wait(timeout=30) == 0
+ assert (project / "source-work").read_text() == "original writer recovered"
+ assert (project / "observed-failure.json").read_bytes().strip() == raw
+ assert held["path"].read_bytes() == raw
+
+
+@pytest.mark.skipif(os.name != "posix", reason="Requires the Host Unix rendezvous")
+@pytest.mark.parametrize("ok,goal", [(False, "foreign"), ("false", "source"), (None, "source")])
+def test_failure_dispatch_does_not_relax_foreign_or_malformed_readback(tmp_path, ok, goal):
+ import socket
+ import tempfile
+ from pathlib import Path
+
+ from benchmark.runtime.checkpoint import held_quota_decision
+ from loopx.control_plane.effect_runtime import EffectRuntimeRemoteError
+
+ root = tmp_path / "decisions"
+ packet = root / "original"
+ packet.mkdir(parents=True)
+ decision = packet / "decision.json"
+ raw = json.dumps({"ok": ok, "goal_id": goal}).encode()
+ decision.write_bytes(raw)
+ responses = []
+ with tempfile.TemporaryDirectory(prefix="invalid-guard-", dir="/tmp") as short:
+ address = str(Path(short).resolve() / "host.sock")
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as listener:
+ listener.bind(address)
+ listener.listen(1)
+
+ def caller():
+ with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as channel:
+ channel.connect(address)
+ channel.sendall(json.dumps({"path": str(decision),
+ "sha256": hashlib.sha256(raw).hexdigest()}).encode() + b"\n")
+ responses.append(channel.recv(4096))
+
+ thread = threading.Thread(target=caller)
+ thread.start()
+ try:
+ with pytest.raises(EffectRuntimeRemoteError):
+ with held_quota_decision(listener, root, goal_id="source",
+ agent_id="agent", turn_instance_id="turn"):
+ pytest.fail("invalid readback cannot enter a capture boundary")
+ finally:
+ thread.join(5)
+ assert not thread.is_alive() and responses == [b""]
+ assert decision.read_bytes() == raw
diff --git a/benchmark/tests/test_checkpoint_joint_receiver.py b/benchmark/tests/test_checkpoint_joint_receiver.py
index 7f6c5926bc..7db1aa7772 100644
--- a/benchmark/tests/test_checkpoint_joint_receiver.py
+++ b/benchmark/tests/test_checkpoint_joint_receiver.py
@@ -9,6 +9,7 @@
import os
from pathlib import Path
import shlex
+import shutil
import subprocess
import sys
import threading
@@ -28,6 +29,7 @@
def test_captured_point_continues_with_fresh_authority(
tmp_path, monkeypatch, capsys, request
):
+ assert os.getuid() != 0, "Workspace continuation must qualify the actual non-root writer"
from benchmark.edgebench.checkpoint_controller import restore
from benchmark.runtime.codex import Execution
from benchmark.runtime.native_checkpoint import (
@@ -44,6 +46,9 @@ def test_captured_point_continues_with_fresh_authority(
)
from loopx.control_plane.quota.settlement import read_heartbeat_settlement
+ # Each variant uses its own fresh guest because the native fork retains
+ # fixed captured workspace/home paths. The default variant stays unchanged.
+ stride_continuation = os.environ.get("LOOPX_JOINT_RECEIVER_STRIDE") == "1"
unit = Path(os.environ["LOOPX_JOINT_RECEIVER_POINT"])
manifest = json.loads((unit / "checkpoint.json").read_bytes())
digest, metadata = manifest["sha256"], manifest["point_metadata"]
@@ -53,17 +58,45 @@ def test_captured_point_continues_with_fresh_authority(
if "completed_turn_id" in native:
assert native["completed_turn_id"] == native["turn_ids"][-1]
else:
- assert native["interrupted_guard"]["decision_sha256"] == metadata["decision_sha256"]
+ assert (
+ native["interrupted_guard"]["decision_sha256"]
+ == metadata["decision_sha256"]
+ )
config = Path(os.environ["LOOPX_JOINT_RECEIVER_CONFIG"])
workspace, home = Path(native["guest_workspace"]), Path(native["guest_home"])
assert not workspace.exists() and not home.exists(), (
"receiver must have fresh fixed guest paths"
)
- workspace.mkdir(parents=True)
+ # Select the sealed declared workspace by its exact source path. Actual
+ # workers declare a directory with caller-owned IDs, not a single code file.
+ roots = [entry for entry in snapshot["components"]
+ if entry["source_path"] == str(workspace)]
+ assert len(roots) == 1, "Requires one declared full native workspace"
+ materialized = tmp_path / "materialized-point"
+ verified = effect_runtime_result("runtime.checkpoint_artifacts", {
+ "action": "materialize", "root": str(unit), "expected_sha256": digest,
+ "destination": str(materialized),
+ })
+ assert verified["materialized"] and not verified["activation_performed"]
+ source_workspace = materialized / "components" / roots[0]["id"]
+ assert source_workspace.is_dir()
+ shutil.copytree(source_workspace, workspace)
monkeypatch.chdir(workspace)
candidate = workspace / "candidate.txt"
- candidate.write_bytes((unit / "components/code").read_bytes())
captured_candidate = candidate.read_text()
+ # Retained source registry/state files are historical inputs. Current CLI
+ # authority must use a fresh registry/runtime outside this copied tree.
+ historical_registry = workspace / ".loopx/registry.json"
+ historical_bytes = historical_registry.read_bytes()
+ historical = json.loads(historical_bytes)
+ original_runtime = Path(historical["common_runtime_root"])
+ assert not original_runtime.exists()
+ source_goal = metadata["loopx_state"]["goal_id"]
+ assert historical["goals"][0]["id"] == source_goal
+ assert (workspace / "evidence-6.json").read_bytes() == (source_workspace / "evidence-6.json").read_bytes()
+ retained_object = workspace / ".git/objects/ab/retained-object"
+ assert retained_object.read_text() == "Readonly source evidence"
+ assert retained_object.stat().st_mode & 0o777 == 0o444
registry, runtime = tmp_path / "registry.json", tmp_path / "runtime"
goal, agent, turn = "joint-receiver", "joint-researcher", "joint-work"
prefix = [
@@ -105,8 +138,27 @@ def cli(*args):
agent,
"--agent-work-mode",
agent + "=active",
+ *(
+ [
+ "--execution-stride-strategy",
+ "evidence_conditioned_v1",
+ "--execution-replan-after-turns",
+ "1",
+ "--explore-mode",
+ "planning",
+ ]
+ if stride_continuation
+ else []
+ ),
"--execute",
)
+ assert historical_registry.read_bytes() == historical_bytes
+ assert json.loads(registry.read_bytes())["goals"][0]["id"] == goal
+ assert runtime != original_runtime and not original_runtime.exists()
+ rejected = main([*prefix, "quota", "should-run", "--goal-id", source_goal,
+ "--agent-id", agent, "--turn-instance-id", "foreign-source"])
+ capsys.readouterr()
+ assert rejected != 0, "Source Goal must not acquire destination authority"
todo = cli(
"todo",
"add",
@@ -221,15 +273,19 @@ def lose_binding_reply(self, *args):
assert guard["interaction_contract"]["agent_channel"]["delivery_allowed"]
_, branch, _, url, controller_files, grades = request.getfixturevalue("controllers")
capsule = unit / "components" / snapshot["native_capsule_component_id"]
- opportunity = json.loads((capsule / "checkpoint.json").read_bytes())["point_metadata"][
- "native_reference"
- ]["opportunity"]
+ opportunity = json.loads((capsule / "checkpoint.json").read_bytes())[
+ "point_metadata"
+ ]["native_reference"]["opportunity"]
remaining_cooldown = opportunity["remaining_cooldown_seconds"]
# Explicit fixture boundaries: either already eligible at capture, or a
# cooldown longer than this entire 60-second native work window. A short
# partial cooldown would make a timing-dependent expectation ambiguous.
assert remaining_cooldown == 0 or remaining_cooldown > 60
cooldown_pending = remaining_cooldown > 60
+ if stride_continuation and not cooldown_pending:
+ pytest.skip(
+ "Stride receiver qualification requires a captured pending cooldown"
+ )
phase = tmp_path / "phase-deadline"
fresh = restore(
controller_files[1],
@@ -262,7 +318,25 @@ def lose_binding_reply(self, *args):
)
(workspace / "judge.json").chmod(0o600)
script = workspace / "continue.py"
- script.write_text("""import json,time,requests
+ if stride_continuation:
+ script.write_text(
+ """import json,requests
+from pathlib import Path
+b=json.loads(Path('judge.json').read_text()); url,submission_token=b['url'],b['token']
+h=requests.get(url+'/api/v1/history',params={'token':submission_token},timeout=5).json()
+assert h['best_score']==7 and h['agent_submissions']==1
+assert Path('candidate.txt').read_text()==CAPTURED_CANDIDATE
+source=json.loads(Path(CONTEXT).read_text())
+assert source
+Path('restoration-evidence.json').write_text(json.dumps({'code_unchanged':True,'public_incumbent':7,'consumed_submissions':1,'source_context_read':True}))
+print('Restored state verified; source code unchanged, incumbent and consumed opportunities retained.')
+""".replace("CAPTURED_CANDIDATE", repr(captured_candidate)).replace(
+ "CONTEXT", repr(adopted["artifact"])
+ )
+ )
+ else:
+ script.write_text(
+ """import json,time,requests
from pathlib import Path
b=json.loads(Path('judge.json').read_text()); url,submission_token=b['url'],b['token']
h=requests.get(url+'/api/v1/history',params={'token':submission_token},timeout=5).json()
@@ -283,9 +357,19 @@ def lose_binding_reply(self, *args):
time.sleep(.01)
assert result['status']=='completed'
print('Retained candidate and public best 7 used; candidate 8 graded.')
-""".replace("EXPECTED_COOLDOWN_PENDING", repr(cooldown_pending))
- .replace("CAPTURED_CANDIDATE", repr(captured_candidate)))
+""".replace("EXPECTED_COOLDOWN_PENDING", repr(cooldown_pending)).replace(
+ "CAPTURED_CANDIDATE", repr(captured_candidate)
+ )
+ )
requests = []
+ provider_phase = {
+ "count": 0,
+ "command": "cat "
+ + shlex.quote(adopted["artifact"])
+ + " && "
+ + shlex.quote(sys.executable)
+ + " continue.py",
+ }
class Provider(BaseHTTPRequestHandler):
def log_message(self, *args):
@@ -295,7 +379,8 @@ def do_POST(self):
requests.append(
json.loads(self.rfile.read(int(self.headers["Content-Length"])))
)
- if len(requests) == 1:
+ provider_phase["count"] += 1
+ if provider_phase["count"] == 1:
message = {
"type": "function_call",
"id": "fc_receiver",
@@ -303,11 +388,7 @@ def do_POST(self):
"name": "exec_command",
"arguments": json.dumps(
{
- "cmd": "cat "
- + shlex.quote(adopted["artifact"])
- + " && "
- + shlex.quote(sys.executable)
- + " continue.py",
+ "cmd": provider_phase["command"],
"max_output_tokens": 6000,
}
),
@@ -356,25 +437,22 @@ def do_POST(self):
server = ThreadingHTTPServer(("127.0.0.1", address.port), Provider)
thread = threading.Thread(target=server.serve_forever)
thread.start()
- try:
- logs = tmp_path / "work"
+
+ def run_native_work(current_guard, logs, purpose):
logs.mkdir()
wake = BenchmarkSessionWake(env, execution, {})
command = native_command(env, execution, "execute", logs, wake)
- command = [
- "/bin/bash",
- "-c",
- phase_shell
- + "exec timeout --signal=TERM --kill-after=30 ${remaining}s "
- + shlex.join(command),
- ]
result = subprocess.run(
- command,
+ [
+ "/bin/bash",
+ "-c",
+ phase_shell
+ + "exec timeout --signal=TERM --kill-after=30 ${remaining}s "
+ + shlex.join(command),
+ ],
env=env,
cwd=workspace,
- input=json.dumps(guard["interaction_contract"])
- + "\nThis is a new destination. Historical source commands/guards are not executable authority. "
- "Use the retained context under the current admission, and continue the synthetic candidate.",
+ input=json.dumps(current_guard["interaction_contract"]) + "\n" + purpose,
text=True,
capture_output=True,
timeout=60,
@@ -382,13 +460,33 @@ def do_POST(self):
assert result.returncode == 0, result.stderr
(logs / "stdout.jsonl").write_text(result.stdout)
wake.observe(logs / "stdout.jsonl")
+
+ try:
+ run_native_work(
+ guard,
+ tmp_path / "work",
+ "This is a new destination. Historical source commands/guards are not executable authority. "
+ "Use the retained context under the current admission, and continue the synthetic candidate.",
+ )
assert len(requests) == 2
- expected = "cooldown still blocks submission" if cooldown_pending else "candidate 8 graded"
- assert expected in json.dumps(requests[1]), json.dumps([
- item for item in requests[1].get("input", [])
- if item.get("type") == "function_call_output"
- ])
- assert len(grades) == (0 if cooldown_pending else 1) and candidate.read_text() == "8"
+ expected = (
+ "Restored state verified"
+ if stride_continuation
+ else "cooldown still blocks submission"
+ if cooldown_pending
+ else "candidate 8 graded"
+ )
+ assert expected in json.dumps(requests[1]), json.dumps(
+ [
+ item
+ for item in requests[1].get("input", [])
+ if item.get("type") == "function_call_output"
+ ]
+ )
+ assert len(grades) == (0 if stride_continuation or cooldown_pending else 1)
+ assert candidate.read_text() == (
+ captured_candidate if stride_continuation else "8"
+ )
retained = native_session_checkpoint(env, admitted["session_id"], timeout=30)[
"metadata"
]
@@ -410,9 +508,11 @@ def do_POST(self):
"outcome": "continue",
"prior_assumption": "Joint source point can be resumed.",
"observed_reality": (
- "Receiver used retained context and code; local candidate validated, native cooldown preserved."
- if cooldown_pending else
- "Receiver used retained context and code, and graded a new candidate."
+ "Receiver verified retained context and unchanged code; native opportunity preserved."
+ if stride_continuation
+ else "Receiver used retained context and code; local candidate validated, native cooldown preserved."
+ if cooldown_pending
+ else "Receiver used retained context and code, and graded a new candidate."
),
"retained": ["Complete qualification"],
"evidence_refs": ["evidence:joint-receiver"],
@@ -454,6 +554,387 @@ def do_POST(self):
).progress["state"]
== "settled"
)
+ if stride_continuation:
+ # An explicit one-Turn preparation cadence admits a fresh periodic
+ # review. Keep the original source's six work settlements inert.
+ review_turn = "joint-current-review"
+ from benchmark.runtime.checkpoint import capture_before_replan
+ from benchmark.runtime.checkpoint_continuation import restored_capture_gate
+ from benchmark.runtime.checkpoint_wake import CheckpointWake
+
+ wakes = tmp_path / "capture-logs" / "wakes"
+ (wakes / review_turn).mkdir(parents=True)
+ executable = tmp_path / "loopx-cli"
+ executable.write_text("#!/bin/sh\nexec " + shlex.quote(sys.executable) + ' -m loopx.cli "$@"\n')
+ executable.chmod(0o700)
+ declaration = tmp_path / "capture-request.json"
+ declaration.write_text(json.dumps({"trigger": "fresh_replan",
+ "destination": str(wakes.parent / "checkpoints"), "native_session": True,
+ "loopx_state": True, "components": [{"id": "code", "source_path": str(candidate)}],
+ "point_metadata": {}}))
+ env.update(LOOPX_CLI=str(executable), LOOPX_WAKE_LOG_DIR=str(wakes),
+ LOOPX_CHECKPOINT_REQUEST=str(declaration))
+ pending_receipt = {"mode": "heartbeat", "stage": "execute", "turn_id": review_turn}
+ pending_body = capture_before_replan(env, pending_receipt, "Current destination contract.")
+ assert pending_receipt["checkpoint"]["reason"] == "restored_replan_pending", pending_receipt
+ assert not pending_receipt["checkpoint"]["captured"]
+ assert "Original pending packet" in pending_body and "checkpoint:" + digest in pending_body
+ # Both stop locations share the same gate. Repeated/restarted
+ # capture attempts neither interrupt the fork nor publish a point.
+ for _ in range(2):
+ gate = restored_capture_gate(env)
+ assert not gate["capture_allowed"]
+ hook = CheckpointWake(env, dict(pending_receipt))
+ assert not hook._first_replan({"capture": True, "obligation_id": "replan-new-destination"})
+ assert not (wakes.parent / "checkpoints").exists()
+ reviewed = cli(
+ "quota",
+ "should-run",
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--turn-instance-id",
+ review_turn,
+ "--runtime-profile",
+ "generic_cli",
+ "--available-capability",
+ "shell",
+ )
+ assert reviewed["interaction_contract"]["mode"] == "autonomous_replan"
+ obligation = reviewed["autonomous_replan_obligation"]
+ assert any(
+ t["kind"] == "periodic_review_due" for t in obligation["triggers"]
+ ), obligation
+ reviewed = cli(
+ "quota",
+ "should-run",
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--todo-id",
+ todo,
+ "--turn-instance-id",
+ review_turn,
+ "--runtime-profile",
+ "generic_cli",
+ "--available-capability",
+ "shell",
+ )
+ control = reviewed["replan_action_packet"]["stride_control"]
+ decision = json.loads(json.dumps(control["minimal_example"]))
+ decision.update(
+ basis="supported_progress",
+ action="grow",
+ axes={
+ "work_slice": {
+ "plan": "Complete candidates 9 and 10 with local evidence in one work wake.",
+ "reason": "One real recovery check verified retained code, research context and public opportunity.",
+ },
+ "route_distance": {
+ "plan": "Retain the synthetic increment method.",
+ "reason": "This qualifies restoration and decision adoption, not scientific strategy quality.",
+ },
+ "validation_scope": {
+ "plan": "Check both candidate evidence files, final value 10 and unchanged native opportunity.",
+ "reason": "A complete larger work unit still needs its full postcondition.",
+ },
+ },
+ next_probe={
+ "scope": "One two-candidate synthetic continuation.",
+ "prediction": "Both candidates are validated locally; captured cooldown remains binding.",
+ "adoption_criterion": "Evidence files and final value agree without resetting opportunity.",
+ "cost_bound": "One native work wake under the original remaining deadline.",
+ "minimum_evidence": "Actual native tool output, candidate bytes, current settlement and original native history.",
+ },
+ )
+ if "explore_review" in decision:
+ decision["explore_review"].update(
+ disposition="no_applicable_evidence",
+ considered_refs=[],
+ reason="The fresh graph contains no findings; retained source context is inert evidence, not reactivated graph authority.",
+ )
+ identity = reviewed["heartbeat_receipt"]["settlement_identity"]
+ review_binding = (
+ [
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--turn-instance-id",
+ review_turn,
+ "--todo-id",
+ identity["todo_id"],
+ ]
+ if identity.get("todo_id")
+ else [
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--turn-instance-id",
+ review_turn,
+ "--replan-obligation-id",
+ identity["replan_obligation_id"],
+ ]
+ )
+ review_vision = tmp_path / "review-vision.json"
+ review_vision.write_text(
+ json.dumps(
+ {
+ "schema_version": "goal_vision_replan_contract_v0",
+ "state": "vision_patch_proposed",
+ "vision_patch": {
+ "acceptance_summary": "A current review must lead to a complete native experiment and preserve remaining opportunity."
+ },
+ "path_delta": {
+ "outcome": "replan",
+ "prior_assumption": "A restored local candidate is only a continuation prerequisite.",
+ "observed_reality": "An actual destination recovery check verified unchanged code, retained research and native opportunity.",
+ "changed": [
+ "Adopt a complete two-candidate work slice under current destination authority."
+ ],
+ "evidence_refs": ["evidence:restoration-check", "checkpoint:" + digest],
+ },
+ "stride_decision": decision,
+ }
+ )
+ )
+ cli(
+ "refresh-state",
+ *review_binding,
+ "--autonomous-replan-recorded",
+ "--classification",
+ "joint_stride_review",
+ "--delivery-outcome",
+ "outcome_progress",
+ "--agent-vision-json",
+ str(review_vision),
+ "--no-global-sync",
+ "--suppress-external-sinks",
+ )
+ # Interruption/lost delivery after the accepted writeback still
+ # owes this original Turn's settlement, so recapture stays closed.
+ unconsumed = restored_capture_gate(env)
+ assert not unconsumed["capture_allowed"]
+ original_packet = unit / "components" / metadata["held_decision_component_id"]
+ assert str(original_packet) in unconsumed["instructions"]
+ assert str(wakes / review_turn / "checkpoint-start-guard.json") not in unconsumed["instructions"]
+ cli(
+ "quota",
+ "spend-slot",
+ *review_binding,
+ "--source",
+ "heartbeat",
+ "--execute",
+ )
+ reopened = restored_capture_gate(env)
+ assert reopened["capture_allowed"], reopened
+ assert not reopened["instructions"] # Do not repeat the historical packet after consumption.
+ assert not restored_capture_gate(env, obligation["obligation_id"])["capture_allowed"]
+ configured = cli(
+ "configure-goal",
+ "--goal-id",
+ goal,
+ "--execution-replan-after-turns",
+ "6",
+ "--execute",
+ )
+ assert (
+ configured["after"]["execution_profile"]["replan_after_effective_turns"]
+ == 6
+ )
+ larger = todo
+ revision = cli("todo", "list", "--goal-id", goal, "--todo-id", todo)[
+ "authority_read"
+ ]["provider_revision"]
+ cli(
+ "todo",
+ "update",
+ "--goal-id",
+ goal,
+ "--todo-id",
+ todo,
+ "--agent-id",
+ agent,
+ "--text",
+ "Complete synthetic candidates 9 and 10 as one larger experiment and preserve native opportunity.",
+ "--update-operation-id",
+ "receiver-grow-task",
+ "--update-expected-provider-revision",
+ revision,
+ "--task-lease-idempotency-key",
+ "receiver-work",
+ "--task-lease-expected-version",
+ str(lease["lease"]["version"]),
+ )
+ next_turn = "wake-joint-larger-work"
+ next_binding = [
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--todo-id",
+ larger,
+ "--turn-instance-id",
+ next_turn,
+ ]
+
+ next_guard = cli(
+ "quota",
+ "should-run",
+ *next_binding,
+ "--runtime-profile",
+ "generic_cli",
+ "--available-capability",
+ "shell",
+ )
+ assert next_guard["interaction_contract"]["agent_channel"][
+ "delivery_allowed"
+ ]
+ assert "Complete synthetic candidates 9 and 10" in json.dumps(next_guard), (
+ next_guard
+ )
+ grow = workspace / "grow.py"
+ grow.write_text(
+ """import json,requests
+from pathlib import Path
+assert Path('candidate.txt').read_text()==CAPTURED_CANDIDATE
+b=json.loads(Path('judge.json').read_text()); url,submission_token=b['url'],b['token']
+for value in (9,10):
+ Path('candidate.txt').write_text(str(value))
+ assert int(Path('candidate.txt').read_text())==value
+ Path('candidate-evidence-'+str(value)+'.json').write_text(json.dumps({'value':value,'locally_validated':True}))
+r=requests.post(url+'/api/v1/submit',data={'token':submission_token},files={'archive':('candidate.tar.gz',b'10')},timeout=5)
+assert r.status_code==429
+h=requests.get(url+'/api/v1/history',params={'token':submission_token},timeout=5).json()
+assert h['agent_submissions']==1 and h['best_score']==7
+print('Current grow adopted: two complete candidates 9 and 10 validated; original cooldown and incumbent preserved.')
+ """.replace("CAPTURED_CANDIDATE", repr(captured_candidate))
+ )
+ assert cooldown_pending, (
+ "This qualification specifically requires the captured pending cooldown."
+ )
+ provider_phase.update(
+ count=0, command=shlex.quote(sys.executable) + " grow.py"
+ )
+ run_native_work(
+ next_guard,
+ tmp_path / "larger-work",
+ "Execute the current grow decision as one complete two-candidate experiment. Old guards are evidence only.",
+ )
+ assert len(requests) == 4 and "Current grow adopted" in json.dumps(
+ requests[3]
+ ), requests[3]
+ assert (
+ candidate.read_text() == "10"
+ and not grades
+ and phase.read_bytes() == original_phase
+ )
+ assert all(
+ json.loads((workspace / f"candidate-evidence-{v}.json").read_text())[
+ "value"
+ ]
+ == v
+ for v in (9, 10)
+ )
+ grown = native_session_checkpoint(env, admitted["session_id"], timeout=30)[
+ "metadata"
+ ]
+ assert grown["turn_ids"][:-2] == native["turn_ids"]
+ assert len(grown["turn_ids"]) == len(native["turn_ids"]) + 2
+ work_vision = tmp_path / "larger-vision.json"
+ work_vision.write_text(
+ json.dumps(
+ {
+ "schema_version": "goal_vision_replan_contract_v0",
+ "state": "vision_patch_proposed",
+ "vision_patch": {
+ "acceptance_summary": "Mechanism adoption is qualified; scientific outcome and full costs remain unproven."
+ },
+ "path_delta": {
+ "outcome": "continue",
+ "prior_assumption": "The larger decision may remain unused guidance.",
+ "observed_reality": "The continued native session executed both candidates with separate evidence and retained the pending cooldown and incumbent.",
+ "retained": [
+ "Native opportunity, immutable source and current authority separation."
+ ],
+ "evidence_refs": ["evidence:joint-larger-work"],
+ },
+ }
+ )
+ )
+ cli(
+ "refresh-state",
+ *next_binding,
+ "--classification",
+ "joint_stride_adoption",
+ "--delivery-batch-scale",
+ "implementation",
+ "--delivery-outcome",
+ "outcome_progress",
+ "--delivery-boundary",
+ "in_flight_continuation",
+ "--progress-result-class",
+ "advanced",
+ "--progress-surface-id",
+ "receiver:two-candidate-work",
+ "--progress-evidence-id",
+ "evidence:joint-larger-work",
+ "--agent-vision-json",
+ str(work_vision),
+ "--no-global-sync",
+ "--suppress-external-sinks",
+ )
+ cli(
+ "quota",
+ "spend-slot",
+ *next_binding,
+ "--source",
+ "heartbeat",
+ "--execute",
+ )
+ final = cli(
+ "quota",
+ "should-run",
+ "--goal-id",
+ goal,
+ "--agent-id",
+ agent,
+ "--turn-instance-id",
+ "joint-next-audit",
+ "--runtime-profile",
+ "generic_cli",
+ "--available-capability",
+ "shell",
+ )
+ assert final["interaction_contract"]["mode"] == "bounded_delivery"
+ # The next genuinely new decision boundary can capture changed
+ # research/code/native history, after actual useful continuation.
+ # This partial point deliberately makes no evaluator qualification.
+ prior_wake = wakes / next_turn
+ prior_wake.mkdir()
+ (prior_wake / "receipt.json").write_text(json.dumps({"turn_id": next_turn,
+ "mode": "heartbeat", "stage": "execute", "ok": True,
+ "session": {"binding_scope": "agent", "action": "resume", "session_id": admitted["session_id"]}}))
+ cli("configure-goal", "--goal-id", goal, "--execution-replan-after-turns", "1", "--execute")
+ boundary_turn = "joint-next-capture"
+ (wakes / boundary_turn).mkdir()
+ boundary = {"turn_id": boundary_turn, "mode": "heartbeat", "stage": "execute", "ok": False}
+ capture_before_replan(env, boundary, "Current new decision contract.")
+ if boundary["checkpoint"].get("reason") == "no_fresh_replan":
+ # Capture eligibility does not manufacture a quota obligation.
+ # This source may not have another due review yet.
+ assert boundary["checkpoint_restore"]["capture_allowed"]
+ else:
+ assert boundary["checkpoint"]["captured"], json.dumps(boundary, indent=2)
+ recaptured = Path(boundary["checkpoint"]["destination"])
+ assert (recaptured / "components/code").read_text() == "10"
+ next_metadata = json.loads((recaptured / "checkpoint.json").read_text())["point_metadata"]
+ assert next_metadata["replan"]["obligation_id"] != obligation["obligation_id"]
+ assert next_metadata["held_decision_component_id"]
+ assert next_metadata["native_session"]["turn_ids"] == grown["turn_ids"]
effect_runtime_result(
"runtime.checkpoint_artifacts",
{"action": "verify", "root": str(unit), "expected_sha256": digest},
diff --git a/benchmark/tests/test_checkpoint_qualification.py b/benchmark/tests/test_checkpoint_qualification.py
new file mode 100644
index 0000000000..b0e4eb04b7
--- /dev/null
+++ b/benchmark/tests/test_checkpoint_qualification.py
@@ -0,0 +1,22 @@
+"""Qualification must distinguish actual execution from pytest's zero exit."""
+import pytest
+
+from benchmark.tests.qualify_checkpoint_joint import require_one_pass
+
+
+@pytest.mark.parametrize("cases,passes", [
+ ('', True),
+ ('', False),
+ ('', False),
+ ('', False),
+ ('', False),
+ ('', False),
+])
+def test_qualification_requires_one_executed_success(tmp_path, cases, passes):
+ report = tmp_path / "junit.xml"
+ report.write_text(f"{cases}")
+ if passes:
+ require_one_pass(report)
+ else:
+ with pytest.raises(ValueError, match="actual pass and no skip"):
+ require_one_pass(report)
diff --git a/benchmark/tests/test_checkpoint_worker.py b/benchmark/tests/test_checkpoint_worker.py
index 404542a0fd..302d7efb37 100644
--- a/benchmark/tests/test_checkpoint_worker.py
+++ b/benchmark/tests/test_checkpoint_worker.py
@@ -2,6 +2,7 @@
from __future__ import annotations
import io
+import hashlib
import json
import os
import sqlite3
@@ -76,6 +77,30 @@ def counter(container):
return int(archive.extractfile("counter").read())
+def test_ordinary_controller_stages_root_private_native_configuration(writer, tmp_path):
+ from benchmark.tests.qualify_checkpoint_joint import _stage_source_configuration
+
+ assert os.getuid() != 0, "Qualify the ordinary controller, not a privileged reader"
+ container, _ = writer
+ raw = b'model = "fixture"\n'
+ command = ("from pathlib import Path; p=Path('/private-codex'); p.mkdir(mode=0o700); "
+ f"f=p/'config.toml'; f.write_bytes({raw!r}); f.chmod(0o600)")
+ assert container.exec_run(["python", "-c", command], user="root").exit_code == 0
+ assert container.exec_run(["stat", "-c", "%u %a", "/private-codex",
+ "/private-codex/config.toml"]).output == b"0 700\n0 600\n"
+ rejected = container.exec_run(["python", "-c",
+ "from pathlib import Path; Path('/private-codex/config.toml').read_bytes()"], user=str(os.getuid()))
+ assert rejected.exit_code != 0 and b"PermissionError" in rejected.output
+ native = {"guest_home": "/private-codex", "config_sha256": hashlib.sha256(raw).hexdigest()}
+ config = _stage_source_configuration(container, native, tmp_path / "staged")
+ assert config.read_bytes() == raw and config.stat().st_uid == os.getuid()
+ assert config.stat().st_mode & 0o777 == 0o600
+ with pytest.raises(ValueError, match="differs from the sealed checkpoint"):
+ _stage_source_configuration(container, {**native, "config_sha256": "f" * 64}, tmp_path / "wrong")
+ assert container.exec_run(["stat", "-c", "%u %a", "/private-codex",
+ "/private-codex/config.toml"]).output == b"0 700\n0 600\n"
+
+
def test_archive_keeps_private_directory_modes_without_special_bits(writer, tmp_path):
from benchmark.edgebench.checkpoint_worker import _archive
container, _ = writer
diff --git a/benchmark/tests/test_native_checkpoint_handoff.py b/benchmark/tests/test_native_checkpoint_handoff.py
index 529417f404..6f8e765432 100644
--- a/benchmark/tests/test_native_checkpoint_handoff.py
+++ b/benchmark/tests/test_native_checkpoint_handoff.py
@@ -350,7 +350,9 @@ def test_checkpoint_interrupt_timeout_reaps_without_qualifying_capture(tmp_path)
def test_midwake_native_profile_refusal_precedes_interruption(tmp_path, monkeypatch, platform, native, interrupt):
from benchmark.runtime.checkpoint_wake import CheckpointWake
- wake = CheckpointWake({"LOOPX_WAKE_LOG_DIR": str(tmp_path / "wakes")}, {"turn_id": "fixture-turn"})
+ wake = CheckpointWake({"LOOPX_WAKE_LOG_DIR": str(tmp_path / "wakes"),
+ "LOOPX_RUNTIME_ROOT": str(tmp_path / "runtime"), "LOOPX_GOAL_ID": "fixture",
+ "LOOPX_AGENT_ID": "researcher"}, {"turn_id": "fixture-turn"})
wake.request, wake.raw = {"native_session": native}, b"fixture declaration"
monkeypatch.setattr(sys, "platform", platform)
intent = {"capture": True, "obligation_id": "replan-0123456789abcdef"}
@@ -389,6 +391,10 @@ def test_actual_worker_captures_pending_replan_before_native_model(tmp_path, exp
logs = Path("/logs/agent/wakes")
candidate = project / "candidate.txt"
candidate.write_text("0")
+ git_object = project / ".git" / "objects" / "ab" / "retained-object"
+ git_object.parent.mkdir(parents=True)
+ git_object.write_text("Readonly source evidence")
+ git_object.chmod(0o444)
executable = tmp_path / "loopx-cli"
executable.write_text("#!/bin/sh\nexec " + shlex.quote(sys.executable) + ' -m loopx.cli "$@"\n')
executable.chmod(0o700)
@@ -501,7 +507,8 @@ def settle_work(result, expected):
assert len(set(work_turns)) == 6 and len(calls) == 13
request = tmp_path / "request.json"
request.write_text(json.dumps({"destination": str(logs.parent / "checkpoints"),
- "components": [{"id": "code", "source_path": str(candidate)}], "point_metadata": {},
+ "components": [{"id": "task_workspace", "source_path": str(project)}],
+ "point_metadata": {},
"trigger": "fresh_replan", "native_session": True, "loopx_state": True,
"evaluation_opportunity": True, "controller_handoff": True}))
phase.update(kind="review", tool_sent=False)
diff --git a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md
index a17c860fe5..13103ea9a9 100644
--- a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md
+++ b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.md
@@ -1008,6 +1008,70 @@ mid-wake native capture before signalling and releases the original tool respons
Linux interrupted and completed-wake capture retain their separate qualification;
strict history, ambiguity, source and deadline predicates remain unchanged.
+An explicit destination preparation can reuse the existing cadence owner for
+its first review: configure `--execution-replan-after-turns 1`, perform and settle
+one actual restoration check, then admit the newly due periodic review. That
+check reads retained research/code and public opportunity without changing the
+candidate or submitting it. The review uses current Goal/Todo/lease and fresh
+context identities; source settlements remain historical evidence. After the
+review settles, restore the preregistered normal cadence through
+`configure-goal` before admitting research work. The isolated native receiver
+qualification records independent grow axes, adopts one two-candidate complete
+slice and verifies unchanged deadline, pending cooldown, consumed rounds and
+incumbent. The default receiver path is exercised separately. The public
+[`qualify_checkpoint_joint.py`](../../../benchmark/tests/qualify_checkpoint_joint.py)
+entry reproduces the six-work-wake source with a non-root controller, then runs
+ordinary and stride receivers in separate fresh guests; the
+[benchmark procedure](../../../benchmark/README.md#controller-only-native-capsule-transport)
+pins the checkpoint provider, prepares guest dependencies and lists explicit
+native CLI/Node inputs. Missing prerequisites fail rather than skip qualification.
+Its private receipt and logs retain input provenance; read-only Git members
+exercise the controller boundary. The source now declares its whole workspace
+directory with a caller-owned component ID. Non-root receivers select that
+directory by the sealed original workspace path and use the typed materialization
+owner; retained source registry/state files remain historical beside a fresh
+external registry/runtime. Admission of the source Goal through the fresh registry
+is rejected, while retained evidence and actual candidate writes remain usable.
+This qualifies the directory/writer seam, not a real task dataset or scientific
+continuation result. This is an
+explicit tested composition, not an automatic restore hook or a new default.
+A Todo-successor repair alone is not a spendable periodic strategy review.
+Matched branches must declare the same preparation/cadence transition and charge
+its full cost. Scripted native-provider execution qualifies transport and
+mechanical adoption, not model judgment, restored Explore graph authority or
+scientific benefit; later-point selection and real branch admission remain open.
+
+Re-enabling capture on a restored conversation requires a destination decision,
+not merely a new Goal/Agent or a locally unseen replan ID. Both pre-model and
+mid-wake hooks refuse capture until the canonical current-Goal settlement
+contains an accepted semantic replan whose Vision evidence references
+`checkpoint:`, and the destination native history has completed
+a continuation with the original full history prefix intact. The consumed
+obligation itself remains ineligible; a subsequent genuinely new node can be
+captured. Retry, restart and a lost admission response preserve the original
+source-root/digest binding. This is a machine-enforced capture barrier, not
+additional execution or budget authority.
+
+Completed pre-model points now retain the original pending guard as a sealed
+participant, as interrupted points already do. The pending packet is read as
+historical decision evidence under the destination's current guard; source
+commands, claims, identities and effects must not execute again. Older pending
+points without that participant cannot re-enable capture. After consumption the
+Host stops repeating this context. Continuation reads the digest-bound manifest
+basis and verifies the particular pending packet and native history it uses;
+it does not rehash large code/incumbent archives every work wake or claim that
+this limited read is fresh full-member verification. Native admission still
+requires full artifact verification. Capture alone never automatically restores
+a branch. An external restore-every-new-point orchestrator must exercise the same
+barrier and current admission; its existence or successful scientific outcome
+does not follow from this receiver qualification.
+
+The artifact copy also preserves read-only file modes without requesting write
+access merely to sync the copied bytes. A non-root controller capture of a
+`0444` participant failed before this fix and now succeeds; Git object stores
+are an ordinary source of such members. Root-only container qualification was
+insufficient for this Host boundary. Source permissions and bytes remain unchanged.
+
Committed native session admission now retains the captured artifact digest in
the original atomic session binding. Exact controller retries after a lost binding
reply verify that receipt, current Goal/Agent/profile and unchanged complete
diff --git a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md
index 063a397057..be7d9ed88c 100644
--- a/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md
+++ b/docs/architecture/rfcs/hierarchical-agent-stride-control-v0.zh-CN.md
@@ -817,6 +817,50 @@ pending command;延迟重读及仅向主进程发信号均未恢复。因此
前明确拒绝中途 native 捕获,继续原工具响应。Linux interrupted 与 completed-wake
路径分别保留验收,不放宽历史、歧义、源或期限检查。
+目标侧显式恢复准备可复用既有 cadence owner 来安排首次复盘:配置
+`--execution-replan-after-turns 1`,完成并结算一次真实恢复检查,再准入新到期的
+periodic review。检查读取保留的研究/代码与公开评测机会,不改候选、不提交。
+复盘使用当前 Goal/Todo/lease 及新 context 身份;源工作结算仅作历史证据。
+复盘结算后,通过 `configure-goal` 恢复预注册的正常频率,再准入研究工作。
+隔离的 native receiver 验证已记录三个轴独立计划的 grow,实际采用一次包含
+两个候选的完整工作切片,并检查 deadline、未到期冷却、已用轮次和 incumbent
+不变;默认 receiver 路径单独验证。公开的
+[`qualify_checkpoint_joint.py`](../../../benchmark/tests/qualify_checkpoint_joint.py)
+入口先由非 root controller 捕获六个真实工作 wake 后的源状态,再在两个全新
+guest 分别运行普通和 stride receiver;
+[benchmark 操作步骤](../../../benchmark/README.md#controller-only-native-capsule-transport)
+固定 checkpoint provider、准备 guest 依赖并列出显式 Codex/Node 输入。
+缺少前置条件会报错,不以 skip 充当资格通过。私有 receipt 与日志保留输入来源,
+只读 Git 对象覆盖 controller 的权限边界。
+源声明整个 workspace 目录并保留调用者的 component ID;非 root receiver 按
+封存的原 workspace 路径定位,由类型化 owner 物化。复制的源 registry/state
+只是历史,新外置 registry/runtime 拒绝原 Goal 准入,同时保留证据并完成候选
+写入。这资格化目录与写入者衔接,不证明真实题目数据集或科研接续收益。
+这是显式且经测试的组合,不是恢复时自动
+触发的 hook,也不改变默认行为。补后继 Todo 的生命周期修复本身不等于可记账的
+periodic 策略复盘。匹配分支须声明相同的准备/频率迁移,并计入完整成本。
+脚本式 native provider 验证传输与机械采用,不证明模型判断、源 Explore graph
+权限恢复或科研收益;中后期研究点选择与真实分支准入仍待完成。
+
+恢复会话重新开启捕获须完成目标侧决策,换一个 Goal/Agent 或本地尚未见过的
+replan ID 都不够。模型前与 wake 中两处 hook 共用门槛:当前 Goal 的 canonical
+结算须接受一个语义 replan,其 Vision 证据引用 `checkpoint:`,
+且目标原生历史已完成接续、保留原完整历史前缀。刚消费的义务本身仍不可捕获;
+后续真正的新节点才可捕获。重试、重启或准入回复丢失保留原 source-root/digest
+绑定。这是机器执行的捕获门槛,不增加执行或预算权限。
+
+模型前的 completed 点现在也将原 pending guard 作为封存参与方,中断点已有
+此参与方。目标在当前 guard 下将原包读作历史决策依据,不重跑源命令、claim、
+身份与 effect。旧 pending 点缺少此参与方时不能重新开启捕获。消费后 Host 不再
+重复注入这段上下文。接续读取 digest 绑定的 manifest 依据,核验实际使用的
+pending 包与原生历史,不在每个工作 wake 重算大型代码/incumbent archive 的哈希,
+也不把这个有限读取称为新的全成员验证;原生准入仍须完整验证产物。捕获本身
+不自动恢复分支。外层“每个新点都恢复”的编排也须通过同一门槛与当前准入;
+其是否存在以及科研收益不能由本 receiver 验证推导。
+
+产物复制保留只读文件模式,同步复制的字节时不额外索取写权限。非 root 控制器
+捕获 `0444` 参与方此前失败,修复后通过;Git object store 是这类成员的常见来源。
+仅在 root 容器中验证不足以覆盖此 Host 边界。源权限和字节保持不变。
inert verification unit 不授予执行权。目标侧使用新的实验/Host 身份,通过既有
Goal/session/state admission 进入。历史租约、quota、timer、pending effect 和
diff --git a/loopx/control_plane/runtime/checkpoint_artifacts.ts b/loopx/control_plane/runtime/checkpoint_artifacts.ts
index fc799a856e..21ecfbefab 100644
--- a/loopx/control_plane/runtime/checkpoint_artifacts.ts
+++ b/loopx/control_plane/runtime/checkpoint_artifacts.ts
@@ -7,7 +7,7 @@ import {createReadStream} from "node:fs";
import {copyFile, lstat, mkdir, mkdtemp, open, readFile, readdir, rm} from "node:fs/promises";
import {dirname, isAbsolute, join, resolve, sep} from "node:path";
import type {JsonObject} from "../effect_program.ts";
-import {requireJsonObject, requireNonEmptyString} from "../runtime_decode.ts";
+import {jsonObject, requireJsonObject, requireNonEmptyString} from "../runtime_decode.ts";
import {canonicalAuthoritySha256, hasExactAuthorityKeys} from "../coordination/authority_store_codec.ts";
import {snapshotSqliteDatabase} from "../coordination/sqlite_backup.ts";
import {durableWriteJson, publishNewDirectory} from "../effect_runtime_io.ts";
@@ -15,6 +15,7 @@ import {settlementIdentity} from "../effect_program.ts";
import {normalizeHeartbeatReplanObligationId, normalizeHeartbeatTodoId} from "../quota/heartbeat_receipt_identity.ts";
import {EffectRuntimeConflictError} from "../effect_runtime_errors.ts";
import {EffectiveAction} from "../quota/effective_action.generated.ts";
+import {BARE_SHA256_PATTERN} from "../content_digest.ts";
const SCHEMA = "loopx_checkpoint_artifact_set_v0";
type Component = {id: string; source_path: string};
@@ -134,7 +135,9 @@ async function copyComponent(source: string, destination: string, snapshotDataba
// SQLite's existing physical snapshot owner seals committed WAL contents.
if (snapshotDatabases && await sqliteFile(source)) await snapshotSqliteDatabase(source, destination);
else await copyFile(source, destination);
- const handle = await open(destination, "r+");
+ // copyFile preserves a read-only participant's mode. Syncing its bytes
+ // needs no write access; r+ fails for an ordinary non-root controller.
+ const handle = await open(destination, "r");
try {await handle.sync();} finally {await handle.close();}
} else throw new Error("checkpoint source contains an unsupported file type");
}
@@ -166,6 +169,42 @@ async function verify(root: string, expected: unknown): Promise {
}
export async function checkpointArtifacts(value: unknown): Promise {
const request = requireJsonObject(value, "checkpoint artifacts request");
+ if (request.action === "restore_capture") {
+ if (!hasExactAuthorityKeys(request, ["action", "checkpoint_sha256", "goal_id", "agent_id", "current_obligation_id", "review"])) {
+ throw new Error("checkpoint restored capture fields are invalid");
+ }
+ const sha = requireNonEmptyString(request.checkpoint_sha256, "restored checkpoint digest");
+ if (!BARE_SHA256_PATTERN.test(sha)) throw new Error("invalid restored checkpoint digest");
+ const goal = requireNonEmptyString(request.goal_id, "checkpoint destination Goal");
+ const agent = requireNonEmptyString(request.agent_id, "checkpoint destination Agent");
+ if (request.current_obligation_id !== null && !normalizeHeartbeatReplanObligationId(request.current_obligation_id)) {
+ throw new Error("restored checkpoint current replan identity is invalid");
+ }
+ const review = jsonObject(request.review), identity = jsonObject(review?.identity);
+ const progress = jsonObject(review?.progress), run = jsonObject(review?.writeback_run);
+ const guard = jsonObject(review?.semantic_replan_guard), ack = jsonObject(run?.autonomous_replan_ack);
+ const delta = jsonObject(ack?.semantic_delta), path = jsonObject(jsonObject(run?.agent_vision)?.path_delta);
+ const id = normalizeHeartbeatReplanObligationId(delta?.obligation_id);
+ const todo = normalizeHeartbeatTodoId(identity?.todo_id);
+ const boundReplan = normalizeHeartbeatReplanObligationId(identity?.replan_obligation_id);
+ // The adapter must obtain these records from the existing canonical
+ // settlement owner. Copied source runs, slot debit alone, a neutral ACK or
+ // the still-pending decision being captured cannot release this barrier.
+ const accepted = !!identity && !!run && identity.goal_id === goal && identity.agent_id === agent &&
+ run.goal_id === goal && run.agent_id === agent && run.turn_instance_id === identity.turn_instance_id &&
+ normalizeHeartbeatTodoId(run.todo_id) === todo &&
+ normalizeHeartbeatReplanObligationId(run.replan_obligation_id) === boundReplan &&
+ typeof identity.turn_instance_id === "string" && identity.turn_instance_id.length > 0 &&
+ identity.effect_id === settlementIdentity({goal_id: goal, agent_id: agent, todo_id: todo,
+ replan_obligation_id: boundReplan, turn_instance_id: identity.turn_instance_id}).effect_id &&
+ progress?.state === "settled" && ack?.schema_version === "autonomous_replan_ack_v0" && ack.recorded === true &&
+ delta?.schema_version === "replan_semantic_delta_v0" && delta.accepted === true && !!id &&
+ guard?.selected_obligation_id === id && request.current_obligation_id !== id &&
+ Array.isArray(path?.evidence_refs) && path.evidence_refs.includes(`checkpoint:${sha}`);
+ return {capture_allowed: accepted, reason: accepted ? "restored_replan_completed" : "restored_replan_pending",
+ ...(accepted ? {review_obligation_id: id} : {}),
+ execution_authority_granted: false};
+ }
if (request.action === "select_replan") {
if (!hasExactAuthorityKeys(request, ["action", "quota", "goal_id", "agent_id", "turn_instance_id"])) {
throw new Error("checkpoint replan selection fields are invalid");
@@ -209,6 +248,17 @@ export async function checkpointArtifacts(value: unknown): Promise {
return {capture: true, obligation_id: id, settlement_effect_id: identity.effect_id,
execution_authority_granted: false};
}
+ if (request.action === "read_basis") {
+ if (!hasExactAuthorityKeys(request, ["action", "root", "expected_sha256"])) throw new Error("checkpoint basis fields are invalid");
+ const root = absolute(request.root, "checkpoint root");
+ await physical(join(root, "checkpoint.json"));
+ const manifest = manifestBody(JSON.parse(await readFile(join(root, "checkpoint.json"), "utf8")));
+ if (manifest.sha256 !== request.expected_sha256) throw new Error("checkpoint reviewed digest mismatch");
+ // This reads a digest-bound declaration after admission's full member
+ // verification. It does not claim current integrity of every component.
+ return {point_metadata: manifest.point_metadata, components: manifest.components,
+ integrity_verified: false, execution_authority_granted: false};
+ }
const fields = request.action === "probe" ? ["action", "components"] :
request.action === "verify" ? ["action", "root", "expected_sha256"] :
request.action === "capture" ? ["action", "components", "destination", "point_metadata", "expected_source_sha256"] :
diff --git a/loopx/control_plane/turn_driver/codex_sessions.py b/loopx/control_plane/turn_driver/codex_sessions.py
index 7381765d09..cf89b51682 100644
--- a/loopx/control_plane/turn_driver/codex_sessions.py
+++ b/loopx/control_plane/turn_driver/codex_sessions.py
@@ -177,9 +177,14 @@ def _store_codex_cli_session(
operation_reasoning_effort: str | None = None,
require_absent: bool = False,
checkpoint_artifact_sha256: str | None = None,
+ checkpoint_artifact_root: str | None = None,
) -> None:
if checkpoint_artifact_sha256 is not None and not re.fullmatch(r"[0-9a-f]{64}", checkpoint_artifact_sha256):
raise ValueError("checkpoint session source digest is invalid")
+ if checkpoint_artifact_root is not None and (
+ checkpoint_artifact_sha256 is None or not Path(checkpoint_artifact_root).is_absolute() or
+ str(Path(checkpoint_artifact_root).resolve()) != checkpoint_artifact_root):
+ raise ValueError("checkpoint session source root must be physical and digest-bound")
path = _session_path(runtime_root, lineage, session_scope)
with exclusive_file_lock(path):
if require_absent and (path.exists() or path.is_symlink()):
@@ -188,15 +193,22 @@ def _store_codex_cli_session(
# but cannot migrate to a replacement session, profile or Goal lifetime.
prior = load_codex_cli_session(runtime_root, lineage=lineage, session_scope=session_scope)
retained_source = None
+ retained_root = None
if prior and (prior.get("session_id"), prior.get("goal_ref"), prior.get("session_profile_digest")) == (
session_id, dict(goal_ref) if goal_ref is not None else None, session_profile_digest
):
retained_source = prior.get("checkpoint_artifact_sha256")
+ retained_root = prior.get("checkpoint_artifact_root")
if retained_source is not None:
if not isinstance(retained_source, str) or not re.fullmatch(r"[0-9a-f]{64}", retained_source):
raise ValueError("stored checkpoint session source digest is invalid")
if checkpoint_artifact_sha256 not in (None, retained_source):
raise ValueError("checkpoint session source digest cannot change")
+ if retained_root is not None and (not isinstance(retained_root, str) or
+ not Path(retained_root).is_absolute() or str(Path(retained_root).resolve()) != retained_root):
+ raise ValueError("stored checkpoint session source root is invalid")
+ if retained_root is not None and checkpoint_artifact_root not in (None, retained_root):
+ raise ValueError("checkpoint session source root cannot change")
_write_codex_cli_session(
runtime_root,
lineage=lineage,
@@ -208,6 +220,7 @@ def _store_codex_cli_session(
operation_model=operation_model,
operation_reasoning_effort=operation_reasoning_effort,
checkpoint_artifact_sha256=checkpoint_artifact_sha256 or retained_source,
+ checkpoint_artifact_root=checkpoint_artifact_root or retained_root,
)
@@ -223,6 +236,7 @@ def _write_codex_cli_session(
operation_model: str | None = None,
operation_reasoning_effort: str | None = None,
checkpoint_artifact_sha256: str | None = None,
+ checkpoint_artifact_root: str | None = None,
) -> None:
normalized_session_id = _valid_session_id(session_id)
if not normalized_session_id:
@@ -253,6 +267,8 @@ def _write_codex_cli_session(
payload["goal_ref"] = dict(goal_ref)
if checkpoint_artifact_sha256 is not None:
payload["checkpoint_artifact_sha256"] = checkpoint_artifact_sha256
+ if checkpoint_artifact_root is not None:
+ payload["checkpoint_artifact_root"] = checkpoint_artifact_root
if operation_profile_digest is not None:
payload["operation_transport"] = "app-server-operation-tools-v0"
payload["operation_profile_digest"] = operation_profile_digest
diff --git a/tests/control_plane/test_checkpoint_restore_capture.py b/tests/control_plane/test_checkpoint_restore_capture.py
new file mode 100644
index 0000000000..ff05b25b2a
--- /dev/null
+++ b/tests/control_plane/test_checkpoint_restore_capture.py
@@ -0,0 +1,70 @@
+"""A restored fork needs a current, settled semantic decision before recapture."""
+import copy
+
+import pytest
+
+from loopx.control_plane.effect_program import SettlementIdentity
+from loopx.control_plane.effect_runtime import effect_runtime_result
+
+
+@pytest.mark.parametrize("fault", [None, "pending", "neutral", "source_actor", "unlinked", "same_node", "wrong_binding"])
+def test_restored_capture_requires_current_consumption_not_local_dedup(fault):
+ sha = "a" * 64
+ identity = SettlementIdentity("destination", "researcher", "todo_probe", "decision-1").as_dict()
+ obligation = "replan-" + "1" * 16
+ review = {"identity": identity, "progress": {"state": "settled"},
+ "semantic_replan_guard": {"schema_version": "semantic_replan_guard_v0", "selected_obligation_id": obligation},
+ "writeback_run": {"goal_id": "destination", "agent_id": "researcher", "todo_id": "todo_probe",
+ "turn_instance_id": "decision-1", "autonomous_replan_ack": {
+ "schema_version": "autonomous_replan_ack_v0", "recorded": True,
+ "semantic_delta": {"schema_version": "replan_semantic_delta_v0", "accepted": True,
+ "obligation_id": obligation}},
+ "agent_vision": {"path_delta": {"evidence_refs": ["checkpoint:" + sha]}}}}
+ current = "replan-" + "2" * 16
+ if fault == "pending":
+ review["progress"]["state"] = "settlement_pending"
+ elif fault == "neutral":
+ review["writeback_run"]["autonomous_replan_ack"]["semantic_delta"]["accepted"] = False
+ elif fault == "source_actor":
+ review["writeback_run"]["goal_id"] = "source"
+ elif fault == "unlinked":
+ review["writeback_run"]["agent_vision"]["path_delta"]["evidence_refs"] = ["checkpoint:" + "b" * 64]
+ elif fault == "same_node":
+ current = obligation
+ elif fault == "wrong_binding":
+ review["writeback_run"]["todo_id"] = "todo_other"
+ request = {"action": "restore_capture", "checkpoint_sha256": sha,
+ "goal_id": "destination", "agent_id": "researcher", "current_obligation_id": current,
+ "review": review}
+ result = effect_runtime_result("runtime.checkpoint_artifacts", request)
+ assert result["capture_allowed"] is (fault is None)
+ assert result["execution_authority_granted"] is False
+ # A fresh Goal/Agent cannot inherit even a valid destination review. This
+ # is the outer restore-every-point counterexample to local-ID dedup.
+ another = copy.deepcopy(request)
+ another.update(goal_id="another-destination", agent_id="another-researcher")
+ assert not effect_runtime_result("runtime.checkpoint_artifacts", another)["capture_allowed"]
+
+
+def test_readonly_participant_capture_and_limited_basis_read(tmp_path):
+ source = tmp_path / "code"
+ source.write_text("retained evidence")
+ source.chmod(0o444)
+ components = [{"id": "code", "source_path": str(source)}]
+ probe = effect_runtime_result("runtime.checkpoint_artifacts", {"action": "probe", "components": components})
+ unit = tmp_path / "point"
+ captured = effect_runtime_result("runtime.checkpoint_artifacts", {"action": "capture", "components": components,
+ "destination": str(unit), "point_metadata": {}, "expected_source_sha256": probe["source_sha256"]}, retry_safe=False)
+ copied = unit / "components" / "code"
+ assert copied.read_text() == source.read_text() and copied.stat().st_mode & 0o777 == 0o444
+ copied.chmod(0o644)
+ copied.write_text("changed member")
+ # Digest-bound declaration readback is explicitly weaker than full member
+ # verification. Native admission must continue using the latter.
+ basis = effect_runtime_result("runtime.checkpoint_artifacts", {"action": "read_basis", "root": str(unit),
+ "expected_sha256": captured["sha256"]})
+ assert basis["integrity_verified"] is False and basis["execution_authority_granted"] is False
+ from loopx.control_plane.effect_runtime import EffectRuntimeRemoteError
+ with pytest.raises(EffectRuntimeRemoteError):
+ effect_runtime_result("runtime.checkpoint_artifacts", {"action": "verify", "root": str(unit),
+ "expected_sha256": captured["sha256"]})
diff --git a/tests/control_plane_ts/content_digest_single_owner.test.ts b/tests/control_plane_ts/content_digest_single_owner.test.ts
index 8d049af41f..25c24a4179 100644
--- a/tests/control_plane_ts/content_digest_single_owner.test.ts
+++ b/tests/control_plane_ts/content_digest_single_owner.test.ts
@@ -132,6 +132,7 @@ const CANONICAL_CONSUMERS = [
"control_plane/presentation/action_review_plan.ts",
"control_plane/quota/refresh_external_delivery.ts",
"control_plane/quota/turn_envelope.ts",
+ "control_plane/runtime/checkpoint_artifacts.ts",
"control_plane/runtime/execution_identity.ts",
"control_plane/runtime/usage_statistics_cycles.ts",
"control_plane/runtime/usage_statistics_goal_contract.ts",