Advance dependency roots with guarded source implementations

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a10391-6355-7b41-b0fa-3e14e874f091
This commit is contained in:
tegwick 2026-10-04 11:01:47 +02:00
parent 20eef043ed
commit 9d10fba4cb
5 changed files with 263 additions and 8 deletions

View file

@ -98,7 +98,7 @@ one, per `MASON-WP-0001`'s own scoping.
## Binding OpenBao approval to execution inputs
The AppRole and Kubernetes-auth builders additionally require two frontmatter
The AppRole, Kubernetes-auth and attended OIDC matrix builders require two frontmatter
fields: `build_spec` and `approved_spec_sha256`. `build_spec` is the complete
value-free output of `ops_mason.executor.build_spec_document(spec)`. It includes
the engine, exact policy/role/path and identity bindings, token bounds, policy
@ -148,8 +148,8 @@ by this format migration.
The existing AppRole engine creates only exact KV-v2 read lanes: one literal
entry path, `read` on data and metadata, no wildcard, empty/parent path segment,
HCL injection, or write capability. OIDC matrix writers remain a separate,
blocked contract under MASON-WP-0005. Role and policy names must be single
HCL injection, or write capability. The separate attended OIDC matrix engine is implemented under MASON-WP-0005;
its live construction remains blocked on identity, platform review and plan approval. Role and policy names must be single
literal names; Kubernetes service-account names/namespaces must be explicit,
nonempty, unique DNS labels. Token use counts must be explicit nonnegative
integers (zero is deliberately unlimited when approved); TTLs are positive
@ -185,3 +185,37 @@ Do not blindly retry or delete them: inspect structure and audit metadata,
resolve/revoke any partial issuance through the scoped custody procedure, and
review a fresh delivery destination before retrying. The executor does not
claim transactional rollback or automatically widen its revocation authority.
### Attended OIDC operator matrix
`OIDCKVSpec` and `build_oidc_kv_lane` implement the ops-mason side of the
fluid-telegram four-entry matrix. Draft and review `build_spec_document(spec)`
and its digest through the same exact-spec approval process. The spec requires
campaign coordinates, one owner-confirmed group, identity-confirmation and
platform-matrix-review references, explicit loopback CLI callbacks, policy/role
names and token bounds. These references are review inputs, not authorization
tokens or proof that an owner accepted a draft.
The renderer emits four literal KV-v2 data stanzas: operator-app read;
operator-session and bot-token create/read/update; redaction-salt create/read.
It adds only the exact token revoke-self update endpoint. There are no metadata,
parent, sibling, wildcard, deletion or salt-update grants. The engine writes
policy/auth-role structure only; it never reads/writes KV values or issues a
login token. The approved role has one bound group, no default policy, explicit
15-minute initial / 30-minute maximum lifetime caps and non-periodic tokens.
Initial callback support is deliberately limited to owner-reviewed
`http://localhost:8250/oidc/callback` and
`http://127.0.0.1:8250/oidc/callback`. A private UI callback or additional MFA
claim shape requires matching owner-confirmed contract work before extending
this engine. A group parameter or a local test does not establish installed
issuer/MFA assurance. MASON-WP-0005-T01/T03/T05 keep those live obligations.
On failure the engine suppresses Bao output and does not claim a successful
audit receipt. It can leave a written policy if the auth-role write fails;
inspect object/audit metadata and use the approved rollback procedure before a
retry. Metadata-only local tests do not prove installed KV create/update/CAS
behavior or agent-policy effective denial. Those remain native owner checks.
Platform CCR request-type support and review remain a separate T02 dependency;
the generic read-lane CCR renderer still must refuse this write matrix.

View file

@ -0,0 +1,19 @@
{
"workplan": "MASON-WP-0005",
"task": "T02",
"status": "progress",
"actual_source_tests": 92,
"elapsed_seconds": 1.33,
"engine": "openbao-oidc-kv-read-write",
"execution": "mock subprocess only; no OpenBao calls",
"platform_ccr_extension": "not implemented; pending owner support/review",
"identity": "unconfirmed live group/MFA/callbacks remain T01",
"construction_plan": "draft unchanged",
"remaining": [
"platform contract support/review",
"confirmed IAM claim and assurance",
"live survey",
"exact construction approval",
"native effective-policy and salt/CAS proof"
]
}

View file

