fix: isolate rein results and normalize manager startup failures
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 01:13:23 +02:00
parent 73cee37ad8
commit 53f2abf7e0
8 changed files with 152 additions and 32 deletions

View file

@ -18,7 +18,7 @@ passes and apply-plan refuses the incomplete request.
| Provider expiry | `2027-01-31T22:00:00+01:00` = `2027-01-31T21:00:00Z` (user supplied) |
| KV-v2 entry | `platform/workloads/glas-harness/claude-agent-dev` |
| Only secret field | `ANTHROPIC_API_KEY` |
| Read policy name | `workload-kv-read-glas-claude-agent-dev` |
| Proposed read policy name | `se-prod-glas-claude-agent-dev-anthropic` |
| Proposed route | `glas-claude-agent-dev-anthropic` |
| Consumer | Sand-boxer owner delivery to the selected Claude workload |
@ -119,3 +119,11 @@ policy name; neither policy has been applied by this work. Adoption is tracked
in SECRETS-WP-0009. The production exec gate refuses before OpenBao while
SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06 remain unresolved.
No production profile selects the unactivated route.
Owner update received 2026-09-06 (message
`3bd23bc9-e863-4f1f-8c76-415812ec0656`): secrets-engine reports the local
authorization join fixed at `627810b`. Live activation still waits on the
approval-engine ActionAuthorization claim endpoint and access-engine Check
service, then configuration, per-lane approval and positive/negative checks.
The owner explicitly distinguishes these durable objects from State Hub
decisions. This update does not activate the route or verify the provider key.

View file

