fix: reject broken runtime launchers and preserve failed proof evidence
Some checks failed
Governed runtime contract / contract (push) Failing after 16s
Some checks failed
Governed runtime contract / contract (push) Failing after 16s
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e387-534d-70e3-ad53-4ea05676db8c
This commit is contained in:
parent
4e666d6fa3
commit
43e621439a
26 changed files with 1367 additions and 3 deletions
|
|
@ -627,6 +627,8 @@ def _process_profiled_run_active(
|
|||
except GlasExecutionError as exc:
|
||||
execution_error = type(exc).__name__
|
||||
execution_reason = str(exc) if isinstance(exc, GlasSpendError) else "profiled execution failed (GlasExecutionError)"
|
||||
if isinstance(exc, GlasSpendError):
|
||||
safe_evidence = exc.execution_evidence
|
||||
|
||||
if tx is not None and tx.baseline is not None:
|
||||
tx_evidence = tx.evidence()
|
||||
|
|
|
|||
|
|
@ -66,6 +66,16 @@ class GlasExecutionError(RuntimeError):
|
|||
class GlasSpendError(GlasExecutionError):
|
||||
"""Spend refusal with bounded, operator-safe reason text."""
|
||||
|
||||
def __init__(self, message: str, *, execution_evidence: Any = None) -> None:
|
||||
super().__init__(message)
|
||||
evidence = normalise_execution_evidence_for_close(execution_evidence)
|
||||
# Accounting must remain fail-closed without erasing the gateway stage
|
||||
# and cleanup facts. Do not transport raw provider error/output here.
|
||||
self.execution_evidence = {
|
||||
key: value for key, value in evidence.items()
|
||||
if key not in {"error", "artifacts"}
|
||||
}
|
||||
|
||||
|
||||
def normalise_execution_evidence_for_close(raw: Any) -> dict[str, Any]:
|
||||
"""Retain only bounded Glas evidence fields safe for durable close state."""
|
||||
|
|
@ -85,6 +95,10 @@ def normalise_execution_evidence_for_close(raw: Any) -> dict[str, Any]:
|
|||
and value >= 0
|
||||
):
|
||||
result[key] = value
|
||||
for key in ("session_cleanup", "sandbox_destroy"):
|
||||
value = raw.get(key)
|
||||
if isinstance(value, str) and value in {"succeeded", "failed", "not_attempted"}:
|
||||
result[key] = value
|
||||
artifacts = raw.get("artifacts")
|
||||
if isinstance(artifacts, list):
|
||||
result["artifacts"] = [
|
||||
|
|
@ -220,7 +234,10 @@ def execute_profiled_run(
|
|||
if not spend.observe(run.id, raw):
|
||||
raise SpendAdmissionError("execution accounting requires reconciliation")
|
||||
except SpendAdmissionError as exc:
|
||||
raise GlasSpendError(f"spend accounting refused: {exc}") from None
|
||||
raise GlasSpendError(
|
||||
f"spend accounting refused: {exc}",
|
||||
execution_evidence=raw["evidence"],
|
||||
) from None
|
||||
if transfer is not None and raw["ok"]:
|
||||
evidence = raw["evidence"]
|
||||
if evidence.get("session_cleanup") != "succeeded" or evidence.get("sandbox_destroy") != "succeeded":
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class BootstrapRefused(RuntimeError):
|
|||
def prepare(path: Path, config: OpsRunConfig) -> tuple[MessagesPolicy, Path, str]:
|
||||
"""Validate value-free, owner-controlled pins without a key or queue claim."""
|
||||
from glas_harness.profiles import ProfileCatalog
|
||||
from sandboxer.extensions.runtime import verified_runtime
|
||||
from sandboxer.extensions.runtime import RUNTIME_MOUNT, verified_runtime
|
||||
try:
|
||||
_private_file(path)
|
||||
if not path.is_absolute() or path.resolve() != path or path.stat().st_size > 65536:
|
||||
|
|
@ -64,6 +64,15 @@ def prepare(path: Path, config: OpsRunConfig) -> tuple[MessagesPolicy, Path, str
|
|||
runtime = verified_runtime({"runtime": data["runtime"]})
|
||||
if runtime is None or not runtime.is_absolute() or runtime.resolve() != runtime:
|
||||
raise ValueError
|
||||
# A matching artifact digest does not prove its generated launchers
|
||||
# survived relocation. Refuse build-host interpreters before claiming.
|
||||
for name in ("rein-aharness", "glas-harness"):
|
||||
executable = runtime / "bin" / name
|
||||
if executable.is_symlink() or not executable.is_file() or not os.access(executable, os.X_OK):
|
||||
raise ValueError
|
||||
with executable.open("rb") as stream:
|
||||
if stream.readline(4096) != f"#!{RUNTIME_MOUNT}/bin/python3\n".encode():
|
||||
raise ValueError
|
||||
for private in (path.parent, spend.path.parent, Path(spend.policy.target_repo)):
|
||||
a, b = runtime.resolve(), private.resolve()
|
||||
if a.is_relative_to(b) or b.is_relative_to(a):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue