fix: validate sandbox readiness before rein handoff
Some checks failed
ci / validate (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0726e-5232-73f2-aaca-2c05ceb62efb
This commit is contained in:
tegwick 2026-09-06 14:53:50 +02:00
parent 31dd5aaca8
commit c38ee08720
6 changed files with 92 additions and 29 deletions

View file

@ -27,7 +27,8 @@ by the maturity target in `INTENT.md`. No committed profile is `ready`.
successful readiness check.
- A synchronous outer lifecycle: resolve, create, start, dispatch `run_task`,
summarize, clean up and destroy. Manager initialization failures return
normalized creation-failure evidence. Cleanup and destruction are attempted
normalized creation-failure evidence. Non-READY owner handoffs refuse
before rein startup, and reachability preparation errors retain their stage. Cleanup and destruction are attempted
after session failures, with independent cleanup/destruction outcomes even
when an earlier execution error remains primary. This is not a durable
recovery/retry service.
@ -67,7 +68,7 @@ universal redaction guarantee for arbitrary caller/provider data.
| Surface | Evidence available | Current limit |
|---|---|---|
| Catalog and adapters | 121 tests passed after GLAS-WP-0013; packaged catalog validation passed | Tests do not prove provider authentication or deployed runtime readiness |
| Catalog and adapters | 129 tests passed after GLAS-WP-0014; packaged catalog validation passed | Tests do not prove provider authentication or deployed runtime readiness |
| Same-host owner boundary | Non-secret live proof: source absent, mutation denied, sandbox-only commit, private task removal and workspace destruction | Deterministic dispatch, not a real model session |
| Standalone runtime and private state | Owner proof of rein CLI startup with pinned read-only Python runtime and private HOME/XDG/TMP state | Temporary proof artifact; pinned Claude executable and production selection still pending |
| Provider egress | Owner extension and persisted-manager proofs of provider TLS reachability, undeclared host/direct-IP denial and cleanup | Destination control, not HTTP-path or TLS-SNI filtering; not a complete real-rein proof |

View file

@ -169,3 +169,12 @@ without them load as `unknown`, rather than implying that cleanup or reporting
occurred. Consumers using older strict schemas must update to accept these
fields before consuming new serialized results. No profile pins or readiness
states change with this addition.
## Sandbox handoff validation (GLAS-WP-0014)
The synchronous create handoff must return a READY sandbox before any rein
method starts. Non-ready returns fail at sandbox_create and trigger destruction
of the returned sandbox. Errors preparing its reachability or SandboxHandle
fail at session_start, with destruction still attempted. Readiness here is the
owner lifecycle state; it does not promote a Glas profile's operational
readiness or independently prove runtime/provider health.

View file

@ -12,7 +12,7 @@ from datetime import UTC, datetime
from pydantic import ValidationError
from sandboxer.core.manager import SandboxManager
from sandboxer.models import Consumer, SandboxCreateRequest, SandboxExecRequest
from sandboxer.models import Consumer, SandboxCreateRequest, SandboxExecRequest, SandboxState
from glas_harness import hub
from glas_harness.contract import (
@ -124,38 +124,40 @@ def run_execution(
)
)
sandbox_id = status.sandbox_id
if status.state != SandboxState.READY:
raise RuntimeError("sandbox owner did not return a ready sandbox")
except Exception as exc:
failure_stage = "sandbox_create"
error = str(exc)
raise
reachability = (
status.reachability.model_dump(mode="json", exclude_none=True)
if status.reachability
else {}
)
sandbox = SandboxHandle(
sandbox_id=status.sandbox_id,
host=status.host or "",
reachability=reachability,
)
def owner_execute(argv, input_text, timeout):
return manager.execute(
sandbox.sandbox_id,
SandboxExecRequest(
command=list(argv),
consumer=consumer,
credential_route_refs=profile.credential_route_refs,
stdin_text=input_text,
timeout_seconds=timeout,
),
try:
reachability = (
status.reachability.model_dump(mode="json", exclude_none=True)
if status.reachability
else {}
)
sandbox = SandboxHandle(
sandbox_id=status.sandbox_id,
host=status.host or "",
reachability=reachability,
)
sandbox._owner_execute = owner_execute
sandbox._timeout_seconds = profile.limits.timeout_seconds or 600
def owner_execute(argv, input_text, timeout):
return manager.execute(
sandbox.sandbox_id,
SandboxExecRequest(
command=list(argv),
consumer=consumer,
credential_route_refs=profile.credential_route_refs,
stdin_text=input_text,
timeout_seconds=timeout,
),
)
sandbox._owner_execute = owner_execute
sandbox._timeout_seconds = profile.limits.timeout_seconds or 600
try:
session = selected_rein.start_session(
profile,
{

View file

@ -519,3 +519,37 @@ def test_old_result_records_have_unknown_outcomes():
assert restored.hub_report_status == 'unknown'
assert restored.evidence.session_cleanup == 'unknown'
assert restored.evidence.sandbox_destroy == 'unknown'
@pytest.mark.parametrize('state', [state for state in SandboxState if state != SandboxState.READY])
def test_nonready_owner_handoff_never_starts_rein(state):
manager = MagicMock()
manager.create.return_value = _fake_status().model_copy(update={'state': state})
rein = _FakeRein()
result = run_execution(_request(), catalog=_catalog_with_readiness(), rein=rein, manager=manager)
assert not result.ok
assert result.evidence.failure_stage == 'sandbox_create'
assert result.evidence.session_cleanup == 'not_attempted'
assert result.evidence.sandbox_destroy == 'succeeded'
assert rein.calls == []
manager.execute.assert_not_called()
manager.destroy.assert_called_once_with('sbx1')
def test_reachability_preparation_failure_is_reported_and_destroyed():
manager = MagicMock()
reachability = MagicMock()
reachability.model_dump.side_effect = RuntimeError('private owner detail')
manager.create.return_value = _fake_status().model_copy(update={'reachability': reachability})
rein = _FakeRein()
with patch('glas_harness.gateway.hub.post_progress_event', return_value=True) as post:
result = run_execution(_request(report_to_hub=True), catalog=_catalog_with_readiness(),
rein=rein, manager=manager)
assert not result.ok
assert result.evidence.failure_stage == 'session_start'
assert result.tool_error == 'private owner detail'
assert result.evidence.session_cleanup == 'not_attempted'
assert result.evidence.sandbox_destroy == 'succeeded'
assert 'private owner detail' not in str(post.call_args)
assert rein.calls == []
manager.destroy.assert_called_once_with('sbx1')

View file

@ -249,3 +249,12 @@ production service identity and verified lane activation. Publication is not
deployment. T02 stays waiting, and runtime pinning and combined acceptance
remain required. No new owner inbox message or activation evidence was found
in this review.
## 2026-09-06 digest verification follow-up
Reviewed secrets-engine `6e9c152` and `925d028`: owner corrected correlation
field handling in the flex-auth digest and refreshed fixtures. Its tests also
record an unresolved approval/native action-vocabulary and embedded-claim
digest mismatch for dual-control requests. This is owner integration evidence,
not successful live activation; SECRETS-WP-0009-T03 remains wait. No new
activation receipt was found in the Glas inbox. T02 remains waiting.

View file

@ -4,7 +4,7 @@ type: workplan
title: "Validate sandbox readiness before rein handoff"
domain: infotech
repo: glas-harness
status: active
status: finished
owner: codex
topic_slug: sandbox-handoff-validation
created: "2026-09-06"
@ -23,7 +23,7 @@ normalized failure evidence while preserving destruction attempts.
```task
id: GLAS-WP-0014-T01
status: progress
status: done
priority: high
state_hub_task_id: "64b1e1ab-421b-566d-b2ee-64284446db8a"
```
@ -35,3 +35,11 @@ the returned sandbox, and keep raw error details out of Hub evidence. Preserve
successful behavior and existing cleanup status semantics. Add regression
coverage for non-ready states and malformed reachability; run the full suite
and catalog validation. No owner implementation or profile readiness changes.
## Completion
Completed 2026-09-06. All seven non-READY owner states are rejected before rein
startup and the returned sandbox is destroyed. Reachability preparation runs
inside the session_start failure boundary. Eight regressions failed before the
fix and pass afterwards; full suite 129 passed and catalog validation passed.
No residuals from this fix; real-profile acceptance remains GLAS-WP-0012.