fix: validate sandbox consumer actors early
Some checks failed
ci / validate (push) Has been cancelled

Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0233b-178d-7162-b92f-31a31ea8ca9b
This commit is contained in:
tegwick 2026-08-23 01:45:35 +02:00
parent ae2a706a68
commit 79bf88a3c4
9 changed files with 150 additions and 2 deletions

View file

@ -14,6 +14,7 @@
| workplan | GLAS-WP-0003 | finished | — | workplans/GLAS-WP-0003-first-channel-extension.md |
| workplan | GLAS-WP-0004 | finished | — | workplans/GLAS-WP-0004-versioned-execution-constellation-profiles.md |
| workplan | GLAS-WP-0005 | blocked | — | workplans/GLAS-WP-0005-sandbox-boundary-runtime-hardening.md |
| workplan | GLAS-WP-0006 | finished | — | workplans/GLAS-WP-0006-consumer-actor-validation.md |
| task | GLAS-0001-T01 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
| task | GLAS-0001-T02 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
| task | GLAS-0001-T03 | done | — | workplans/GLAS-0001-statehub-bootstrap.md |
@ -44,6 +45,8 @@
| task | GLAS-WP-0005-T03 | done | — | workplans/GLAS-WP-0005-sandbox-boundary-runtime-hardening.md |
| task | GLAS-WP-0005-T04 | done | — | workplans/GLAS-WP-0005-sandbox-boundary-runtime-hardening.md |
| task | GLAS-WP-0005-T05 | wait | — | workplans/GLAS-WP-0005-sandbox-boundary-runtime-hardening.md |
| task | GLAS-WP-0006-T01 | done | — | workplans/GLAS-WP-0006-consumer-actor-validation.md |
| intake | GLAS-IN-0001 | done | blue | docs/intakes/residuals.md |
| intake | GLAS-IN-0002 | todo | red | docs/intakes/residuals.md |
| intake | GLAS-IN-0003 | todo | green | docs/intakes/residuals.md |
| intake | GLAS-IN-0004 | todo | green | docs/intakes/residuals.md |

View file

@ -22,6 +22,12 @@ revision. Channels may obtain a profile choice from their own approved
configuration, but must pass it explicitly and must surface resolution refusal
to their caller.
Channels pass `actor` as a governed sandbox consumer type: `adm`, `agt`, or
`atm`. A queue's concrete `worker_id` is not an actor and remains upstream
ownership metadata. For example, a worker named `rein-aharness@railiance01`
invoking ordinary governed agent work passes `actor="agt"`; it does not copy
its worker id into the actor field.
The channel renders the full direct `GatewayResult`. State Hub receives only
the compact `ExecutionEvidence` subset.

View file

@ -15,6 +15,14 @@ title, and description. It may carry correlation and organizational references
leadership/workforce records; they do not transfer organizational authority to
Glas.
`actor` is the governed sandbox consumer type and must be `adm`, `agt`, or
`atm`. It is not a worker instance identifier. Queue consumers retain values
such as `rein-aharness@railiance01` as upstream `worker_id` ownership metadata
and map governed agent execution to `actor="agt"`. Glas validates this before
sandbox creation and returns a normalized `resolution` refusal for invalid
channel/library requests; the CLI rejects invalid choices during argument
parsing.
The gateway returns `GatewayResult` with:
- `ok`, derived from normalized outcome;

View file

@ -101,3 +101,31 @@ description: |
legacy identifiers without C-35 or C-31 warnings.
state_hub_intake_id: "01a02b78-adff-70b5-8088-594784edff23"
```
## GLAS-IN-0004 — Align the mandated ad hoc convention with identifier canon
```yaml
id: GLAS-IN-0004
kind: intake
title: "Make ADHOC workplans valid under the fleet identifier canon"
lane: green
status: todo
priority: medium
owner: repo-manager
repo: glas-harness
origin: residual
origin_ref: GLAS-WP-0006
description: |
The repository's generated agent instructions mandate
workplans/ADHOC-YYYY-MM-DD.md with ADHOC-YYYY-MM-DD-Txx task ids for small,
low-risk fixes. On 2026-08-23, statehub fix-consistency accepted the task id
but Repo Manager identity preflight raised C-35 because the matching
ADHOC-2026-08-23 workplan id is absent from the canon kind registry.
Align the generated convention, Repo Manager identity preflight, and State
Hub synchronization. Either register the documented ad hoc workplan form or
replace the generated instruction with one canonical representation. Prove
the chosen form through registrar-reconcile without a contradictory identity
warning. Do not grandfather the one discarded local attempt; no UUID was
assigned and GLAS-WP-0006 is the authoritative record for that work.
```

View file

@ -21,7 +21,12 @@ def main(argv: list[str] | None = None) -> int:
run.add_argument("--repo", required=True, help="Local repo path to mirror into the sandbox")
run.add_argument("--title", required=True)
run.add_argument("--description", required=True)
run.add_argument("--actor", default="agt")
run.add_argument(
"--actor",
choices=("adm", "agt", "atm"),
default="agt",
help="Governed execution actor type; queue worker identifiers belong upstream",
)
run.add_argument("--project", default="glas-harness")
run.add_argument("--no-hub", action="store_true", help="Skip the gateway's own hub reporting")

View file

@ -10,6 +10,7 @@ import time
import uuid
from datetime import UTC, datetime
from pydantic import ValidationError
from sandboxer.core.manager import SandboxManager
from sandboxer.models import Consumer, SandboxCreateRequest
@ -44,6 +45,18 @@ def _request_refs(request: ExecutionRequest) -> dict:
return {key: value for key, value in refs.items() if value not in (None, [], "")}
def _consumer_from_request(request: ExecutionRequest) -> Consumer:
"""Validate the governed execution identity before sandbox provisioning."""
try:
return Consumer(actor=request.actor, project=request.project)
except ValidationError as exc:
raise ValueError(
"execution actor must be a governed consumer type: adm, agt, or atm; "
"queue worker identifiers are not execution actors"
) from exc
def run_execution(
request: ExecutionRequest,
*,
@ -69,6 +82,7 @@ def run_execution(
error: str | None = None
try:
consumer = _consumer_from_request(request)
profile, descriptor = catalog.resolve(request.harness_profile_ref)
selected_rein = rein or catalog.build_rein(profile, descriptor)
except Exception as exc:
@ -102,7 +116,7 @@ def run_execution(
SandboxCreateRequest(
profile=profile.sandbox_profile,
inputs={"repo": request.repo},
consumer=Consumer(actor=request.actor, project=request.project),
consumer=consumer,
ttl=None,
)
)

View file

@ -1,6 +1,8 @@
import json
from pathlib import Path
import pytest
from glas_harness.cli import main
from glas_harness.contract import ExecutionRequest
@ -19,3 +21,24 @@ def test_execution_request_example_matches_contract() -> None:
assert request.harness_profile_ref == "harness.agent-dev-local@1.0.0"
assert request.assignment_ref == "role-assignment:agent-7:42"
def test_run_command_rejects_worker_identifier_as_actor() -> None:
with pytest.raises(SystemExit) as exc_info:
main(
[
"run",
"--harness-profile",
"harness.agent-dev-local@1.0.0",
"--repo",
"/tmp/repo",
"--title",
"t",
"--description",
"d",
"--actor",
"rein-aharness@railiance01",
]
)
assert exc_info.value.code == 2

View file

@ -139,6 +139,22 @@ def test_run_execution_refuses_unknown_profile_before_sandbox() -> None:
manager.create.assert_not_called()
def test_run_execution_refuses_worker_identifier_before_sandbox() -> None:
manager = MagicMock()
request = _request().model_copy(update={"actor": "rein-aharness@railiance01"})
result = run_execution(request, rein=_FakeRein(), manager=manager)
assert result.ok is False
assert result.evidence.outcome == "refused"
assert result.evidence.failure_stage == "resolution"
assert result.evidence.error == (
"execution actor must be a governed consumer type: adm, agt, or atm; "
"queue worker identifiers are not execution actors"
)
manager.create.assert_not_called()
def test_hub_receives_normalized_evidence_without_raw_output() -> None:
manager = MagicMock()
manager.create.return_value = _fake_status()

View file

@ -0,0 +1,45 @@
---
id: GLAS-WP-0006
type: workplan
title: "Fail fast on invalid sandbox consumer actors"
domain: infotech
repo: glas-harness
status: finished
owner: codex
topic_slug: sandbox-consumer-actor-validation
created: "2026-08-23"
updated: "2026-08-23"
---
# Fail fast on invalid sandbox consumer actors
## Validate the actor boundary before provisioning
```task
id: GLAS-WP-0006-T01
status: done
priority: high
```
The Activity Core live pilot passed its queue/profile boundary but supplied the
rein worker identifier as `ExecutionRequest.actor`. Sand-boxer accepts only the
governed consumer actor types `adm`, `agt`, and `atm`, so the mismatch was found
during sandbox creation.
Validate and normalize this boundary before provisioning, retain `worker_id` as
an upstream queue-ownership field rather than an execution actor, document the
distinction, and test that an invalid actor produces resolution refusal without
calling sand-boxer.
**Completed 2026-08-23:** the gateway now validates the sand-boxer `Consumer`
while resolving the request and returns a normalized `resolution` refusal
without constructing or calling a sandbox manager when the actor is invalid.
The CLI constrains `--actor` to `adm|agt|atm`; contract and channel guidance
separates the upstream queue `worker_id` from the governed execution actor.
Unit coverage proves both gateway and CLI refusal paths. All 58 tests and the
packaged profile catalog pass.
The repo-mandated `ADHOC-YYYY-MM-DD` workplan form was attempted first, but
Repo Manager rejected the otherwise instruction-conformant identifier as C-35.
That fleet convention mismatch is retained as residual `GLAS-IN-0004`; this
completed implementation uses the accepted normal workplan identity.