@ -76,6 +76,107 @@ class KubernetesKVSpec:
bao_bin: str = "bao"
@dataclass(frozen=True)
class OIDCKVSpec:
"""Attended four-entry operator matrix; all identity evidence is reviewed input."""
policy_name: str
role_name: str
campaign_path: str
bound_group: str
allowed_redirect_uris: tuple[str, ...]
identity_confirmation_ref: str
platform_matrix_review_ref: str
auth_mount: str = "netkingdom"
token_ttl: str = "15m"
token_max_ttl: str = "30m"
bao_bin: str = "bao"
audit_log_path: Path | None = None
_OPERATOR_MATRIX = (
("operator-app", ("read",)),
("operator-session", ("create", "read", "update")),
("bot-token", ("create", "read", "update")),
("redaction-salt", ("create", "read")),
)
def oidc_policy_hcl(spec: OIDCKVSpec) -> str:
"""Render exact KV data paths plus self-revocation; no metadata grants."""
_validate_oidc_spec(spec)
mount, rest = spec.campaign_path.split("/", 1)
stanzas = []
for entry, caps in _OPERATOR_MATRIX:
encoded = ", ".join(json.dumps(c) for c in caps)
stanzas.append(f'path "{mount}/data/{rest}/{entry}" {{\n capabilities = [{encoded}]\n}}\n')
stanzas.append('path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n')
return "\n".join(stanzas)
def _validate_oidc_spec(spec: OIDCKVSpec) -> None:
from urllib.parse import urlsplit
for value, label in ((spec.role_name, "OIDC role"), (spec.auth_mount, "auth mount"),
(spec.bound_group, "bound group")):
_name(value, label)
if not isinstance(spec.campaign_path, str):
raise BuildRefused("campaign path must be literal")
parts = spec.campaign_path.split("/")
if len(parts) != 5 or parts[0:2] != ["platform", "workloads"] or parts[3] != "fluid-telegram":
raise BuildRefused("operator matrix requires platform/workloads/<tenant>/fluid-telegram/<campaign>")
for part in parts:
_name(part, "campaign path segment")
for ref in (spec.identity_confirmation_ref, spec.platform_matrix_review_ref):
if not isinstance(ref, str) or not ref.strip() or ref.strip().lower() in {"pending", "unconfirmed", "unknown", "draft"}:
raise BuildRefused("confirmed identity and platform matrix review references are required")
ttl, maximum = _duration(spec.token_ttl, "token_ttl"), _duration(spec.token_max_ttl, "token_max_ttl")
if not 0 < ttl <= 900 or not ttl <= maximum <= 1800:
raise BuildRefused("attended OIDC token bounds must be at most 15m/30m")
if not isinstance(spec.allowed_redirect_uris, tuple) or not spec.allowed_redirect_uris:
raise BuildRefused("explicit OIDC callback tuple required")
if len(set(spec.allowed_redirect_uris)) != len(spec.allowed_redirect_uris):
raise BuildRefused("duplicate OIDC callback")
for uri in spec.allowed_redirect_uris:
if not isinstance(uri, str):
raise BuildRefused("OIDC callback must be a URI")
try:
u = urlsplit(uri)
valid = (u.scheme == "http" and u.hostname in {"localhost", "127.0.0.1"}
and u.port == 8250 and u.path == "/oidc/callback" and not u.username
and not u.password and not u.query and not u.fragment)
except ValueError:
valid = False
if not valid:
raise BuildRefused("initial matrix engine accepts only reviewed loopback CLI callbacks on port 8250")
def oidc_role_document(spec: OIDCKVSpec) -> dict:
_validate_oidc_spec(spec)
return {
"role_type": "oidc", "user_claim": "sub", "groups_claim": "groups",
"oidc_scopes": ["openid", "profile", "email", "groups"],
"bound_claims": {"groups": spec.bound_group},
"allowed_redirect_uris": list(spec.allowed_redirect_uris),
"token_policies": [spec.policy_name], "token_no_default_policy": True,
"token_ttl": spec.token_ttl, "token_max_ttl": spec.token_max_ttl,
"token_explicit_max_ttl": spec.token_max_ttl, "token_num_uses": 0,
}
def build_oidc_kv_lane(plan: ConstructionPlan, spec: OIDCKVSpec) -> dict[str, str]:
"""Write only policy/auth structure after fresh exact-spec approval."""
spec = replace(spec)
plan = _approved_build(plan, spec)
_run(spec.bao_bin, ["policy", "write", spec.policy_name, "-"], input_text=oidc_policy_hcl(spec))
_run(spec.bao_bin, ["write", f"auth/{spec.auth_mount}/role/{spec.role_name}", "-"],
input_text=json.dumps(oidc_role_document(spec)))
objects = {"approved_spec_sha256": plan.approved_spec_sha256,
"policy_name": spec.policy_name, "oidc_role_name": spec.role_name,
"campaign_path": spec.campaign_path}
record_build(plan_id=plan.id, plan_path=str(plan.path), approved_by=plan.approved_by or "",
approved_at=plan.approved_at or "", objects=objects, log_path=spec.audit_log_path)
return objects
def _run(bao_bin: str, args: list[str], input_text: str | None = None) -> str:
try:
proc = subprocess.run(
@ -131,7 +232,7 @@ def _hash(value: str) -> None:
raise BuildRefused("reused policy requires an exact SHA-256 content pin")
def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec | OIDCKVSpec) -> None:
_name(spec.policy_name, "policy name")
_duration(spec.token_ttl, "token_ttl")
if not isinstance(spec.bao_bin, str) or not spec.bao_bin.strip():
@ -160,6 +261,8 @@ def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
spec.delivery_dir, spec.delivery_dir / "role_id", spec.delivery_dir / "secret_id"
}:
raise BuildRefused("audit path must not overlap credential delivery")
elif isinstance(spec, OIDCKVSpec):
_validate_oidc_spec(spec)
else:
_name(spec.role_name, "Kubernetes role name")
_hash(spec.policy_sha256)
@ -173,7 +276,7 @@ def _validate_spec(spec: AppRoleKVSpec | KubernetesKVSpec) -> None:
raise BuildRefused("duplicate service account binding")
def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec) -> dict:
def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec | OIDCKVSpec) -> dict:
"""Render the complete, value-free execution input for phase-3 review."""
_validate_spec(spec)
fields = asdict(spec)
@ -183,7 +286,7 @@ def build_spec_document(spec: AppRoleKVSpec | KubernetesKVSpec) -> dict:
elif isinstance(value, tuple):
fields[key] = list(value)
return {
"engine": "openbao-approle-kv" if isinstance(spec, AppRoleKVSpec) else "openbao-kubernetes-kv",
"engine": "openbao-approle-kv" if isinstance(spec, AppRoleKVSpec) else "openbao-oidc-kv-read-write" if isinstance(spec, OIDCKVSpec) else "openbao-kubernetes-kv",
**fields,
}

