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. successful readiness check.
- A synchronous outer lifecycle: resolve, create, start, dispatch `run_task`, - A synchronous outer lifecycle: resolve, create, start, dispatch `run_task`,
summarize, clean up and destroy. Manager initialization failures return 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 after session failures, with independent cleanup/destruction outcomes even
when an earlier execution error remains primary. This is not a durable when an earlier execution error remains primary. This is not a durable
recovery/retry service. recovery/retry service.
@ -67,7 +68,7 @@ universal redaction guarantee for arbitrary caller/provider data.
| Surface | Evidence available | Current limit | | 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 | | 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 | | 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 | | 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 occurred. Consumers using older strict schemas must update to accept these
fields before consuming new serialized results. No profile pins or readiness fields before consuming new serialized results. No profile pins or readiness
states change with this addition. 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 pydantic import ValidationError
from sandboxer.core.manager import SandboxManager 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 import hub
from glas_harness.contract import ( from glas_harness.contract import (
@ -124,38 +124,40 @@ def run_execution(
) )
) )
sandbox_id = status.sandbox_id sandbox_id = status.sandbox_id
if status.state != SandboxState.READY:
raise RuntimeError("sandbox owner did not return a ready sandbox")
except Exception as exc: except Exception as exc:
failure_stage = "sandbox_create" failure_stage = "sandbox_create"
error = str(exc) error = str(exc)
raise raise
reachability = ( try:
status.reachability.model_dump(mode="json", exclude_none=True) reachability = (
if status.reachability status.reachability.model_dump(mode="json", exclude_none=True)
else {} if status.reachability
) else {}
sandbox = SandboxHandle( )
sandbox_id=status.sandbox_id, sandbox = SandboxHandle(
host=status.host or "", sandbox_id=status.sandbox_id,
reachability=reachability, 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,
),
) )
sandbox._owner_execute = owner_execute def owner_execute(argv, input_text, timeout):
sandbox._timeout_seconds = profile.limits.timeout_seconds or 600 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( session = selected_rein.start_session(
profile, profile,
{ {

View file

@ -519,3 +519,37 @@ def test_old_result_records_have_unknown_outcomes():
assert restored.hub_report_status == 'unknown' assert restored.hub_report_status == 'unknown'
assert restored.evidence.session_cleanup == 'unknown' assert restored.evidence.session_cleanup == 'unknown'
assert restored.evidence.sandbox_destroy == '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 deployment. T02 stays waiting, and runtime pinning and combined acceptance
remain required. No new owner inbox message or activation evidence was found remain required. No new owner inbox message or activation evidence was found
in this review. 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" title: "Validate sandbox readiness before rein handoff"
domain: infotech domain: infotech
repo: glas-harness repo: glas-harness
status: active status: finished
owner: codex owner: codex
topic_slug: sandbox-handoff-validation topic_slug: sandbox-handoff-validation
created: "2026-09-06" created: "2026-09-06"
@ -23,7 +23,7 @@ normalized failure evidence while preserving destruction attempts.
```task ```task
id: GLAS-WP-0014-T01 id: GLAS-WP-0014-T01
status: progress status: done
priority: high priority: high
state_hub_task_id: "64b1e1ab-421b-566d-b2ee-64284446db8a" 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 successful behavior and existing cleanup status semantics. Add regression
coverage for non-ready states and malformed reachability; run the full suite coverage for non-ready states and malformed reachability; run the full suite
and catalog validation. No owner implementation or profile readiness changes. 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.