@ -108,11 +108,11 @@ def run_execution(
_report(request, result)
return result
manager = manager or SandboxManager()
status = None
session = None
try:
try:
manager = manager if manager is not None else SandboxManager()
status = manager.create(
SandboxCreateRequest(
profile=profile.sandbox_profile,

View file

@ -53,7 +53,6 @@ class ReinAharness(Rein):
self.model = model
self.tool_profile = tool_profile
self.budget_tokens = budget_tokens
self._last_result: dict[str, Any] = {}
def _bin(self, transport: ExecutionTransport) -> str:
try:
@ -109,18 +108,18 @@ class ReinAharness(Rein):
proc = transport.run(argv, timeout=session["timeout_seconds"])
ok = proc.returncode == 0
output, events = self._split_stream_events(proc.stdout)
self._last_result = parse_json_object(output)
last_result = session["last_result"] = parse_json_object(output)
return ToolResult(
ok=ok,
output=output,
error=None if ok else (proc.stderr or self._last_result.get("reason")),
error=None if ok else (proc.stderr or last_result.get("reason")),
events=events,
events_completeness="complete" if self.stream_tool_events else "unavailable",
tokens_spent=self._last_result.get("tokens_spent"),
duration_s=self._last_result.get("execution_time_s"),
resolved_model=self._last_result.get("model") or self.model,
tokens_spent=last_result.get("tokens_spent"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
metadata={
"tool_profile": self._last_result.get("tool_profile") or self.tool_profile,
"tool_profile": last_result.get("tool_profile") or self.tool_profile,
},
)
@ -147,10 +146,11 @@ class ReinAharness(Rein):
def end_session(self, session: dict[str, Any]) -> ExecutionSummary:
transport: ExecutionTransport = session["transport"]
last_result = session.get("last_result", {})
head_after = transport.git_head()
committed = bool(head_after) and head_after != session.get("head_before")
reported_ok = bool(self._last_result.get("ok", committed))
reason = self._last_result.get("reason") or None
reported_ok = bool(last_result.get("ok", committed))
reason = last_result.get("reason") or None
outcome = "succeeded" if committed and reported_ok else (
"refused" if isinstance(reason, str) and reason.startswith("refused:") else "failed"
)
@ -159,13 +159,13 @@ class ReinAharness(Rein):
committed=committed,
outcome=outcome,
reason=reason,
tokens_spent=self._last_result.get("tokens_spent"),
duration_s=self._last_result.get("execution_time_s"),
resolved_model=self._last_result.get("model") or self.model,
tokens_spent=last_result.get("tokens_spent"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
artifacts=[head_after] if committed and head_after else [],
metadata={
"tool_profile": self._last_result.get("tool_profile") or self.tool_profile,
"persona_source": self._last_result.get("persona_source"),
"tool_profile": last_result.get("tool_profile") or self.tool_profile,
"persona_source": last_result.get("persona_source"),
},
)

View file

@ -41,7 +41,6 @@ class ReinOpenWeights(Rein):
self.max_turns = max_turns
self.budget_tokens = budget_tokens
self.tool_profile = tool_profile
self._last_result: dict[str, Any] = {}
def _bin(self, transport: ExecutionTransport) -> str:
try:
@ -94,27 +93,28 @@ class ReinOpenWeights(Rein):
argv += ["--tool-profile", self.tool_profile]
proc = transport.run(argv, timeout=session["timeout_seconds"])
ok = proc.returncode == 0
self._last_result = parse_json_object(proc.stdout)
last_result = session["last_result"] = parse_json_object(proc.stdout)
return ToolResult(
ok=ok,
output=proc.stdout,
error=None if ok else (proc.stderr or self._last_result.get("reason")),
error=None if ok else (proc.stderr or last_result.get("reason")),
events_completeness="unavailable",
tokens_spent=self._last_result.get("tokens_spent"),
duration_s=self._last_result.get("execution_time_s"),
resolved_model=self._last_result.get("model") or self.model,
tokens_spent=last_result.get("tokens_spent"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
metadata={
"turns": self._last_result.get("turns"),
"tool_profile": self._last_result.get("tool_profile") or self.tool_profile,
"turns": last_result.get("turns"),
"tool_profile": last_result.get("tool_profile") or self.tool_profile,
},
)
def end_session(self, session: dict[str, Any]) -> ExecutionSummary:
transport: ExecutionTransport = session["transport"]
last_result = session.get("last_result", {})
head_after = transport.git_head()
committed = bool(head_after) and head_after != session.get("head_before")
reported_ok = bool(self._last_result.get("ok", committed))
reason = self._last_result.get("reason") or None
reported_ok = bool(last_result.get("ok", committed))
reason = last_result.get("reason") or None
outcome = "succeeded" if committed and reported_ok else (
"refused" if reason == "no OpenRouter credential resolved" else "failed"
)
@ -123,13 +123,13 @@ class ReinOpenWeights(Rein):
committed=committed,
outcome=outcome,
reason=reason,
tokens_spent=self._last_result.get("tokens_spent"),
duration_s=self._last_result.get("execution_time_s"),
resolved_model=self._last_result.get("model") or self.model,
tokens_spent=last_result.get("tokens_spent"),
duration_s=last_result.get("execution_time_s"),
resolved_model=last_result.get("model") or self.model,
artifacts=[head_after] if committed and head_after else [],
metadata={
"turns": self._last_result.get("turns"),
"tool_profile": self._last_result.get("tool_profile") or self.tool_profile,
"turns": last_result.get("turns"),
"tool_profile": last_result.get("tool_profile") or self.tool_profile,
},
)

View file

@ -401,3 +401,17 @@ def test_teardown_failure_is_visible_in_evidence() -> None:
assert result.evidence.failure_stage == "teardown"
assert result.evidence.error == "teardown failed; inspect direct caller error"
assert result.tool_error == "cannot teardown"
def test_manager_initialization_failure_returns_redacted_evidence():
with patch("glas_harness.gateway.SandboxManager", side_effect=RuntimeError(
"private manager configuration"
)), patch("glas_harness.gateway.hub.post_progress_event") as post:
result = run_execution(_request(report_to_hub=True),
catalog=_catalog_with_readiness(), rein=_FakeRein())
assert not result.ok
assert result.evidence.failure_stage == "sandbox_create"
assert result.evidence.sandbox_id is None
assert result.tool_error == "private manager configuration"
assert "private manager configuration" not in str(post.call_args)
post.assert_called_once()

View file

@ -0,0 +1,36 @@
"""A reusable adapter must keep each sandbox's result in its own session."""
import json
from unittest.mock import MagicMock
import pytest
from glas_harness.contract import ToolCall
from glas_harness.reins.rein_aharness import ReinAharness
from glas_harness.reins.rein_openweights import ReinOpenWeights
from glas_harness.transport import ExecutionTransport
@pytest.mark.parametrize("adapter", [ReinAharness, ReinOpenWeights])
def test_interleaved_sessions_keep_their_own_outcomes_and_usage(adapter):
rein = adapter()
sessions = []
for index, ok in enumerate([True, False]):
transport = MagicMock(spec=ExecutionTransport)
transport.resolve_executable.return_value = "/runtime/rein"
transport.git_head.return_value = f"head-{index}"
transport.run.return_value = MagicMock(returncode=0 if ok else 1, stderr="",
stdout=json.dumps({"ok": ok, "tokens_spent": 10 + index,
"model": f"model-{index}", "reason": None if ok else "failed"}))
sessions.append({"transport": transport, "task_file": f"/workspace-{index}/task",
"timeout_seconds": 30, "head_before": "baseline"})
# Finish A only after B has reported its different result.
for session in sessions:
rein.dispatch_tool(session, ToolCall(name="run_task"))
first, second = [rein.end_session(session) for session in sessions]
assert first.outcome == "succeeded"
assert first.tokens_spent == 10
assert first.resolved_model == "model-0"
assert second.outcome == "failed"
assert second.tokens_spent == 11
assert second.resolved_model == "model-1"

View file

@ -0,0 +1,51 @@
---
id: GLAS-WP-ADHOC-2026-09-06
type: workplan
title: "Repository review: session isolation and startup failure reporting"
domain: infotech
repo: glas-harness
status: finished
owner: codex
topic_slug: repository-review
created: "2026-09-06"
updated: "2026-09-06"
---
# Repository review
The working tree was clean at review start. GLAS-WP-0012 is the only unfinished
workplan. Its real-key proof still waits on owner activation and runtime
acceptance; the latest owner update is recorded there. These bounded local
fixes do not change profile readiness. No residuals from these fixes.
## Isolate reusable rein session results
```task
id: ADHOC-2026-09-06-T01
status: done
priority: medium
```
Both adapters stored the parsed CLI response on the adapter instance. When two
sessions interleaved, ending the first used the second session's outcome,
model and usage. Store the response in its session and read it from there.
The regression test reproduces the mixed outcome in both adapters before the
fix and validates distinct outcomes/model/usage afterwards.
## Normalize sandbox manager initialization failure
```task
id: ADHOC-2026-09-06-T02
status: done
priority: medium
```
Move default manager construction inside the sandbox-create error boundary.
Initialization failure now returns a failed GatewayResult with sandbox_create
stage and no sandbox id. Direct callers retain the error; Hub evidence omits
its raw contents. The regression test failed with an escaping exception before
the fix and passes afterwards.
Validation: full suite 104 passed; profile catalog validation passed with
existing readiness unchanged. Reviewed credential owner update and refreshed
the proposed policy reference in docs/anthropic-workload-key.md.

View file

@ -8,7 +8,7 @@ status: blocked
owner: codex
topic_slug: first-local-profile-production-proof
created: "2026-09-05"
updated: "2026-09-05"
updated: "2026-09-06"
state_hub_workstream_id: "170bf1ae-337f-5553-8d1e-03b07100e08f"
---
@ -223,3 +223,14 @@ policy/AppRole proposal and activation now live in SECRETS-WP-0009. Its
production exec refuses before OpenBao because durable access-engine decision
records and scoped service authority remain unavailable. T02 remains waiting
on that activation, pinned Claude startup and real provider/task acceptance.
## 2026-09-06 credential owner update
Reviewed secrets-engine message `3bd23bc9-e863-4f1f-8c76-415812ec0656`.
Owner reports the local authorization join implemented at `627810b`. Remaining
native activation depends on approval-engine serving the durable
ActionAuthorization claim endpoint and access-engine serving Check, followed
by configuration, per-lane approval and positive/negative verification.
State Hub decisions are not a substitute for the durable authorization object.
SECRETS-WP-0009-T03 and this plan's T02 remain waiting; runtime pinning and
combined real proof remain required. No readiness change or real-key read.