77
tests/test_oidc_matrix.py Normal file
View file

@ -0,0 +1,77 @@
from dataclasses import replace
from unittest.mock import MagicMock, patch
import json
import pytest
from ops_mason.executor import OIDCKVSpec, BuildRefused, BuildError, oidc_policy_hcl, oidc_role_document, build_oidc_kv_lane, build_spec_document
from test_executor import _plan
def spec(tmp_path):
return OIDCKVSpec(policy_name='workload-kv-fluid-telegram-hall-of-helix-operator',
role_name='fluid-telegram-hall-of-helix-workload-kv',
campaign_path='platform/workloads/coulomb/fluid-telegram/hall-of-helix',
bound_group='reviewed-test-operators', allowed_redirect_uris=('http://localhost:8250/oidc/callback',),
identity_confirmation_ref='test-only:iam-owner-receipt', platform_matrix_review_ref='test-only:platform-owner-receipt',
audit_log_path=tmp_path/'audit.jsonl')
def test_matrix_has_exact_data_scope_and_write_once_salt(tmp_path):
s=spec(tmp_path);h=oidc_policy_hcl(s)
assert h.count('platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/')==4
assert 'metadata/' not in h and '*' not in h and '/destroy/' not in h
salt=h.split('/redaction-salt"')[1].split('}')[0]
assert '"create", "read"' in salt and '"update"' not in salt
app=h.split('/operator-app"')[1].split('}')[0]
assert '"read"' in app and '"create"' not in app
assert 'auth/token/revoke-self' in h
@pytest.mark.parametrize('field,value',[
('campaign_path','platform/workloads/coulomb/fluid-telegram/*'),
('campaign_path','platform/workloads/coulomb/fluid-telegram'),
('campaign_path','platform/workloads/coulomb/fluid-telegram/../sibling'),
('campaign_path','platform/workloads/coulomb/another-workload/campaign'),
('bound_group','*'),('bound_group','group,other'),
('identity_confirmation_ref','unconfirmed'),('platform_matrix_review_ref','pending'),
('allowed_redirect_uris',('https://public.example/ui/callback',)),
('allowed_redirect_uris',('http://user@localhost:8250/oidc/callback',)),
('allowed_redirect_uris',('http://localhost:8250/oidc/callback?x=1',)),
('allowed_redirect_uris',()),('allowed_redirect_uris',('http://localhost:bad/oidc/callback',)),
('token_ttl','16m'),('token_max_ttl','31m'),('token_max_ttl','1m')])
def test_refuses_unsafe_matrix_before_bao(tmp_path,field,value):
s=replace(spec(tmp_path),**{field:value})
with patch('ops_mason.executor.subprocess.run') as run:
with pytest.raises(BuildRefused):build_oidc_kv_lane(_plan(tmp_path,spec=spec(tmp_path)),s)
run.assert_not_called()
def test_draft_and_changed_approval_never_execute(tmp_path):
s=spec(tmp_path)
with patch('ops_mason.executor.subprocess.run') as run:
with pytest.raises(BuildRefused):build_oidc_kv_lane(_plan(tmp_path,status='draft',spec=s),s)
p=_plan(tmp_path,spec=s)
with pytest.raises(BuildRefused):build_oidc_kv_lane(p,replace(s,bound_group='other-reviewed-group'))
run.assert_not_called()
def test_exact_role_binding_and_value_free_build(tmp_path):
s=spec(tmp_path);p=_plan(tmp_path,spec=s)
with patch('ops_mason.executor.subprocess.run',return_value=MagicMock(returncode=0,stdout='suppressed-response')) as run:
result=build_oidc_kv_lane(p,s)
assert len(run.call_args_list)==2
assert run.call_args_list[0].args[0]==['bao','policy','write',s.policy_name,'-']
assert run.call_args_list[1].args[0]==['bao','write','auth/netkingdom/role/'+s.role_name,'-']
role=json.loads(run.call_args_list[1].kwargs['input'])
assert role['bound_claims']=={'groups':s.bound_group}
assert role['token_no_default_policy'] is True and role['token_explicit_max_ttl']=='30m'
assert role['token_policies']==[s.policy_name]
assert build_spec_document(s)['engine']=='openbao-oidc-kv-read-write'
assert 'suppressed-response' not in s.audit_log_path.read_text()
assert result['approved_spec_sha256']==p.approved_spec_sha256
def test_failure_suppresses_output_and_never_claims_success(tmp_path):
s=spec(tmp_path)
with patch('ops_mason.executor.subprocess.run',return_value=MagicMock(returncode=1,stdout='sensitive-marker',stderr='sensitive-marker')):
with pytest.raises(BuildError) as e:build_oidc_kv_lane(_plan(tmp_path,spec=s),s)
assert 'sensitive-marker' not in str(e.value) and not s.audit_log_path.exists()

