Enforce companion-only credential delivery and refresh activation handoff
Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a0e324-abce-7e51-bb2b-496f097afdb0
This commit is contained in:
parent
303cbf652b
commit
5c6f2b319d
8 changed files with 150 additions and 24 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
# Draft exec_owner for glas-claude-agent-dev-anthropic (SECRETS-WP-0009-T03).
|
# Draft exec_owner for glas-claude-agent-dev-anthropic (SECRETS-WP-0009-T03).
|
||||||
# Not in the catalog: it replaces the pending binding only after
|
# Not in the catalog. Activity Core reports custody and identity live as of
|
||||||
# activity-core-metered-worker-token is seeded and ACTIVITY-WP-0039 is live.
|
# 2026-09-24 (ACTIVITY-WP-0039). Admission still requires current owner/pin
|
||||||
|
# validation and exact per-lane approvals for attended native activation.
|
||||||
exec_owner:
|
exec_owner:
|
||||||
status: configured
|
status: configured
|
||||||
owner: rein-aharness MessagesOwner (metered-once) with sand-boxer runtime boundary
|
owner: rein-aharness MessagesOwner (metered-once) with sand-boxer runtime boundary
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,9 @@ The companion lane must consent in its own catalog entry with
|
||||||
the same stage, declare the field and `exec-env`, and bind no exec owner of its
|
the same stage, declare the field and `exec-env`, and bind no exec owner of its
|
||||||
own. Env names must be unique, must not match the fixed environment or the
|
own. Env names must be unique, must not match the fixed environment or the
|
||||||
primary field's name, and must not use loader or engine credential prefixes.
|
primary field's name, and must not use loader or engine credential prefixes.
|
||||||
Pending owners cannot list companions.
|
Pending owners cannot list companions. A lane declaring `companion_of` cannot
|
||||||
|
be selected as the primary `exec` lane: it is delivered only through a listed
|
||||||
|
primary with a configured owner, even if standalone lane approval exists.
|
||||||
|
|
||||||
Companions are part of the owner binding, so changing a companion's lane, field
|
Companions are part of the owner binding, so changing a companion's lane, field
|
||||||
or env name changes the owner digest and invalidates earlier decisions.
|
or env name changes the owner digest and invalidates earlier decisions.
|
||||||
|
|
@ -120,6 +122,9 @@ consume for action `exec`. No lane's decision covers another lane. After every
|
||||||
gate passes, each value is read through its own lane's AppRole session. A
|
gate passes, each value is read through its own lane's AppRole session. A
|
||||||
failure on any lane starts no child. The binding is checked again after the
|
failure on any lane starts no child. The binding is checked again after the
|
||||||
reads, and all values are injected together and redacted from the output.
|
reads, and all values are injected together and redacted from the output.
|
||||||
|
The delivery helper independently checks that the supplied companions exactly
|
||||||
|
match the pinned lane/field/environment list and still satisfy consent and stage
|
||||||
|
constraints before reading the primary or any companion.
|
||||||
|
|
||||||
Proof: `tests/test_exec_owner_companions.py`, plus
|
Proof: `tests/test_exec_owner_companions.py`, plus
|
||||||
`tests/test_integration_companions.py` on a throwaway OpenBao (two lanes, one
|
`tests/test_integration_companions.py` on a throwaway OpenBao (two lanes, one
|
||||||
|
|
|
||||||
|
|
@ -37,23 +37,27 @@ approval, OpenBao access, provider authentication or production readiness.
|
||||||
|
|
||||||
## Activation requirements
|
## Activation requirements
|
||||||
|
|
||||||
The current engine's production stance refuses before opening the backend:
|
As of 2026-09-27, the shared approval/consume/PDP chain has live evidence from
|
||||||
`production action 'exec' requires a durable access-engine decision record;
|
SECRETS-WP-0010-T03. The Glas catalog still has a pending owner binding and
|
||||||
live production remains disabled`. This refusal was exercised with the proposed
|
refuses exec before approval consumption or backend access. The earlier lack
|
||||||
catalog and service-jwt selection. No real value was requested.
|
of a served decision path is no longer the current activation blocker.
|
||||||
|
|
||||||
Activation depends on SECRETS-WP-0007-T04 (exact production actions) and
|
The metered owner configuration and binding were prepared on 2026-09-23.
|
||||||
SECRETS-WP-0008-T02/T06 (decision consumption and service authority). Require
|
Activity Core reports ACTIVITY-WP-0039 complete on 2026-09-24: custody and the
|
||||||
canonical validated DecisionEnvelope for each protected action, successful consume,
|
separate `rein-aharness-metered@railiance01` identity are live. See the exact
|
||||||
and exact scoped backend authority. This draft cannot authorize itself; an
|
handoff in SECRETS-WP-0011. Its worker token is companion-only; direct exec of
|
||||||
operator browser token or unsafe-demo flag is not a runtime substitute.
|
`activity-core-metered-worker-token` is refused. The intended recipient is the
|
||||||
|
metered MessagesOwner described in [exec owner binding](exec-owner-binding.md),
|
||||||
|
not the historical sandbox helper above.
|
||||||
|
|
||||||
Once those services exist: obtain the reviewed apply authorization, apply this
|
SECRETS-WP-0009-T03 still owns current recipient/pin admission and the attended
|
||||||
exact policy/AppRole with scoped authority, verify positive read and denied
|
activation. Review the draft binding, revalidate installed files and private
|
||||||
metadata/sibling/write access without exposing values, and record delivery-ready
|
state, configure the approved owner, and obtain exact per-action/per-lane
|
||||||
state. Bind approved exec authorization and named engine service authentication
|
approvals. Apply the scoped policy/AppRole, verify positive read and denied
|
||||||
to the sand-boxer owner route. Prove actual provider authentication and a bounded
|
metadata/sibling/write access with an unrelated negative identity, then prove
|
||||||
Glas task, then activate routing and only the validated profile.
|
bounded owner delivery and session revocation. Both lanes must independently
|
||||||
|
pass approval, PDP, consume and delivery readiness. The handoff and draft are
|
||||||
|
not runtime authorization. No production activation was performed in this review.
|
||||||
|
|
||||||
Rotation: store replacement with CAS, stop old runs, verify replacement, revoke
|
Rotation: store replacement with CAS, stop old runs, verify replacement, revoke
|
||||||
predecessor at Anthropic and prove denial. Bao session expiration does not revoke
|
predecessor at Anthropic and prove denial. Bao session expiration does not revoke
|
||||||
|
|
|
||||||
|
|
@ -221,14 +221,28 @@ def exec_with_secret(
|
||||||
f"(allowed {sorted(declared)})"
|
f"(allowed {sorted(declared)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# The caller's resolved lanes must exactly match the pinned recipient.
|
||||||
|
# Refuse missing/substituted companions before reading even the primary.
|
||||||
|
from secrets_engine.exec_owner import companion_specs, resolve_companions
|
||||||
|
|
||||||
|
supplied = [
|
||||||
|
{"catalog": lane.id, "field": lane_field, "env": env_name}
|
||||||
|
for lane, lane_field, env_name in companions
|
||||||
|
]
|
||||||
|
if supplied != companion_specs(entry):
|
||||||
|
raise DeliveryError("companion delivery differs from the catalog-bound exec owner")
|
||||||
|
if companions:
|
||||||
|
if binding_digest is None or mode != "exec-env":
|
||||||
|
raise DeliveryError("companion delivery requires a configured exec owner and exec-env")
|
||||||
|
lanes = {lane.id: lane for lane, _, _ in companions}
|
||||||
|
resolve_companions(entry, lanes.__getitem__)
|
||||||
|
|
||||||
if session_evidence is None:
|
if session_evidence is None:
|
||||||
value = _fetch_value(client, entry, field)
|
value = _fetch_value(client, entry, field)
|
||||||
else:
|
else:
|
||||||
value = _fetch_value(
|
value = _fetch_value(
|
||||||
client, entry, field, session_evidence=session_evidence
|
client, entry, field, session_evidence=session_evidence
|
||||||
)
|
)
|
||||||
if companions and (binding_digest is None or mode != "exec-env"):
|
|
||||||
raise DeliveryError("companion delivery requires a configured exec owner and exec-env")
|
|
||||||
# Every lane is read through its own AppRole session; any failure raises
|
# Every lane is read through its own AppRole session; any failure raises
|
||||||
# before a child exists, and the values already read go out of scope.
|
# before a child exists, and the values already read go out of scope.
|
||||||
extra: dict[str, str] = {}
|
extra: dict[str, str] = {}
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,8 @@ def _check_path(path: Path, *, directory: bool = False, private: bool = False) -
|
||||||
|
|
||||||
|
|
||||||
def validate_delivery_target(entry, field: str, command: list[str], mode: str) -> str | None:
|
def validate_delivery_target(entry, field: str, command: list[str], mode: str) -> str | None:
|
||||||
|
if entry.delivery_config.get("companion_of"):
|
||||||
|
raise DeliveryError("companion-only lane requires delivery through its bound primary owner")
|
||||||
binding = owner_binding(entry)
|
binding = owner_binding(entry)
|
||||||
if binding is None:
|
if binding is None:
|
||||||
return None
|
return None
|
||||||
|
|
|
||||||
|
|
@ -179,3 +179,47 @@ def test_unresolvable_companion_refuses_before_any_gate(bound, monkeypatch, tmp_
|
||||||
command = data["delivery_config"]["exec_owner"]["command"]
|
command = data["delivery_config"]["exec_owner"]["command"]
|
||||||
with pytest.raises(DeliveryError, match="unavailable"):
|
with pytest.raises(DeliveryError, match="unavailable"):
|
||||||
cli.cmd_exec(_cfg(tmp_path), SimpleNamespace(field=None, catalog=entry.id, command=command, mode="exec-env"))
|
cli.cmd_exec(_cfg(tmp_path), SimpleNamespace(field=None, catalog=entry.id, command=command, mode="exec-env"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_companion_only_lane_refuses_standalone_exec_before_gate(monkeypatch, tmp_path):
|
||||||
|
lane = validate_entry(_companion())
|
||||||
|
monkeypatch.setattr(cli, "get_entry", lambda *a: lane)
|
||||||
|
monkeypatch.setattr(cli, "_require_lane_approval", lambda *a, **k: pytest.fail("no gate"))
|
||||||
|
monkeypatch.setattr(cli, "_open_backend", lambda *a, **k: pytest.fail("no backend"))
|
||||||
|
with pytest.raises(DeliveryError, match="companion-only"):
|
||||||
|
cli.cmd_exec(_cfg(tmp_path), SimpleNamespace(
|
||||||
|
field=None, catalog=lane.id, command=["/bin/sh"], mode="exec-env",
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def test_companion_only_lane_refuses_direct_delivery_before_read(monkeypatch):
|
||||||
|
lane = validate_entry(_companion())
|
||||||
|
monkeypatch.setattr(exec_delivery, "_fetch_value", lambda *a, **k: pytest.fail("no read"))
|
||||||
|
with pytest.raises(DeliveryError, match="companion-only"):
|
||||||
|
exec_delivery.exec_with_secret(object(), lane, "worker_token", ["/bin/sh"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("change", ["missing", "extra", "env", "field", "lane", "consent", "stage"])
|
||||||
|
def test_delivery_rechecks_companion_contract_before_any_read(bound, monkeypatch, change):
|
||||||
|
data, _, _ = bound
|
||||||
|
entry = validate_entry(_with_companion(data))
|
||||||
|
lane_data = _companion()
|
||||||
|
if change == "consent":
|
||||||
|
lane_data["delivery_config"] = {}
|
||||||
|
elif change == "stage":
|
||||||
|
lane_data.update(stage="build", path="build/team/worker")
|
||||||
|
elif change == "lane":
|
||||||
|
lane_data["id"] = "another-worker"
|
||||||
|
lane = validate_entry(lane_data)
|
||||||
|
companions = [(lane, "worker_token", "WORKER_TOKEN")]
|
||||||
|
if change == "missing": companions = []
|
||||||
|
elif change == "extra": companions *= 2
|
||||||
|
elif change == "env": companions = [(lane, "worker_token", "API_TOKEN")]
|
||||||
|
elif change == "field": companions = [(lane, "other_field", "WORKER_TOKEN")]
|
||||||
|
monkeypatch.setattr(exec_delivery, "_fetch_value", lambda *a, **k: pytest.fail("no read"))
|
||||||
|
monkeypatch.setattr(exec_delivery, "_spawn", lambda *a, **k: pytest.fail("no child"))
|
||||||
|
with pytest.raises(DeliveryError, match="companion"):
|
||||||
|
exec_delivery.exec_with_secret(
|
||||||
|
object(), entry, "api_token", data["delivery_config"]["exec_owner"]["command"],
|
||||||
|
companions=companions,
|
||||||
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ status: blocked
|
||||||
flavor: implementation
|
flavor: implementation
|
||||||
owner: codex
|
owner: codex
|
||||||
created: "2026-09-05"
|
created: "2026-09-05"
|
||||||
updated: "2026-09-21"
|
updated: "2026-09-27"
|
||||||
state_hub_workstream_id: "40ccc3b4-d046-5a58-8649-e7935f45c974"
|
state_hub_workstream_id: "40ccc3b4-d046-5a58-8649-e7935f45c974"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -50,7 +50,7 @@ synthetic exec-env transport proof passed; no real secret was read.
|
||||||
id: SECRETS-WP-0009-T03
|
id: SECRETS-WP-0009-T03
|
||||||
status: wait
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
blocking_reason: "Shared native chain proved by SECRETS-WP-0010-T03 (2026-09-16): Approval Engine, CCR-2026-0019 reader, requester client, Informed Decision human review, current PDP. Lane-specific residue: operator inputs (placement, spend envelope/model bounds, unrelated negative identity), private SpendPolicy/ledger and owner config (HFACT-WP-0001-T01/T04), configured exec_owner, three human approvals, attended apply/verify/exec/revoke."
|
blocking_reason: "Shared native chain proved by SECRETS-WP-0010-T03. Metered owner provisioned and binding drafted (2026-09-23); Activity Core reports identity live (2026-09-24). Remaining: current recipient/pin admission, unrelated negative identity, configured exec_owner, exact per-action/per-lane approvals and attended apply/verify/exec/revoke."
|
||||||
state_hub_task_id: "f8069c8a-ad6b-5d0b-9a36-c2326699437d"
|
state_hub_task_id: "f8069c8a-ad6b-5d0b-9a36-c2326699437d"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -487,3 +487,19 @@ that it answers before the attended session.
|
||||||
|
|
||||||
Founder decision 2026-09-23, relayed to activity-core (thread `a5449d3f`): the claim-loop token is
|
Founder decision 2026-09-23, relayed to activity-core (thread `a5449d3f`): the claim-loop token is
|
||||||
minted fresh with a coordinated claim-loop restart, not moved (ACTIVITY-WP-0039-T03).
|
minted fresh with a coordinated claim-loop restart, not moved (ACTIVITY-WP-0039-T03).
|
||||||
|
|
||||||
|
|
||||||
|
### 2026-09-27 Activity Core dependency resolved
|
||||||
|
|
||||||
|
Activity Core's 2026-09-24 handoff (`a2eae5f8`, `bfef619d`; source `54ab1e4`)
|
||||||
|
reports the metered identity live, HTTP 200 for its own no-match claim and HTTP
|
||||||
|
403 for a claim as the loop identity. SECRETS-WP-0011 records that return and
|
||||||
|
corrects its former wait reason. The drafted worker identity and label match.
|
||||||
|
|
||||||
|
This resolves the seed/rollout prerequisite in the 2026-09-23 note. It does not
|
||||||
|
activate the native lane. T03 still needs current recipient admission and file
|
||||||
|
pin validation, exact approvals for each protected action/lane, and attended
|
||||||
|
native apply, positive/negative verification, exec and session revocation.
|
||||||
|
The production catalog remains pending. The companion-only delivery guards
|
||||||
|
added under SECRETS-WP-0011-T05 must be included in the host checkout used for
|
||||||
|
activation. No credential read, queue claim or paid run occurred in this review.
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ flavor: implementation
|
||||||
owner: claude-code
|
owner: claude-code
|
||||||
topic_slug: netkingdom
|
topic_slug: netkingdom
|
||||||
created: "2026-09-23"
|
created: "2026-09-23"
|
||||||
updated: "2026-09-23"
|
updated: "2026-09-27"
|
||||||
related_workplans:
|
related_workplans:
|
||||||
- SECRETS-WP-0009
|
- SECRETS-WP-0009
|
||||||
- HFACT-WP-0001
|
- HFACT-WP-0001
|
||||||
|
|
@ -93,7 +93,7 @@ a throwaway OpenBao dev server.
|
||||||
id: SECRETS-WP-0011-T04
|
id: SECRETS-WP-0011-T04
|
||||||
status: wait
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
blocking_reason: "Lane cataloged; token seeded (ACTIVITY-WP-0039-T03 done 2026-09-23). Waits on T04 cutover: railiance-platform store policy and founder go-ahead. Do not apply or deliver until activity-core reports the metered identity authenticates."
|
blocking_reason: "Activity Core reports ACTIVITY-WP-0039 finished and metered identity authentication proved (2026-09-24). Remaining: admit the configured Glas owner, obtain exact per-lane approvals, and perform attended native apply/verify/delivery under SECRETS-WP-0009-T03."
|
||||||
state_hub_task_id: "cf465065-de7a-5d9c-bc80-fee16ffef70d"
|
state_hub_task_id: "cf465065-de7a-5d9c-bc80-fee16ffef70d"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -131,3 +131,43 @@ approval). Not applied. Suite: 467 passed.
|
||||||
Founder go-ahead for ACTIVITY-WP-0039-T04 (ExternalSecret, multi-identity rollout, claim-loop restart)
|
Founder go-ahead for ACTIVITY-WP-0039-T04 (ExternalSecret, multi-identity rollout, claim-loop restart)
|
||||||
relayed 2026-09-24 on activity-core thread `7b51d9c3`. Store policy was done 2026-09-23T18:06Z
|
relayed 2026-09-24 on activity-core thread `7b51d9c3`. Store policy was done 2026-09-23T18:06Z
|
||||||
(CCR-2026-0029/0030).
|
(CCR-2026-0029/0030).
|
||||||
|
|
||||||
|
|
||||||
|
## Enforce companion-only delivery at both entry points
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: SECRETS-WP-0011-T05
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
```
|
||||||
|
|
||||||
|
Review on 2026-09-27 found that `companion_of` consent was checked when resolving
|
||||||
|
companions, but a caller could select that lane directly as the primary `exec`
|
||||||
|
lane with an arbitrary child. The delivery helper also accepted a supplied
|
||||||
|
companion list without comparing it with the pinned owner declaration.
|
||||||
|
|
||||||
|
Refuse standalone companion delivery before approval/backend/read. Require the
|
||||||
|
helper's supplied lane/field/environment list to match the owner declaration,
|
||||||
|
and recheck companion consent and stage before any value is read. Preserve
|
||||||
|
ordinary lanes and the configured multi-lane child. Regression tests cover both
|
||||||
|
entry points, omissions, additions, substituted lane/field/environment, revoked
|
||||||
|
consent and stage mismatch.
|
||||||
|
|
||||||
|
Validation: 476 tests passed, including disposable OpenBao integration tests;
|
||||||
|
layer conformance and `git diff --check` passed. No production credential was
|
||||||
|
requested or delivered.
|
||||||
|
|
||||||
|
## Activity Core handoff reviewed — 2026-09-27
|
||||||
|
|
||||||
|
Owner messages `a2eae5f8-60cf-499b-8f52-dd04ac407924` and
|
||||||
|
`bfef619d-53e0-4b4c-8b92-ef092450e4a1` report ACTIVITY-WP-0039 finished at
|
||||||
|
activity-core `54ab1e4`. The founder-run check on 2026-09-24 returned HTTP 200
|
||||||
|
for the metered identity on a no-match label, and HTTP 403 when that token
|
||||||
|
claimed the loop identity. The existing loop continued to claim with its own
|
||||||
|
fresh token. This is the owner's reported evidence, not a new live check here.
|
||||||
|
|
||||||
|
The former token seeding/store-policy/identity-rollout blocker is resolved.
|
||||||
|
T04 remains wait for native lane activation with SECRETS-WP-0009-T03: current
|
||||||
|
owner admission/pins, exact per-lane approvals and attended apply/verify/exec.
|
||||||
|
The draft binds `rein-aharness-metered@railiance01` and `hfact-metered`, matching
|
||||||
|
the handoff. No production policy, role, catalog binding or credential changed.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue