diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b0cd713b6..ef7cf0ea2f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,6 +189,7 @@ touch, mention it in the PR instead of fixing it there. | `postgresql-integration.yml` | control-plane or npm lockfile paths | no | PostgreSQL authority store and service on a temporary instance | | `package-smoke.yml` | extension package paths | no | extension packages install, entrypoints, and example schemas | | `release-artifacts.yml` | `loopx/`, packaging, and lockfile paths | no | release identity and a release build from this source | +| `runtime-snapshots.yml` | runtime artifact, installer, and updater paths | no | qualified, revision-addressed runtime archives and the main channel pointer | | `ark-turn.yml` | Turn driver and collaboration paths | no | optional Ark Turn package, stdio MCP, and DSH parity | | `memt-turn.yml` | optional MemT package, Turn driver, lock, and packaging paths | no | explicit optional dependency installation, complete adapter workflow, stdio MCP, and driver binding | | `dsh-plugin.yml` | DSH plugin and distribution workflow paths | no | Linux/Windows package install and uninstall, typed host contracts, and Linux real LoopX/web runtime admission | diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.md index 6a4b46ff83..53ff3618a7 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.md @@ -302,6 +302,16 @@ records both edges. Existing Ark/JEV private edges remain migration debt and receive no new exemption. The facade forwards existing behavior; it must not copy candidate, acceptance, process-lifetime or locking decisions. +The independently installed Codex provider-routing host uses +`loopx.extensions.chat_service_guard` for its existing authorized restart of +managed Chat consumers. The facade exports only `managed_chat_activation_guard` +and `RuntimeActivationDeferred` from the current runtime activation owner. +Accepted-work observation, exact service identity and the ingress lock stay +with that owner; the package neither reads private Goal state nor copies the +decision. Missing or unsupported Core defers the service restart. The no-service +path remains independent of Core. This one supported public edge is pinned; +it grants no authority to stop a service or add other kernel imports. + The stdio relay cancels its owned process group on parent EOF, SIGTERM or SIGINT and waits for the existing supervisor's cleanup before exiting. POSIX process-group behavior and Windows best-effort tree cleanup retain their diff --git a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md index 9d64e1d4d2..caf9dc4c0d 100644 --- a/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md +++ b/docs/architecture/rfcs/monorepo-distribution-split-v0.zh-CN.md @@ -208,6 +208,14 @@ MemT 只使用这两个公开模块,不能直接 import executor 或其他内 边。Ark/JEV 的既有私有边仍是迁移积压,不新增豁免。facade 只转发现有行为, 不能复制候选、验收、进程生命周期或文件锁决策。 +独立安装的 Codex provider-routing host 通过 +`loopx.extensions.chat_service_guard` 接入其已有、已获授权的托管 Chat consumer +重启。facade 只转出现有 runtime activation owner 的 +`managed_chat_activation_guard` 与 `RuntimeActivationDeferred`。已接受工作观察、 +准确服务身份和 ingress lock 仍由该 owner 负责;包不读取私有 Goal 状态, +也不复制决策。Core 缺失或不支持时延期重启;没有托管服务的路径仍不依赖 Core。 +import ratchet 只记录这一条受支持公开边;它不授予停止服务或增加其他内核 import 的权限。 + stdio relay 在父进程 EOF、SIGTERM 或 SIGINT 时取消所持进程组,并等待已有 监督器完成回收再退出。POSIX 进程组行为与 Windows 尽力而为的进程树回收仍沿用 原资格边界。这是进程监督,不是沙箱;操作者选择的工具只保有原先获准的效果。 diff --git a/examples/blocker-push-runtime-smoke.py b/examples/blocker-push-runtime-smoke.py index 58636449e4..14ba2be387 100644 --- a/examples/blocker-push-runtime-smoke.py +++ b/examples/blocker-push-runtime-smoke.py @@ -220,7 +220,7 @@ def main() -> int: # current compact sentence instead of the retired per-shell phrasing. assert "reuse the value on retries" in compact_prompt, prompt assert "obey projected replan/stop, never wake counts" in compact_prompt, prompt - assert "re-enter guard." in compact_prompt, prompt + assert "re-enter as instructed, do/verify authorized work." in compact_prompt, prompt assert "2 stalls->replan" not in compact_prompt, prompt assert "Exact monitor settlement=no refresh/spend" in compact_prompt, prompt assert "Admitted work: settlement_plan even if artifacts unchanged" in compact_prompt, prompt diff --git a/examples/control_plane/heartbeat_prompt_fixtures.py b/examples/control_plane/heartbeat_prompt_fixtures.py index d952154164..8fbb92747e 100644 --- a/examples/control_plane/heartbeat_prompt_fixtures.py +++ b/examples/control_plane/heartbeat_prompt_fixtures.py @@ -39,10 +39,9 @@ def normalized(text: str) -> str: def prompt_budget_text(text: str) -> str: - return text.replace(GOAL_ID, "").replace( - str(ACTIVE_STATE), - "", - ) + # Normalize the complete path before its Goal substring, matching the + # independently declared authored-guidance measurement contract. + return text.replace(str(ACTIVE_STATE), "").replace(GOAL_ID, "") def assert_prompt_budget(label: str, text: str) -> None: diff --git a/examples/export-frontstage-share-bundle.mjs b/examples/export-frontstage-share-bundle.mjs index 5e67d4eb95..e1eb653f74 100644 --- a/examples/export-frontstage-share-bundle.mjs +++ b/examples/export-frontstage-share-bundle.mjs @@ -131,7 +131,11 @@ async function copyHomepage(siteDir, base) { await cp(buildDir, siteDir, { force: true, recursive: true }); await rm(buildDir, { force: true, recursive: true }); await copyFile(resolve(repoRoot, installerScriptPath), resolve(siteDir, "install.sh")); - await copyFile(resolve(repoRoot, "loopx/cli_commands/update_artifact.py"), resolve(siteDir, "update-artifact.py")); + const bootstrap = run(resolveTestPython(), ["-c", + "from loopx.cli_commands.update_artifact import standalone_source; print(standalone_source(), end='')"], { + capture: true, cwd: repoRoot, + }); + await writeFile(resolve(siteDir, "update-artifact.py"), bootstrap, "utf8"); } async function copyPublicSiteRoutes(siteDir) { diff --git a/examples/install-local-smoke.py b/examples/install-local-smoke.py index 06d5c18071..a881324a06 100644 --- a/examples/install-local-smoke.py +++ b/examples/install-local-smoke.py @@ -57,7 +57,7 @@ def normal_turns_use_cli_interaction_contract(task_body: str) -> bool: def read_installed_workflow_skill( skill: Path, *, references: set[str] ) -> dict[str, str]: - """Read the installed entrypoint and its complete, self-contained procedures.""" + """Read installed procedures and verify links stay in the declared bundle.""" skill_root = skill.parent.resolve() documents = {"SKILL.md": skill.read_text(encoding="utf-8")} linked_references = { @@ -81,8 +81,12 @@ def read_installed_workflow_skill( if "://" in destination or destination.startswith("#"): continue resource = (document.parent / destination.split("#", 1)[0]).resolve() - assert resource.is_relative_to(skill_root), (document, destination) + bundle_root = skill_root.parent + assert resource.is_relative_to(bundle_root), (document, destination) + bundled_path = resource.relative_to(bundle_root) + assert bundled_path.parts[0] in {"loopx", *PACKAGED_HOST_SKILL_IDS}, (document, destination) assert resource.is_file(), (document, destination) + assert resource.read_bytes() == (REPO_ROOT / "skills" / bundled_path).read_bytes(), resource return documents @@ -917,7 +921,7 @@ def main() -> int: assert "```sh\nLOOPX_TURN=\n" in payload["task_body"], payload assert "not a command-prefix assignment" in payload["task_body"], payload assert "obey projected replan/stop, never wake counts" in payload["task_body"], payload - assert "re-enter guard." in payload["task_body"], payload + assert "re-enter as instructed, do/verify authorized work." in payload["task_body"], payload assert "2 stalls->replan" not in payload["task_body"], payload assert "Exact monitor settlement=no refresh/spend" in payload["task_body"], payload assert "Admitted work: settlement_plan even if artifacts unchanged" in payload["task_body"], payload diff --git a/loopx/chat_lark_api.py b/loopx/chat_lark_api.py index 5b171737bb..1fa689ac0b 100644 --- a/loopx/chat_lark_api.py +++ b/loopx/chat_lark_api.py @@ -1,7 +1,7 @@ from __future__ import annotations import subprocess -from collections.abc import Mapping +from collections.abc import Callable, Mapping from pathlib import Path from typing import TYPE_CHECKING, Any from urllib.parse import parse_qs, urlparse @@ -46,6 +46,28 @@ from .repository_identity import normalize_repository_identity +def manager_source_scope_resolver( + server: Any, *, registry_path: Path, executor_runtime_root: Path, +) -> Callable[[dict[str, Any]], list[str] | None]: + """Keep the default manager's native and legacy source routing together.""" + from .extensions.lark.manager_routing import authorized_manager_goal_ids + + def read(session): + controller = server.runtime_controller + if isinstance(session.get("steward_context"), dict): + bindings = controller.project_contexts.conversation_bindings + if bindings is None: + raise ValueError("Steward conversation bindings are unavailable") + return bindings.steward_scope(session) + return authorized_manager_goal_ids( + build_lark_goal_topic_runtime_snapshot( + registry_path=server.registry_path, runtime_root_override=server.runtime_root_override, + ), session, runtime_root=controller.coordination_runtime_root, + registry_path=registry_path, executor_runtime_root=executor_runtime_root, + ) + return read + + def _compact_text(value: Any, *, limit: int = 600) -> str: return " ".join(str(value or "").split())[:limit].strip() diff --git a/loopx/chat_server.py b/loopx/chat_server.py index 58b2088fcf..0e30d2e13b 100644 --- a/loopx/chat_server.py +++ b/loopx/chat_server.py @@ -1921,7 +1921,8 @@ def serve_chat( ) server.chat_store = ChatSessionStore(runtime_root) server.action_store = ChatActionStore(runtime_root / "chat" / "actions") - from .presentation.request_control_api import bound_steward_inventory_scope, manager_source_scope_resolver + from .presentation.request_control_api import bound_steward_inventory_scope + from .chat_lark_api import manager_source_scope_resolver manager_scope = manager_source_scope_resolver(server, registry_path=resolved_registry_path, executor_runtime_root=runtime_root) diff --git a/loopx/cli_commands/quota.py b/loopx/cli_commands/quota.py index 32be13a96d..f2731a0ff0 100644 --- a/loopx/cli_commands/quota.py +++ b/loopx/cli_commands/quota.py @@ -6,6 +6,10 @@ from pathlib import Path from typing import Any +from .quota_presentation import ( + _project_quota_cli_payload as _project_quota_cli_payload, + _quota_renderer as _quota_renderer, +) from .quota_capture import ( bind_capture_selection_transport, capture_decision, handoff_decision, handle_native_context_command, handle_replan_view, prepare_quota_capture, native_context_capture_view, @@ -26,17 +30,11 @@ from ..capabilities.repository_change_window import ( repository_delivery_interaction_hook, ) -from ..control_plane.effect_runtime import EffectRuntimeRejected from ..control_plane.coordination.local_authority import CanonicalAuthorityRequired from ..control_plane.goals.first_party_host_admission import ( capture_first_party_host_goal_ref, ) from ..control_plane.goals.source_session_registry_state import exact_goal_ref -from ..control_plane.quota.cli_projection import ( - compact_quota_monitor_poll_cli_payload, - compact_quota_plan_cli_payload, - compact_quota_should_run_cli_payload, -) from ..control_plane.quota.effect_program import SettlementIdentity from ..control_plane.quota.error_codes import ( CloseoutQueryUnavailableError, @@ -60,20 +58,6 @@ reconcile_existing_heartbeat_receipt_for_turn, render_existing_heartbeat_receipt_payload, ) -from ..control_plane.quota.turn_envelope import build_turn_envelope -from ..presentation.renderers.quota_event_markdown import ( - render_quota_monitor_poll_markdown, - render_quota_slot_preview_markdown, -) -from ..presentation.renderers.quota_markdown import ( - render_quota_markdown, - render_quota_scheduler_ack_markdown, - render_quota_scheduler_failure_markdown, - render_quota_should_run_markdown, -) -from ..presentation.renderers.turn_envelope_markdown import ( - render_turn_envelope_markdown, -) from ..quota import ( build_quota_plan, record_quota_monitor_poll, @@ -184,24 +168,6 @@ def _effective_spend_turn_instance_id( return payload_turn_id -def _quota_renderer( - args: argparse.Namespace, - *, - failure: bool = False, -) -> Callable[[dict[str, object]], str]: - command = args.quota_command - if not failure and bool(getattr(args, "turn_envelope", False)): - return render_turn_envelope_markdown - return { - "should-run": render_quota_should_run_markdown, - "monitor-poll": render_quota_monitor_poll_markdown, - "scheduler-ack": render_quota_scheduler_ack_markdown, - "scheduler-ack-current": render_quota_scheduler_ack_markdown, - "scheduler-fail-current": render_quota_scheduler_failure_markdown, - "spend-slot": render_quota_slot_preview_markdown, - "void-slot": render_quota_slot_preview_markdown, - }.get(command, render_quota_markdown) - def _dispatch_quota_turn_start_hooks( args: argparse.Namespace, @@ -300,58 +266,6 @@ def _attach_turn_start_hook_dispatch( payload["turn_start_capability_hook_dispatch"] = { key: value for key, value in dispatch.items() if key != "contexts" } -def _project_quota_cli_payload( - payload: dict[str, object], - args: argparse.Namespace, - detail_sections: frozenset[str], - scheduler_context: object, - *, - captured_decision_path: str | None = None, -) -> dict[str, object]: - """Project successful decisions; failed requests retain their diagnostic. - - Optional presentation must not call a runtime that failed during collection. - A rejected envelope likewise retains the already-decided facts (issue #3687). - """ - from ..control_plane.effect_program import reject_retired_quota_format - - reject_retired_quota_format(payload) - if payload.get("ok") is False: - # Failure reporting must not reenter the runtime that just failed. - # Keep the decided diagnostic, including its original recovery/receipt. - if bool(getattr(args, "turn_envelope", False)): - return {**payload, "turn_envelope_skipped": "quota request failed; original diagnostic retained"} - return payload - if not bool(getattr(args, "turn_envelope", False)): - if args.quota_command in {"status", "plan"}: - return compact_quota_plan_cli_payload(payload, detail_sections=detail_sections) - if args.quota_command == "should-run": - if captured_decision_path and "vision" not in detail_sections: - from ..control_plane.quota.turn_envelope import _turn_envelope_result - payload = _turn_envelope_result("replan_cli_view", payload=payload) - return compact_quota_should_run_cli_payload( - payload, - include_todo_summary_detail="agent-todos" in detail_sections, - include_user_todo_summary_detail="user-todos" in detail_sections, - include_goal_boundary_detail="goal-boundary" in detail_sections, - include_vision_detail="vision" in detail_sections, - ) - if args.quota_command == "monitor-poll": - return compact_quota_monitor_poll_cli_payload( - payload, include_decision_detail="decisions" in detail_sections, - ) - return payload - try: - return build_turn_envelope( - payload, - scheduler_execution_context=scheduler_context, - captured_decision_path=captured_decision_path, - ) - except EffectRuntimeRejected as envelope_error: - degraded = dict(payload) - degraded["turn_envelope_skipped"] = str(envelope_error)[:200] - return degraded - def _emit_quota_result( payload: dict[str, object], diff --git a/loopx/cli_commands/quota_presentation.py b/loopx/cli_commands/quota_presentation.py new file mode 100644 index 0000000000..0041fa5f9f --- /dev/null +++ b/loopx/cli_commands/quota_presentation.py @@ -0,0 +1,109 @@ +"""Quota CLI presentation transport; TypeScript retains decision ownership.""" +from __future__ import annotations + +import argparse +from collections.abc import Callable, Mapping + +from ..control_plane.effect_runtime import EffectRuntimeRejected +from ..control_plane.quota.turn_envelope import build_turn_envelope +from ..control_plane.quota.cli_projection import ( + compact_quota_monitor_poll_cli_payload, + compact_quota_plan_cli_payload, + compact_quota_should_run_cli_payload, +) + +from ..presentation.renderers.quota_event_markdown import ( + render_quota_monitor_poll_markdown, + render_quota_slot_preview_markdown, +) + +from ..presentation.renderers.quota_markdown import ( + render_quota_markdown, + render_quota_scheduler_ack_markdown, + render_quota_scheduler_failure_markdown, + render_quota_should_run_markdown, +) + +from ..presentation.renderers.turn_envelope_markdown import ( + render_turn_envelope_markdown, +) + +def _quota_renderer( + args: argparse.Namespace, + *, + failure: bool = False, +) -> Callable[[dict[str, object]], str]: + command = args.quota_command + if not failure and bool(getattr(args, "turn_envelope", False)): + return render_turn_envelope_markdown + return { + "should-run": render_quota_should_run_markdown, + "monitor-poll": render_quota_monitor_poll_markdown, + "scheduler-ack": render_quota_scheduler_ack_markdown, + "scheduler-ack-current": render_quota_scheduler_ack_markdown, + "scheduler-fail-current": render_quota_scheduler_failure_markdown, + "spend-slot": render_quota_slot_preview_markdown, + "void-slot": render_quota_slot_preview_markdown, + }.get(command, render_quota_markdown) + + +def _project_quota_cli_payload( + payload: dict[str, object], + args: argparse.Namespace, + detail_sections: frozenset[str], + scheduler_context: object, + *, + captured_decision_path: str | None = None, +) -> dict[str, object]: + """Project decided contracts; failed collection retains its diagnostic. + + Optional presentation must not call a runtime that failed during collection. + A rejected envelope likewise retains the already-decided facts (issue #3687). + """ + from ..control_plane.effect_program import reject_retired_quota_format + + reject_retired_quota_format(payload) + if payload.get("ok") is False and not isinstance(payload.get("interaction_contract"), Mapping): + if (not bool(getattr(args, "turn_envelope", False)) + and getattr(args, "quota_command", None) == "monitor-poll" + and isinstance(payload.get("before"), Mapping)): + # A refused poll still has its decided before snapshot. Its + # existing compaction is pure and does not reenter the runtime. + return compact_quota_monitor_poll_cli_payload( + payload, include_decision_detail="decisions" in detail_sections, + ) + # A collection failure has no decided contract and must not reenter + # its unavailable runtime. Decided refusals still need signed views. + # Keep the decided diagnostic, including its original recovery/receipt. + if bool(getattr(args, "turn_envelope", False)): + return {**payload, "turn_envelope_skipped": "quota request failed; original diagnostic retained"} + return payload + if not bool(getattr(args, "turn_envelope", False)): + if args.quota_command in {"status", "plan"}: + return compact_quota_plan_cli_payload(payload, detail_sections=detail_sections) + if args.quota_command == "should-run": + if captured_decision_path and "vision" not in detail_sections: + from ..control_plane.quota.turn_envelope import _turn_envelope_result + payload = _turn_envelope_result("replan_cli_view", payload=payload) + return compact_quota_should_run_cli_payload( + payload, + include_todo_summary_detail="agent-todos" in detail_sections, + include_user_todo_summary_detail="user-todos" in detail_sections, + include_goal_boundary_detail="goal-boundary" in detail_sections, + include_vision_detail="vision" in detail_sections, + ) + if args.quota_command == "monitor-poll": + return compact_quota_monitor_poll_cli_payload( + payload, include_decision_detail="decisions" in detail_sections, + ) + return payload + try: + return build_turn_envelope( + payload, + scheduler_execution_context=scheduler_context, + captured_decision_path=captured_decision_path, + ) + except EffectRuntimeRejected as envelope_error: + degraded = dict(payload) + degraded["turn_envelope_skipped"] = str(envelope_error)[:200] + return degraded diff --git a/loopx/cli_commands/update_artifact.py b/loopx/cli_commands/update_artifact.py index a657afa609..67e773f462 100644 --- a/loopx/cli_commands/update_artifact.py +++ b/loopx/cli_commands/update_artifact.py @@ -1,13 +1,15 @@ """Bootstrap IO for prebuilt snapshots (stdlib only, including before Node exists). -This is also the website installer's Python bootstrap. It deliberately reuses -install-local.sh for candidate validation, migrations and activation. It is not -a second control-plane decision owner or a package-manager replacement. +The website exports a standalone copy through ``standalone_source``. Shared +metadata grammars are bound at export time, so that copy needs only stdlib +before LoopX or Node exists. Candidate validation and activation still reuse +install-local.sh. """ from __future__ import annotations import argparse +import ast from contextlib import contextmanager import hashlib from http.client import HTTPException @@ -25,14 +27,41 @@ from urllib.parse import urlsplit from urllib.request import HTTPRedirectHandler, Request, build_opener +from ..control_plane.content_digest import BARE_SHA256_PATTERN as HASH +from ..capabilities.issue_fix.repository_snapshot import _REPO_PATTERN as REPOSITORY_PATTERN + RUNTIME_ARTIFACT_SCHEMA_VERSION = "loopx_runtime_artifact_v1" SHA = re.compile(r"[0-9a-f]{40}") -HASH = re.compile(r"[0-9a-f]{64}") MAX_MANIFEST = 256 * 1024 MAX_ARCHIVE = 256 * 1024 * 1024 MAX_EXPANDED = 1024 * 1024 * 1024 +def standalone_source() -> str: + """Bind existing grammar owners into the independent bootstrap artifact.""" + source = Path(__file__).read_text(encoding="utf-8") + for statement, owner_path, symbol, alias in ( + ("from ..control_plane.content_digest import BARE_SHA256_PATTERN as HASH", + "control_plane/content_digest.py", "BARE_SHA256_PATTERN", "HASH"), + ("from ..capabilities.issue_fix.repository_snapshot import _REPO_PATTERN as REPOSITORY_PATTERN", + "capabilities/issue_fix/repository_snapshot.py", "_REPO_PATTERN", "REPOSITORY_PATTERN"), + ): + owner = (Path(__file__).resolve().parents[1] / owner_path).read_text(encoding="utf-8") + assignments = [node for node in ast.parse(owner).body if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == symbol for target in node.targets)] + if len(assignments) != 1: + raise ValueError("Bootstrap grammar owner no longer has one source binding") + # Copy the canonical binding verbatim. A consumer must not rebuild a + # regex by reading and recompiling the compiled owner's pattern text. + binding = ast.get_source_segment(owner, assignments[0]) + f"\n{alias} = {symbol}" + # Match import lines only, never this generator's statement literals. + old = "\n" + statement + "\n" + if source.count(old) != 1: + raise ValueError("Bootstrap grammar binding no longer matches its source import") + source = source.replace(old, "\n" + binding + "\n", 1) + return source + + def safe_url(url: str) -> str: parsed = urlsplit(url) # HTTP is only useful for disposable loopback qualification servers. @@ -89,7 +118,7 @@ def validate_manifest(value: dict[str, Any], *, revision: str | None = None) -> def resolve_artifact(repo: str, ref: str) -> dict[str, Any]: - if not re.fullmatch(r"[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+", repo): + if not REPOSITORY_PATTERN.fullmatch(repo): raise ValueError("Update repo must use GitHub owner/name syntax") if ref == "stable": url = f"https://github.com/{repo}/releases/latest/download/loopx-runtime.json" @@ -201,7 +230,7 @@ def download_artifact(manifest: dict[str, Any], *, progress=lambda _event: None, url = urls[attempt % len(urls)] offset, etag = 0, None try: - saved = json.loads(metadata.read_text()) + saved = json.loads(metadata.read_text(encoding="utf-8")) candidate = saved.get("etag") if (saved.get("url_hash") == hashlib.sha256(url.encode()).hexdigest() and isinstance(candidate, str) and candidate.startswith('"') and not candidate.startswith('W/') @@ -348,7 +377,7 @@ def install_artifact(manifest: dict[str, Any], *, env: dict[str, str], timeout_s progress({"stage": "verify"}) with tempfile.TemporaryDirectory(prefix="loopx-runtime-") as raw: root = extract_artifact(archive, Path(raw)) - identity = json.loads((root / "runtime-artifact.json").read_text()) + identity = json.loads((root / "runtime-artifact.json").read_text(encoding="utf-8")) if identity.get("source_revision") != manifest["source_revision"] or identity.get("package_version") != manifest["package_version"]: raise ValueError("Runtime identity disagrees with its release metadata") selected = dict(env) @@ -375,7 +404,7 @@ def report(event): entry = Path(os.environ.get("LOOPX_BIN_DIR") or Path.home() / ".local/bin") / "loopx" if entry.is_symlink(): try: - installed = json.loads((entry.resolve().parents[1] / "release.json").read_text()).get("source", {}) + installed = json.loads((entry.resolve().parents[1] / "release.json").read_text(encoding="utf-8")).get("source", {}) except (OSError, ValueError): pass if (entry.exists() or entry.is_symlink()) and not installed: diff --git a/loopx/cli_commands/update_operation.py b/loopx/cli_commands/update_operation.py index 3c7f10d0af..685752793a 100644 --- a/loopx/cli_commands/update_operation.py +++ b/loopx/cli_commands/update_operation.py @@ -45,27 +45,27 @@ def read_operation(operation_id: str | None = None) -> dict: root = operation_root() if operation_id is None: try: - operation_id = json.loads((root / f"{installation_key()}.active.json").read_text())["operation_id"] + operation_id = json.loads((root / f"{installation_key()}.active.json").read_text(encoding="utf-8"))["operation_id"] except (OSError, ValueError, KeyError): return {"ok": True, "status": "none", "operation_id": None} if not re.fullmatch(r"[a-f0-9]{32}", operation_id): raise ValueError("Invalid update operation id") folder = root / operation_id try: - state = json.loads((folder / "status.json").read_text()) + state = json.loads((folder / "status.json").read_text(encoding="utf-8")) except (OSError, ValueError): state = {"operation_id": operation_id, "status": "unavailable", "ok": False} result_file = folder / "result.json" if result_file.is_file(): try: - result = json.loads(result_file.read_text()) + result = json.loads(result_file.read_text(encoding="utf-8")) state.update(status="succeeded" if result.get("ok") else "failed", ok=bool(result.get("ok")), result=result) except (OSError, ValueError): state.update(status="interrupted", ok=False) elif state.get("status") in {"running", "starting"}: # PID alone is not sufficient proof of work after a reboot/PID reuse. try: - worker = json.loads((folder / "worker.json").read_text()) + worker = json.loads((folder / "worker.json").read_text(encoding="utf-8")) except (OSError, ValueError): worker = {} observed = process_is_alive(state.get("pid") or worker.get("pid")) and time.time() - state.get("updated_at", 0) < 30 @@ -148,7 +148,7 @@ def observe(operation_id: str, *, json_output: bool = False) -> dict: def worker(operation_id: str) -> int: folder = operation_root() / operation_id - request = json.loads((folder / "request.json").read_text()) + request = json.loads((folder / "request.json").read_text(encoding="utf-8")) # The lifetime lock covers planning, downloading, install and readback. with exclusive_file_lock(operation_root() / (request["installation_key"] + ".worker"), timeout_seconds=0, operation="update_worker"): @@ -168,7 +168,7 @@ def worker(operation_id: str) -> int: "error": "Update worker failed; inspect private worker.log and installed doctor before retrying"} code = 1 try: - progress = json.loads((folder / "status.json").read_text()) + progress = json.loads((folder / "status.json").read_text(encoding="utf-8")) progress.update(status="succeeded" if payload.get("ok") else "failed", updated_at=time.time()) atomic_json(folder / "status.json", progress) except (OSError, ValueError): diff --git a/loopx/control_plane/testing/cli_output_budget.py b/loopx/control_plane/testing/cli_output_budget.py index 0ad58b9dca..9866a68332 100644 --- a/loopx/control_plane/testing/cli_output_budget.py +++ b/loopx/control_plane/testing/cli_output_budget.py @@ -312,13 +312,15 @@ class CliOutputCommandClassification: ), markdown_anchor="# Heartbeat Automation Prompt", max_chars={ - # The complete first-guard selection guidance and supported - # 320-char scope emit 4,532 JSON chars at immutable b47bbc6c9. - # Retain the old 4,500 failure; allow 68 chars of JSON headroom. - # Body presentation stays 4,200; Markdown/line guards are unchanged. - "small": {"json": 4_600, "markdown": 6_200}, - "crowded": {"json": 4_600, "markdown": 6_200}, - "multi_agent": {"json": 4_600, "markdown": 6_200}, + # Same fixed-path, 320-char scope workload at 9cae3b289 emitted + # 4,532 JSON / 6,648 Markdown chars; timed-wait guidance adds 321 + # chars, retaining the original Turn, due time and fresh admission. + # All three scenarios now emit 4,853 / 6,969 chars and 63 Markdown + # lines. Preserve historical misses; retain complete instructions, + # the current 4,500 body allowance and unchanged line/growth guards. + "small": {"json": 5_000, "markdown": 7_100}, + "crowded": {"json": 5_000, "markdown": 7_100}, + "multi_agent": {"json": 5_000, "markdown": 7_100}, }, max_lines={ "small": {"json": 18, "markdown": 72}, diff --git a/loopx/extensions/chat_service_guard.py b/loopx/extensions/chat_service_guard.py new file mode 100644 index 0000000000..f628935268 --- /dev/null +++ b/loopx/extensions/chat_service_guard.py @@ -0,0 +1,12 @@ +"""Supported host seam for an authorized restart of managed Chat services. + +The runtime owner keeps accepted-work observation and ingress locking. This +facade grants no stop authority and does not copy its admission decisions. +""" + +from ..runtime_activation import ( + RuntimeActivationDeferred as RuntimeActivationDeferred, + managed_chat_activation_guard as managed_chat_activation_guard, +) + +__all__ = ["RuntimeActivationDeferred", "managed_chat_activation_guard"] diff --git a/loopx/presentation/request_control_api.py b/loopx/presentation/request_control_api.py index d3cdbcbaa3..be6c3aa2fc 100644 --- a/loopx/presentation/request_control_api.py +++ b/loopx/presentation/request_control_api.py @@ -56,27 +56,6 @@ def read(session): return read -def manager_source_scope_resolver( - server: Any, *, registry_path: Path, executor_runtime_root: Path, -) -> Callable[[dict[str, Any]], list[str] | None]: - """Keep the default manager's native and legacy source routing together.""" - from ..chat_lark_api import build_lark_goal_topic_runtime_snapshot - from ..extensions.lark.manager_routing import authorized_manager_goal_ids - - def read(session): - controller = server.runtime_controller - if isinstance(session.get("steward_context"), dict): - bindings = controller.project_contexts.conversation_bindings - if bindings is None: - raise ValueError("Steward conversation bindings are unavailable") - return bindings.steward_scope(session) - return authorized_manager_goal_ids( - build_lark_goal_topic_runtime_snapshot( - registry_path=server.registry_path, runtime_root_override=server.runtime_root_override, - ), session, runtime_root=controller.coordination_runtime_root, - registry_path=registry_path, executor_runtime_root=executor_runtime_root, - ) - return read class RequestControlMixin: diff --git a/packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py b/packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py index 467f1886fa..21bdaf746f 100644 --- a/packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py +++ b/packages/loopx-codex-provider-routing/src/loopx_codex_provider_routing/native_host.py @@ -564,7 +564,7 @@ def restart_guard(self): try: # Reuse the running product's accepted-work reader and ingress # lock. Missing/unsupported Core cannot authorize a service stop. - from loopx.runtime_activation import managed_chat_activation_guard, RuntimeActivationDeferred + from loopx.extensions.chat_service_guard import managed_chat_activation_guard, RuntimeActivationDeferred except ImportError as error: raise HostError("waiting_for_idle") from error frozen = {label: (pid, identity, self._chat_definition(plist, label)) diff --git a/tests/architecture/package_kernel_import_pins.json b/tests/architecture/package_kernel_import_pins.json index a1f468f70c..7f0db05546 100644 --- a/tests/architecture/package_kernel_import_pins.json +++ b/tests/architecture/package_kernel_import_pins.json @@ -3,6 +3,9 @@ "baseline_commit": "70bc98198ce9e8c33e461cc41fe77243d713dcf0", "note": "Exact pin of every `loopx.*` module imported by a co-located package. Pre-existing private edges remain migration debt. The supported-host-integration decision in the monorepo-distribution-split RFC Section 5 permits MemT's two public integration imports; it does not permit private kernel imports. Remove an entry in the PR that removes the import. Adding an entry needs a recorded design decision.", "imports": { + "loopx-codex-provider-routing": [ + "loopx.extensions.chat_service_guard" + ], "loopx-ark-turn": [ "loopx.control_plane.turn_driver.host_candidate", "loopx.file_lock" diff --git a/tests/architecture/test_content_digest_single_owner.py b/tests/architecture/test_content_digest_single_owner.py index d855dd023d..42ff717c6c 100644 --- a/tests/architecture/test_content_digest_single_owner.py +++ b/tests/architecture/test_content_digest_single_owner.py @@ -180,6 +180,7 @@ "loopx.chat_action_normalization", "loopx.capabilities.manager_context.chat_history", "loopx.cli_commands.quota_capture", + "loopx.cli_commands.update_artifact", "loopx.configuration_transaction", "loopx.control_plane.agents.execution_facts", "loopx.control_plane.collaboration.conversation_inventory", diff --git a/tests/architecture/test_control_plane_import_boundaries.py b/tests/architecture/test_control_plane_import_boundaries.py index 0c912989b1..102a426d6c 100644 --- a/tests/architecture/test_control_plane_import_boundaries.py +++ b/tests/architecture/test_control_plane_import_boundaries.py @@ -460,12 +460,15 @@ def test_core_does_not_import_experiments() -> None: def test_quota_markdown_is_owned_by_the_presentation_layer() -> None: legacy_renderer = CONTROL_PLANE_ROOT / "quota" / "markdown.py" imports = _resolved_imports(QUOTA_MODULE) - cli_imports = _resolved_from_imports(QUOTA_CLI_MODULE) + cli_imports = _resolved_from_imports(QUOTA_CLI_MODULE.with_name("quota_presentation.py")) presentation_renderers = cli_imports.get( "loopx.presentation.renderers.quota_event_markdown", set() ) assert not legacy_renderer.exists() + assert {"_quota_renderer", "_project_quota_cli_payload"} <= _resolved_from_imports( + QUOTA_CLI_MODULE + ).get("loopx.cli_commands.quota_presentation", set()) assert "loopx.presentation.renderers.quota_markdown" in imports assert { "render_quota_monitor_poll_markdown", diff --git a/tests/control_plane/test_cli_output_budget.py b/tests/control_plane/test_cli_output_budget.py index 2a3377e7fe..497d7018b8 100644 --- a/tests/control_plane/test_cli_output_budget.py +++ b/tests/control_plane/test_cli_output_budget.py @@ -1712,7 +1712,7 @@ def test_thin_json_budget_retains_full_supported_scope(tmp_path: Path) -> None: ) payload = measurement["payload"] assert scope in payload["task_body"] - assert payload["interface_budget"]["max_chars"] == 4200 + assert payload["interface_budget"]["max_chars"] == 4500 assert payload["interface_budget"]["within_budget"] is True assert "refresh-state --goal-outcome-update-json" in payload["task_body"] assert "Local outcomes never replace root continuation, budget or stop criteria." in payload["task_body"] diff --git a/tests/control_plane/test_codex_context_boundary.py b/tests/control_plane/test_codex_context_boundary.py index 88dce46132..5c82cedcb7 100644 --- a/tests/control_plane/test_codex_context_boundary.py +++ b/tests/control_plane/test_codex_context_boundary.py @@ -177,7 +177,7 @@ def test_native_reader_requires_complete_hook_output_even_when_model_saw_page(tm name: [{"hooks": [handler]}] for name in ("SessionStart", "PostToolUse")}})) publish = workspace / "publish.py" publish.write_text( - "import json, pathlib, time\n" + "import json, pathlib, time, subprocess, shlex\n" "from loopx.control_plane.turn_driver.codex_context import project_codex_context\n" "from loopx.cli_commands.quota_capture import native_context_capture_view\n" f"capture=pathlib.Path({str(capture)!r})\n" @@ -186,6 +186,10 @@ def test_native_reader_requires_complete_hook_output_even_when_model_saw_page(tm f"full=project_codex_context(body,directory={str(root)!r},capture_directory=capture," "goal_id='fixture',agent_id='agent',surface='work_context',capture_id='fixture-capture')\n" f"view=native_context_capture_view(full,capture,context_directory={str(root)!r})\n" + # Read the returned reference through its real command. The test still + # needs an actual page in model input to isolate complete-hook coverage. + "view=json.loads(subprocess.run(shlex.split(view['command']),check=True," + "capture_output=True,text=True).stdout)\n" "text=json.dumps(view,separators=(',',':'))\n" f"pathlib.Path({str(view_file)!r}).write_text(text)\n" "print(text,flush=True)\n" diff --git a/tests/control_plane/test_codex_context_capture.py b/tests/control_plane/test_codex_context_capture.py index 9957ccaa28..4e31db9d33 100644 --- a/tests/control_plane/test_codex_context_capture.py +++ b/tests/control_plane/test_codex_context_capture.py @@ -9,6 +9,7 @@ import json import os from pathlib import Path +import re import shlex import subprocess import sys @@ -371,11 +372,30 @@ def response(request, number, message): assert "LoopX model-context baseline invalidated." not in text # The recovery entry must really be present after compaction; # the fixture may not supply a hidden saved capture path. - prefix = "Before dependent action, recover missing or uncertain necessary facts" - permanent = text[text.index(prefix):] - # Read only the advertised command sentence. Subsequent - # recovery guidance is not part of the shell invocation. - cmd = permanent.split("with: ", 1)[1].split(". ", 1)[0] + commands = set() + for advertised in re.findall(r"with: ([^\n]+?)(?:\. |$)", text): + try: + argv = shlex.split(advertised) + quota = argv.index("quota") + if argv[quota:quota + 2] != ["quota", "context-read"]: + continue + # The worker advertises its own trial-local directory + # in the actual guard; it need not use this fixture's. + if (argv[argv.index(context_option) + 1] != command[command.index(context_option) + 1] + or argv[argv.index("--goal-id") + 1] != GOAL_ID + or argv[argv.index("--agent-id") + 1] != AGENT_ID): + continue + except (ValueError, IndexError): + continue + # Voluntary full traversal serves this byte oracle. Index + # and selected reads do not themselves establish a baseline. + for option, width in (("--select", 2), ("--list", 1)): + while option in argv: + index = argv.index(option) + del argv[index:index + width] + commands.add(shlex.join(argv)) + assert len(commands) == 1, "One scoped permanent reader must be visible in model input" + cmd = commands.pop() stages["phase"] = "lookup_read" assert delivered(request, "cold") == "" and delivered(request, "warm") == "" return tool(cmd, number) diff --git a/tests/control_plane/test_frontier_deadline.py b/tests/control_plane/test_frontier_deadline.py index 6e60ccd4f5..33a9a84232 100644 --- a/tests/control_plane/test_frontier_deadline.py +++ b/tests/control_plane/test_frontier_deadline.py @@ -136,9 +136,9 @@ def test_quota_planning_keeps_gate_deadline_before_visibility_limits(monkeypatch original = json.loads(json.dumps(gates)) crossings = [] invoke = quota_selection.effect_runtime_result - def observed(method, params): + def observed(method, params, **kwargs): crossings.append(method) - return invoke(method, params) + return invoke(method, params, **kwargs) monkeypatch.setattr(quota_selection, "effect_runtime_result", observed) summary = quota_summary.summarize_user_todos_for_quota( {"schema_version": "todo_summary_v0", "items": gates, "total_count": 26, diff --git a/tests/control_plane/test_goal_state_context_transport.py b/tests/control_plane/test_goal_state_context_transport.py index 759c9296db..7a734abef4 100644 --- a/tests/control_plane/test_goal_state_context_transport.py +++ b/tests/control_plane/test_goal_state_context_transport.py @@ -86,6 +86,11 @@ def guard(): page, raw = json.loads(output.getvalue()), bytearray() assert code == 0 and page["ok"], (page.get("reason"), page.get("error"), page.get("error_code")) digest = page["sha256"] + if page.get("schema_version") != "context_capture_page_v1": + page = read_context_capture(build_parser().parse_args([ + "--format", "json", "quota", "context-read", "--capture-file", page["path"], + "--capture-sha256", digest, "--offset", "0", "--page-bytes", "3072"])) + assert page["ok"], page while True: assert page["offset"] == len(raw) and page["sha256"] == digest raw.extend(page["content"].encode()) diff --git a/tests/control_plane/test_host_prompt_behavior.py b/tests/control_plane/test_host_prompt_behavior.py index 97fb0e2334..19c8c2cd79 100644 --- a/tests/control_plane/test_host_prompt_behavior.py +++ b/tests/control_plane/test_host_prompt_behavior.py @@ -54,7 +54,10 @@ def test_probe_uses_current_production_prompts_and_hidden_independent_oracle(): assert "heartbeat_recommendation.agent_must_attempt" in body assert "projected monitor/continuation" in body assert "no invented successor" in body - assert "no wait spend" in body.lower() + assert "no wait spend" in body.lower() or "no spend for waiting alone." in body.lower() + assert "resume_at:" in body + assert "yield to host scheduler" in body + assert "Fresh admission on resume." in body assert "--codex-app" in body assert "LOOPX_TURN=" in body assert case["id"] not in json.dumps(messages) diff --git a/tests/control_plane/test_quota_monitor_clock.py b/tests/control_plane/test_quota_monitor_clock.py index 23a8f89959..09659c36c4 100644 --- a/tests/control_plane/test_quota_monitor_clock.py +++ b/tests/control_plane/test_quota_monitor_clock.py @@ -70,9 +70,9 @@ def clock(): clocks.append(True) return NOW - def record(method, params): + def record(method, params, **kwargs): calls.append(method) - return original(method, params) + return original(method, params, **kwargs) monkeypatch.setattr(quota_selection, "now_utc", clock, raising=False) monkeypatch.setattr(quota_selection, "effect_runtime_result", record) diff --git a/tests/control_plane/test_quota_summary_source_precedence.py b/tests/control_plane/test_quota_summary_source_precedence.py index ab94870c9e..313df40983 100644 --- a/tests/control_plane/test_quota_summary_source_precedence.py +++ b/tests/control_plane/test_quota_summary_source_precedence.py @@ -39,9 +39,9 @@ def test_only_preferred_available_source_is_projected( calls = [] original = quota_selection.effect_runtime_result - def record(method, params): + def record(method, params, **kwargs): calls.append(method) - return original(method, params) + return original(method, params, **kwargs) monkeypatch.setattr(quota_selection, "effect_runtime_result", record) summary = quota_summary.select_quota_todo_summary( diff --git a/tests/presentation/test_codex_accounts.py b/tests/presentation/test_codex_accounts.py index 411dcf1ae1..7b88b14e45 100644 --- a/tests/presentation/test_codex_accounts.py +++ b/tests/presentation/test_codex_accounts.py @@ -188,7 +188,7 @@ def test_native_worker_inherits_loaded_core_outside_checkout(native, monkeypatch import json, os, sys from pathlib import Path import loopx -from loopx.runtime_activation import managed_chat_activation_guard +from loopx.extensions.chat_service_guard import managed_chat_activation_guard packet = {"ok": True, "result": {"core": str(Path(loopx.__file__).resolve()), "guard": callable(managed_chat_activation_guard)}} if len(sys.argv) == 3 and sys.argv[1] == "--result-file": diff --git a/tests/test_chat_lark_api_contract.py b/tests/test_chat_lark_api_contract.py index 4e0b9b24ff..c8ac755c1c 100644 --- a/tests/test_chat_lark_api_contract.py +++ b/tests/test_chat_lark_api_contract.py @@ -1104,7 +1104,9 @@ def test_manager_scope_reads_policy_from_coordination_root_when_chat_root_overri assets = tmp_path / "assets" assets.mkdir() (assets / "index.html").write_text("fixture", encoding="utf-8") - monkeypatch.setattr(chat_server, "build_lark_goal_topic_runtime_snapshot", lambda **_: snapshot) + from loopx import chat_lark_api + + monkeypatch.setattr(chat_lark_api, "build_lark_goal_topic_runtime_snapshot", lambda **_: snapshot) monkeypatch.setattr( chat_server, "resolve_lark_cli_for_runtime", diff --git a/tests/test_native_steward_ingress.py b/tests/test_native_steward_ingress.py index 4e236be3b8..f6743ceacf 100644 --- a/tests/test_native_steward_ingress.py +++ b/tests/test_native_steward_ingress.py @@ -107,7 +107,8 @@ def test_native_inventory_http_keeps_long_history_and_fresh_provider_checks(stew from loopx.control_plane.coordination.runtime_shadow import build_todo_runtime_shadow_projection from loopx.control_plane.todos.mutation_api import add_goal_todo from loopx.extensions.lark.conversation_identity import identity_ref - from loopx.presentation.request_control_api import bound_steward_inventory_scope, manager_source_scope_resolver + from loopx.presentation.request_control_api import bound_steward_inventory_scope + from loopx.chat_lark_api import manager_source_scope_resolver store, runtime, provider, transport, binding, capture, workspace = steward goal = "registered-work" @@ -207,7 +208,7 @@ def test_manager_source_scope_keeps_source_and_executor_routes_distinct(monkeypa from types import SimpleNamespace from loopx import chat_lark_api from loopx.extensions.lark import manager_routing - from loopx.presentation.request_control_api import manager_source_scope_resolver + from loopx.chat_lark_api import manager_source_scope_resolver source, frozen_registry, executor = tmp_path / "source", tmp_path / "registry", tmp_path / "executor" server = SimpleNamespace(registry_path=source, runtime_root_override="current-source-override", diff --git a/tests/test_turn_envelope.py b/tests/test_turn_envelope.py index 7934a25aef..8f503101f1 100644 --- a/tests/test_turn_envelope.py +++ b/tests/test_turn_envelope.py @@ -1007,7 +1007,7 @@ def test_modern_envelope_does_not_bypass_host_signature_check(corruption: str) - @pytest.mark.parametrize("has_packet", [False, True]) -def test_envelope_fallback_preserves_typed_failure_with_or_without_packet(has_packet: bool) -> None: +def test_envelope_fallback_preserves_typed_failure_with_or_without_packet(has_packet: bool, monkeypatch) -> None: from loopx.cli_commands.quota import _project_quota_cli_payload from argparse import Namespace @@ -1026,8 +1026,13 @@ def test_envelope_fallback_preserves_typed_failure_with_or_without_packet(has_pa _project_quota_cli_payload(failure, Namespace(turn_envelope=True), frozenset(), None) assert failure == before return + + def unexpected_envelope(*_args, **_kwargs): + pytest.fail("A collection failure cannot reenter its unavailable runtime") + + monkeypatch.setattr("loopx.cli_commands.quota_presentation.build_turn_envelope", unexpected_envelope) rendered = _project_quota_cli_payload(failure, Namespace(turn_envelope=True), frozenset(), None) - assert "interaction_contract must be an object" in rendered.pop("turn_envelope_skipped") + assert rendered.pop("turn_envelope_skipped") == "quota request failed; original diagnostic retained" assert rendered == before assert failure == before diff --git a/tests/test_update_artifact.py b/tests/test_update_artifact.py index a1dd0fd3bf..bb2e8e0dd7 100644 --- a/tests/test_update_artifact.py +++ b/tests/test_update_artifact.py @@ -6,6 +6,8 @@ import io import json import os +import subprocess +import sys import tarfile import threading @@ -14,6 +16,27 @@ from loopx.cli_commands import update_artifact as update +def test_exported_bootstrap_validates_metadata_without_an_installed_package(tmp_path): + script = tmp_path / "bootstrap.py" + script.write_text(update.standalone_source(), encoding="utf-8") + descriptor = manifest(b"runtime", "https://publisher.invalid/runtime.tar.gz") + probe = """ +import json,runpy,sys +module=runpy.run_path(sys.argv[1]) +value=json.load(sys.stdin) +assert module['validate_manifest'](value)==value +for invalid in ['A'*64,'a'*63,'a'*65,'sha256:'+('a'*64),'a'*63+'g']: + try:module['validate_manifest']({**value,'sha256':invalid}) + except ValueError:pass + else:raise AssertionError('invalid digest accepted') +assert not any(name=='loopx' or name.startswith('loopx.') for name in sys.modules) +print('standalone metadata boundary passed') +""" + result = subprocess.run([sys.executable, "-I", "-c", probe, str(script)], + cwd=tmp_path, input=json.dumps(descriptor), capture_output=True, text=True, check=True) + assert result.stdout.strip() == "standalone metadata boundary passed" + + @contextmanager def server(body: bytes): state = {"requests": [], "interrupt": False, "offline": False, "ignore_range": False,