View file

@ -9,7 +9,7 @@ flavor: planning
owner: codex
topic_slug: helix-forge
created: "2026-09-04"
updated: "2026-10-03"
updated: "2026-10-04"
state_hub_workstream_id: "483e56d6-6601-5377-9066-66225214c046"
---
@ -76,7 +76,7 @@ side. Re-qualified from `message-from:railiance-platform` to
```task
id: MASON-WP-0005-T02
status: todo
status: progress
priority: high
state_hub_task_id: "69c558d9-664f-5ce0-80b2-d2e7544353a3"
```
@ -202,3 +202,25 @@ role.
T01–T06 remain wait: accepted tenant/matrix, confirmed identity/MFA/callbacks, approved matching writer contracts, live survey, explicit construction approval and verification are outstanding. The platform design remains proposed. No lane was built or activated.
Evidence and precise resumption conditions: [review](../docs/evidence/2026-09-28-loose-end-review.md). Existing tasks retain all remaining obligations; no new task or workplan was opened.
## Operator-matrix source delivery — 2026-10-04
T02 advances to progress. The ops-mason engine now renders the exact four-entry
OIDC matrix, writes only policy/auth structure, checks confirmed identity and
platform-review reference parameters, enforces narrow loopback callbacks and
bounded tokens, and reuses the existing fresh exact-spec construction approval
check before any Bao command. Value output is suppressed; audit records hold
only coordinates and approval digest. Actual source tests cover no-approval,
changed scope, unsafe paths/claims/callbacks, salt create-only capabilities,
role payload, no KV-value calls, and failure redaction.
Platform's design is still a proposal: no matching operator-matrix request type
exists in its current CCR schema/validator. The old September note saying the
schema/validator are already drafted does not establish implementation or
acceptance. Platform contract support/review remains in T02; confirmed claim,
issuer/MFA assurance and callbacks remain T01, then live survey and explicit
exact-plan approval T03. No production role, policy, login or credential was
created. The construction plan stays draft; local synthetic identity references
in tests do not count as owner returns. This source wave also preserves all
unrelated dirty owner files and the existing untracked construction plan.