Catalog fluid-telegram-operator (draft) and tenant-engine sender projection; case plan env spec and case-ref
- fluid-telegram-operator: pointer-only draft entry and playbook from ops-mason's
MASON-WP-0005-T06 proposal; activation waits for the owner-run member and
non-member checks and the salt create-once probe.
- audit-core-senders: tenant-engine recorded as a verified exact-path consumer
projection (MASON-WP-0007-T06). The registry lane itself stays draft.
- warden case plan: {param:env} format spec (net-kingdom 850907a), other specs
and conversions refused; prints case-ref <case_id>@sha256:<file digest>.
- Workload-join unknown count 21 -> 22 (test and pep-stance.yaml).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 427672@bnt-lap001
Assistant-Session: a0a2367e-4697-440c-a4e5-9713c009f6d8
This commit is contained in:
parent
330de7b5ac
commit
23ba584ccd
9 changed files with 153 additions and 13 deletions
|
|
@ -98,7 +98,7 @@ on_apply:
|
|||
# 2026-10-08: platform-supplier-credentials added as a not-applicable path pattern,
|
||||
# so routing not_applicable rose 16 -> 17.
|
||||
classification_coverage:
|
||||
measured: "2026-10-08"
|
||||
measured: "2026-10-09"
|
||||
attribution: self-measured
|
||||
signing_targets:
|
||||
resolved: 0
|
||||
|
|
@ -106,7 +106,7 @@ classification_coverage:
|
|||
not_applicable: 1
|
||||
routing_lanes:
|
||||
resolved: 4
|
||||
unknown: 21
|
||||
unknown: 22
|
||||
not_applicable: 17
|
||||
|
||||
verdict_caching: none
|
||||
|
|
|
|||
|
|
@ -1324,7 +1324,7 @@ entries:
|
|||
workload_ref:
|
||||
applicability: applicable
|
||||
unknown_reason: "audit-core has not published an authoritative workload identity declaration."
|
||||
need_keywords: [audit-core, senders, sender registry, ingest token, AUDIT_CORE_SENDERS]
|
||||
need_keywords: [audit-core, senders, sender registry, ingest token, AUDIT_CORE_SENDERS, tenant-engine audit-core sender]
|
||||
owner_repo: ops-mason
|
||||
subsystem: OpenBao + audit-core
|
||||
warden_executes: false
|
||||
|
|
@ -1335,8 +1335,14 @@ entries:
|
|||
delegation:
|
||||
mode: native
|
||||
intended_owner: ops-mason
|
||||
reviewed: "2026-08-15"
|
||||
reviewed: "2026-10-09"
|
||||
verified: unverified
|
||||
# Per-sender projections. Each is verified on its own and none activates the
|
||||
# registry lane. tenant-engine: KV platform/workloads/tenant-engine/audit-core-sender
|
||||
# (v1), read policy workload-kv-read-tenant-engine-audit-core-sender-eso (exact
|
||||
# read, no registry path), ESO-delivered; verified by ops-mason 2026-10-09
|
||||
# (MASON-WP-0007-T06, message e7e866bf).
|
||||
consumers: [tenant-engine]
|
||||
auth_method: "in-cluster generated Secret audit-core-senders; later OpenBao KV via Mason wrap-migrate"
|
||||
path_template: "platform/workloads/audit-core/senders"
|
||||
policy_ref: "external-secrets-audit-core"
|
||||
|
|
@ -1490,3 +1496,37 @@ entries:
|
|||
- "Re-mint the credential at the supplier, written to a mode-0600 file and never printed."
|
||||
- "Write it through a railiance-platform delegated credential change to platform/suppliers/<supplier>/<account> (value from the file, never on argv); revoke the predecessor at the supplier."
|
||||
- "Verify with capabilities and metadata only (`bao token capabilities`, `bao kv metadata get`), then tell readers to re-fetch."
|
||||
|
||||
- id: fluid-telegram-operator
|
||||
title: fluid-telegram Hall of Helix operator KV lane (attended OIDC)
|
||||
# Pointer only (MASON-WP-0005-T06, proposal 856bd6ba, 2026-10-09). Built by
|
||||
# ops-mason as CCR-2026-0039; ops-mason verified the policy text and role:
|
||||
# exact four-path matrix, role bound to fluid-telegram-operators at aal2, and
|
||||
# 8 agent denies present. ops-mason proposed `applied-pending-verify`; the
|
||||
# catalog has only draft/active, so it stays draft until the owner-run checks
|
||||
# pass: member capabilities and non-member refusal
|
||||
# (ops-mason scripts/verify-fluid-telegram-lane.sh), plus the salt
|
||||
# create-once probe (FT-WP-0002). Then ops-warden activates it. The adapter
|
||||
# runtime lane is separate (MASON-IN-0003): bot-token and redaction-salt only.
|
||||
risk: high
|
||||
workload_ref:
|
||||
applicability: applicable
|
||||
unknown_reason: "fluid-telegram has not published an authoritative workload identity declaration."
|
||||
need_keywords: [fluid-telegram, hall-of-helix, telegram operator, telegram bot, mtproto, operator-session, bot-token, redaction-salt, fluid-telegram-operators]
|
||||
owner_repo: ops-mason
|
||||
subsystem: OpenBao netkingdom OIDC + platform KV (fluid-telegram campaign)
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/fluid-telegram-operator.md#worker-checklist
|
||||
canon_ref: ops-mason/plans/fluid-telegram-operator-credential-lane.md
|
||||
reviewed: "2026-10-09"
|
||||
status: draft
|
||||
delegation:
|
||||
mode: native
|
||||
intended_owner: ops-mason
|
||||
reviewed: "2026-10-09"
|
||||
verified: owner-confirmed
|
||||
auth_method: "bao login -method=oidc -path=netkingdom role=fluid-telegram-hall-of-helix-workload-kv (members of fluid-telegram-operators, aal2, 15m token)"
|
||||
path_template: "platform/workloads/coulomb/fluid-telegram/hall-of-helix/{operator-app,operator-session,bot-token,redaction-salt}"
|
||||
exec_capable: false
|
||||
resolvable: false
|
||||
lane: login
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ parameter whose type declares ``sensitivity: secret`` is refused outright.
|
|||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import re
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
|
|
@ -56,10 +57,12 @@ class CasePlan:
|
|||
skipped: List[str] = field(default_factory=list)
|
||||
attended_sessions: List[str] = field(default_factory=list)
|
||||
verification: List[dict] = field(default_factory=list)
|
||||
case_ref: Optional[str] = None
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return {
|
||||
"case": self.case_id,
|
||||
"case_ref": self.case_ref,
|
||||
"type": self.type_ref,
|
||||
"steps": [s.as_dict() for s in self.steps],
|
||||
"skipped": self.skipped,
|
||||
|
|
@ -76,6 +79,15 @@ def load_case(path: Path) -> dict:
|
|||
return doc
|
||||
|
||||
|
||||
def case_ref(case_id: str, case_bytes: bytes) -> str:
|
||||
"""``<case_id>@sha256:<hex>`` over the case file's exact bytes.
|
||||
|
||||
The reference an owner entry point records for a run (RPF-WP-0054-T01). The
|
||||
owner re-derives it from the file rather than trusting the caller.
|
||||
"""
|
||||
return f"{case_id}@sha256:{hashlib.sha256(case_bytes).hexdigest()}"
|
||||
|
||||
|
||||
def default_type_path(case_path: Path, type_ref: str) -> Path:
|
||||
"""net-kingdom layout: security-cases/<case>.yaml beside security-case-types/<id>/type.yaml."""
|
||||
type_id = type_ref.split("@", 1)[0]
|
||||
|
|
@ -94,14 +106,31 @@ def _applies(when: Optional[str], params: Dict[str, str]) -> bool:
|
|||
return str(params[name]).lower() == expected.lower()
|
||||
|
||||
|
||||
#: Format specs a template may apply, matching net-kingdom's validator
|
||||
#: (``FORMAT_SPECS``): ``{client_id:env}`` is upper case with ``-`` as ``_``.
|
||||
FORMAT_SPECS = ("", "env")
|
||||
|
||||
|
||||
class _Param(str):
|
||||
def __format__(self, spec: str) -> str:
|
||||
if spec == "env":
|
||||
return self.upper().replace("-", "_")
|
||||
return str.__format__(self, spec)
|
||||
|
||||
|
||||
def _render(text: Optional[str], params: Dict[str, str], where: str) -> Optional[str]:
|
||||
if text is None:
|
||||
return None
|
||||
names = {f for _, f, _, _ in string.Formatter().parse(text) if f}
|
||||
missing = sorted(names - set(params))
|
||||
fields = [(f, spec or "", conv) for _, f, spec, conv in string.Formatter().parse(text) if f]
|
||||
bad = sorted({f"{f}:{spec}" for f, spec, _ in fields if spec not in FORMAT_SPECS})
|
||||
if bad:
|
||||
raise CaseError(f"{where}: unknown format spec in {bad}; only {{param:env}} is supported")
|
||||
if any(conv for _, _, conv in fields):
|
||||
raise CaseError(f"{where}: conversions such as !r are not supported in templates")
|
||||
missing = sorted({f for f, _, _ in fields} - set(params))
|
||||
if missing:
|
||||
raise CaseError(f"{where}: parameters {missing} are not set by the case")
|
||||
return text.format(**params)
|
||||
return text.format_map({k: _Param(v) for k, v in params.items()})
|
||||
|
||||
|
||||
def plan_case(case_doc: dict, type_doc: dict, catalog: Optional[Catalog]) -> CasePlan:
|
||||
|
|
@ -178,7 +207,10 @@ def _line(f: Finding) -> str:
|
|||
|
||||
def format_plan(plan: CasePlan) -> str:
|
||||
labels = {"warden": "warden (caller proxy)", "attended": "ATTENDED", "owner": "owner"}
|
||||
lines = [f"case {plan.case_id} ({plan.type_ref}): plan only, nothing executed", ""]
|
||||
lines = [f"case {plan.case_id} ({plan.type_ref}): plan only, nothing executed"]
|
||||
if plan.case_ref:
|
||||
lines.append(f"case-ref: {plan.case_ref}")
|
||||
lines.append("")
|
||||
for n, s in enumerate(plan.steps, 1):
|
||||
lane = f" lane={s.lane}" if s.lane else ""
|
||||
lines.append(f"{n}. {s.id} [{labels[s.mode]}] owner={s.owner}{lane} values={s.value_handling}")
|
||||
|
|
|
|||
|
|
@ -2255,13 +2255,14 @@ def case_plan(
|
|||
output_json: Annotated[bool, typer.Option("--json", help="Output JSON")] = False,
|
||||
) -> None:
|
||||
"""Print a case's run (warden, attended, owner steps and verification); executes nothing (exit 1 if refused)."""
|
||||
from warden.case_plan import CaseError, default_type_path, format_plan, load_case, plan_case
|
||||
from warden.case_plan import CaseError, case_ref, default_type_path, format_plan, load_case, plan_case
|
||||
from warden.case_type import load_type
|
||||
|
||||
try:
|
||||
case_doc = load_case(case_file)
|
||||
type_path = type_file or default_type_path(case_file, str((case_doc.get("spec") or {}).get("type", "")))
|
||||
plan = plan_case(case_doc, load_type(type_path), _load_catalog())
|
||||
plan.case_ref = case_ref(plan.case_id, case_file.read_bytes())
|
||||
except CaseError as exc:
|
||||
err.print(str(exc), markup=False)
|
||||
raise typer.Exit(1)
|
||||
|
|
@ -2271,4 +2272,4 @@ def case_plan(
|
|||
if output_json:
|
||||
print(json.dumps(plan.as_dict(), indent=2))
|
||||
else:
|
||||
console.print(format_plan(plan), markup=False)
|
||||
console.print(format_plan(plan), markup=False, soft_wrap=True)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,8 @@ TYPE = {
|
|||
{"id": "mint", "owner": "o", "entry_point": "mint {client_id}", "auth": "caller",
|
||||
"lane": "key-cape-oidc-login", "warden_executes": True, "attended": False,
|
||||
"value_handling": "generated_in_custody", "when": "client_type == confidential"},
|
||||
{"id": "login", "owner": "o", "entry_point": "apply {client_id}", "auth": "operator",
|
||||
{"id": "login", "owner": "o", "entry_point": "apply {client_id} env KEYCAPE_{client_id:env}_X",
|
||||
"auth": "operator",
|
||||
"lane": "key-cape-oidc-login", "warden_executes": False, "attended": True,
|
||||
"value_handling": "none", "attended_justification": "live registry is a Secret"},
|
||||
{"id": "handover", "owner": "{consumer}", "entry_point": "use {issuer}", "auth": "a",
|
||||
|
|
@ -158,6 +159,21 @@ def test_refuse_secret_parameter(catalog):
|
|||
plan_case(_case(parameters=dict(CASE["spec"]["parameters"], pw="hunter2")), doc, catalog)
|
||||
|
||||
|
||||
def test_env_format_spec_renders_like_keycape_client(catalog):
|
||||
params = dict(CASE["spec"]["parameters"], client_id="my-app")
|
||||
plan = plan_case(_case(parameters=params), _type(), catalog)
|
||||
login = next(s for s in plan.steps if s.id == "login")
|
||||
assert login.entry_point == "apply my-app env KEYCAPE_MY_APP_X"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("template", ["{client_id:upper}", "{client_id:>9}", "{client_id!r}"])
|
||||
def test_refuse_unknown_format_spec_or_conversion(catalog, template):
|
||||
doc = _type()
|
||||
doc["spec"]["follow_through"][0]["entry_point"] = template
|
||||
with pytest.raises(CaseError, match="format spec|conversions"):
|
||||
plan_case(_case(), doc, catalog)
|
||||
|
||||
|
||||
def test_refuse_unsupported_condition(catalog):
|
||||
doc = _type()
|
||||
doc["spec"]["follow_through"][0]["when"] = "client_type in (a, b)"
|
||||
|
|
@ -173,6 +189,9 @@ def test_cli_plan_shows_attended_and_exit_codes(tmp_path):
|
|||
ok = runner.invoke(app, ["case", "plan", str(case_file), "--type", str(type_file)])
|
||||
assert ok.exit_code == 0, ok.output
|
||||
assert "ATTENDED" in ok.output and "nothing executed" in ok.output
|
||||
import hashlib
|
||||
digest = hashlib.sha256(case_file.read_bytes()).hexdigest()
|
||||
assert f"case-ref: c@sha256:{digest}" in ok.output
|
||||
case_file.write_text(yaml.safe_dump(_case(status="accepted")))
|
||||
refused = runner.invoke(app, ["case", "plan", str(case_file), "--type", str(type_file)])
|
||||
assert refused.exit_code == 1
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ def test_repo_catalog_uses_only_explicit_workload_references():
|
|||
report = build(ROOT / "registry/routing/catalog.yaml", Path.home())
|
||||
assert report["ok"] is True
|
||||
assert len(report["resolved"]) == 4
|
||||
assert len(report["unknown"]) == 21
|
||||
assert len(report["unknown"]) == 22
|
||||
# 15 since 2026-09-21: the layer-declaration route is repository conformance
|
||||
# guidance, not a workload delivery lane, and the two NetKingdom SSO lanes
|
||||
# (c374d41) remain provider/control-plane credentials. Lanes added after
|
||||
|
|
@ -24,6 +24,8 @@ def test_repo_catalog_uses_only_explicit_workload_references():
|
|||
# under a founder-minted grant, not a workload credential. 17 since
|
||||
# 2026-10-08: platform-supplier-credentials (WARDEN-WP-0043) is a generic
|
||||
# supplier-account path pattern; concrete consumers keep their own lanes.
|
||||
# unknown 22 since 2026-10-09: fluid-telegram-operator (MASON-WP-0005-T06)
|
||||
# names fluid-telegram, which has published no workload declaration.
|
||||
assert len(report["not_applicable"]) == 17
|
||||
assert {row["lane"] for row in report["resolved"]} == {
|
||||
"ops-warden-warden-sign-token",
|
||||
|
|
|
|||
|
|
@ -15,5 +15,15 @@ First deploy mints sender tokens in-cluster into Secret
|
|||
`platform/workloads/audit-core/senders` is the later authority, filled by a
|
||||
Mason wrap-migrate — not by a founder `bao kv put`.
|
||||
|
||||
## Sender projections
|
||||
|
||||
A sender that needs its own token gets an exact-path projection of it. That is a
|
||||
separate reader of a single sender's token, never read access to the registry.
|
||||
Each projection is verified on its own and does not activate the registry lane.
|
||||
|
||||
| Sender | KV path | Reader | Verified |
|
||||
| --- | --- | --- | --- |
|
||||
| tenant-engine | `platform/workloads/tenant-engine/audit-core-sender` | policy `workload-kv-read-tenant-engine-audit-core-sender-eso`, role `tenant-engine-audit-core-sender-eso` (SA `tenant-engine/tenant-engine-eso`), ExternalSecret `tenant-engine/tenant-engine-audit-core-sender` | 2026-10-09, ops-mason MASON-WP-0007-T06 (metadata and policy text only) |
|
||||
|
||||
Never place a sender token, bearer, or `senders.json` value in Git, State
|
||||
Hub, logs, or chat.
|
||||
|
|
|
|||
26
wiki/playbooks/fluid-telegram-operator.md
Normal file
26
wiki/playbooks/fluid-telegram-operator.md
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# fluid-telegram operator lane (Hall of Helix)
|
||||
|
||||
Catalog: `fluid-telegram-operator` (status `draft`, pointer only)
|
||||
Owner: `ops-mason` (construction plan `ops-mason/plans/fluid-telegram-operator-credential-lane.md`, CCR-2026-0039)
|
||||
Consumer: `fluid-telegram` attended provisioner (FT-WP-0002)
|
||||
|
||||
## Worker checklist
|
||||
|
||||
This page is a pointer. ops-warden does not issue, read, or hold these values.
|
||||
|
||||
1. You must be a member of the LLDAP group `fluid-telegram-operators`, signed
|
||||
in at aal2 (MFA).
|
||||
2. Log in attended:
|
||||
`bao login -no-print -method=oidc -path=netkingdom role=fluid-telegram-hall-of-helix-workload-kv`.
|
||||
The token lives 15 minutes. Agents are denied on these paths.
|
||||
3. Run the fluid-telegram provisioner per FT-WP-0002. It reads the operator
|
||||
app entry, writes the MTProto session and bot token, and creates
|
||||
`redaction-salt` once.
|
||||
4. Verify with `bao token capabilities` only, never by reading a value. The
|
||||
owner-run check is `ops-mason/scripts/verify-fluid-telegram-lane.sh`.
|
||||
|
||||
The bot adapter's runtime read of `bot-token` and `redaction-salt` is a
|
||||
separate lane (MASON-IN-0003). It never uses this attended role.
|
||||
|
||||
Status: draft until the owner-run member and non-member checks and the salt
|
||||
create-once probe pass. ops-warden then activates the entry.
|
||||
|
|
@ -108,7 +108,7 @@ Tests: refuse an unaccepted or unmaterialized case; refuse a step whose catalog
|
|||
entry is missing or not reviewed; show attended steps explicitly.
|
||||
|
||||
2026-10-09: done. `warden case plan <case.yaml> [--type] [--json]`
|
||||
(`src/warden/case_plan.py`, 15 tests in `tests/test_case_plan.py`). It refuses:
|
||||
(`src/warden/case_plan.py`, 14 tests in `tests/test_case_plan.py`). It refuses:
|
||||
- a case that is not `materialized`, or has no acceptance record
|
||||
- a type that is mismatched or not acknowledged
|
||||
- any `case check-type` finding, including a missing or unreviewed lane
|
||||
|
|
@ -130,6 +130,16 @@ Observation for net-kingdom (not blocking): `bind-env` runs before
|
|||
`KEYCAPE_<CLIENT_ID>_CLIENT_SECRET`, which is not a `{param}` placeholder, so it
|
||||
reaches the plan unrendered.
|
||||
|
||||
2026-10-09 follow-up:
|
||||
- **Format specs.** `_render` supports net-kingdom's `{param:env}` spec (upper
|
||||
case, `-` as `_`, the same as `keycape-client.py env_name`) and refuses any
|
||||
other spec or `!conversion`.
|
||||
- **Case reference.** The plan prints `case-ref: <case_id>@sha256:<hex>` over the
|
||||
case file's exact bytes. This is the reference proposed to railiance-platform
|
||||
for `credential.py generate --case-ref` (RPF-WP-0054-T01); the broker
|
||||
re-derives it from the file.
|
||||
- 18 tests now.
|
||||
|
||||
```task
|
||||
id: WARDEN-WP-0044-T03
|
||||
status: wait
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue