diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index df21467..4074d55 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -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 | diff --git a/docs/channel-contract.md b/docs/channel-contract.md index 30f4d86..18dbbff 100644 --- a/docs/channel-contract.md +++ b/docs/channel-contract.md @@ -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. diff --git a/docs/harness-contract.md b/docs/harness-contract.md index b700479..6724872 100644 --- a/docs/harness-contract.md +++ b/docs/harness-contract.md @@ -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; diff --git a/docs/intakes/residuals.md b/docs/intakes/residuals.md index 6dc7583..ad16c78 100644 --- a/docs/intakes/residuals.md +++ b/docs/intakes/residuals.md @@ -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. +``` diff --git a/src/glas_harness/cli.py b/src/glas_harness/cli.py index 2f8547d..d3344ae 100644 --- a/src/glas_harness/cli.py +++ b/src/glas_harness/cli.py @@ -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") diff --git a/src/glas_harness/gateway.py b/src/glas_harness/gateway.py index 08fb97e..4669329 100644 --- a/src/glas_harness/gateway.py +++ b/src/glas_harness/gateway.py @@ -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, ) ) diff --git a/tests/test_cli.py b/tests/test_cli.py index bf7b215..2654bd6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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 diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 9e50c4a..7eac49a 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -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() diff --git a/workplans/GLAS-WP-0006-consumer-actor-validation.md b/workplans/GLAS-WP-0006-consumer-actor-validation.md new file mode 100644 index 0000000..c9b536c --- /dev/null +++ b/workplans/GLAS-WP-0006-consumer-actor-validation.md @@ -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.