access-engine/workplans/FLEX-WP-0037-ops-warden-agt-custodian-actor.md
tegwick cf360d6211
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Workplan finished: all tasks done after the attended cutover
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 527080@bnt-lap001
Assistant-Session: 5a0e864a-67be-4605-a77a-16635f7dd292
2026-10-21 00:02:25 +02:00

5.7 KiB

id type title domain repo status owner topic_slug created updated origin origin_ref related state_hub_workstream_id
FLEX-WP-0037 workplan Register ops-warden actor agt-custodian in the signing-policy registry and roll the flex-auth-ops-warden pin infotech access-engine finished claude netkingdom 2026-10-20 2026-10-20 demand CUST-WP-0073-T02 (warden sign agt-custodian refused with unknown_actor_resource)
CUST-WP-0073
WARDEN-WP-0045
FLEX-WP-0016
FLEX-WP-0007
1547f1ac-7b8c-5986-a87c-bbd293b3ba8b

ops-warden WARDEN-WP-0045 (commits e62763c, a07c6c7) added the actor agt-custodian (type agt, principal agt-custodian, default and max TTL 12h, extension permit-pty, zone subject workload custodian-agents) for the supervised interactive custodian agent of CUST-WP-0073-T02. The in-cluster deployment flex-auth/flex-auth-ops-warden serves the registry snapshot baked into its image (/opt/flex-auth/examples/ops-warden/production_registry_snapshot.json), which does not know the actor, so warden sign agt-custodian is refused with unknown_actor_resource.

Add agt-custodian to the production fixture

id: FLEX-WP-0037-T01
status: done
priority: high
state_hub_task_id: "e565130d-6de6-520b-87c1-ddd47000ed62"

Regenerated the snapshot with ops-warden scripts/build_flex_auth_registry.py from the live inventory and took only the agt-custodian elements (resource ssh-cert:actor/agt-custodian, subject agt-custodian, membership in group:ops-warden-agents, relationship rel:agt-custodian-sign-agt-custodian). The diff to examples/ops-warden/production_registry_snapshot.json is additions only.

Drift deliberately not taken (needs its own owner decision):

  • live inventory also carries agt-rehearsal (principal rehearsal-inert-nohost, 2h), which is not in ops-warden's committed snapshot either;
  • the generator now emits security_zone_admission: unknown / catalog_applicability_absent for adm-example (fixture: not-applicable, human operator) and drops workload_id: codex-interhub-bootstrap from agt-codex-interhub-bootstrap.

Validation: access-engine load-registry loads 5 subjects / 5 relationships; go test ./... passes. TestOpsWardenProductionRegistryActors gains agt-custodian cases: own principal (actor and iam: subject) proceeds with advisory_would_signing_policy_matched; a foreign principal yields advisory_would_disallowed_principal. Both are audit_only because the actor's security zone resolves unknown (same advisory phase as every other agt actor in this fixture).

Build the image and pin flex-auth-ops-warden

id: FLEX-WP-0037-T02
status: done
priority: high
state_hub_task_id: "c112fd15-7f17-559c-8aa5-2c1d289e1274"

Pushing examples/** to main triggers .forgejo/workflows/image.yaml, which publishes forgejo.coulomb.social/coulomb/flex-auth:main-<short-sha>. Take the digest from the "Report immutable digest" step and set it in values/ops-warden.yaml only (the fetch pin, values/ops-warden-fetch.yaml, serves a different registry and stays where it is). Rollback target is the current pin sha256:138aa3471c46bca6e814691fa1e6520aedda3dffd743e6b09141ab433afdb64b. flex-auth has no Argo Application; the pin is applied by an attended Helm upgrade, so a pushed pin does not deploy by itself.

Result: CI build-and-push for 9132f9b succeeded; main-9132f9b = sha256:f7c04991a54ebc5fd2a55939e2f68efc7400a51f8773fb6703487833c65b3893. The snapshot extracted from the image layer has the same sha256 as the committed fixture (5ee90a82…). values/ops-warden.yaml now pins that digest. Note the previous pin was built from main-3de72fe (2026-08), so this roll also carries every code change since then; the same image line already serves the fetch pin. The generated runtime twin values/access-engine/ops-warden.yaml (access-engine-ops-warden, FLEX-WP-0036) was regenerated with tools/gen_runtime_twins.py to the same digest; tests/stage1.sh passes.

Attended rollout and smoke

id: FLEX-WP-0037-T03
status: done
priority: high
state_hub_task_id: "26cd6a2d-ac04-5453-9060-d469b439986d"

2026-10-20 (recorded 2026-10-21): attended helm upgrade flex-auth-ops-warden on railiance01 went to revision 3, image sha256:f7c04991…, 1/1 ready (rollback: revision 2). Allow smoke: after the upgrade, both an existing actor and agt-custodian pass the gate (stopping at the Vault token stage), where agt-custodian was previously denied unknown_actor_resource. Receipt: the-custodian docs/evidence/2026-10-20-railiance01-cutover-steps-2-4.md (CUST-WP-0073-T02). Done.

helm upgrade --install flex-auth-ops-warden charts/flex-auth \
  --namespace flex-auth -f values/ops-warden.yaml --wait --timeout 2m
kubectl -n flex-auth get deploy flex-auth-ops-warden \
  -o jsonpath='{.spec.template.spec.containers[0].image}'

Smoke through the 127.0.0.1:19090 tunnel (caller auth is enforce, so the request needs the ops-warden ServiceAccount bearer; the simplest real smoke is warden sign agt-custodian itself):

curl -s -X POST http://127.0.0.1:19090/v1/check \
  -H 'Content-Type: application/json' -H "Authorization: Bearer $OPS_WARDEN_SA_TOKEN" \
  -d '{"id":"check:smoke-agt-custodian","tenant":"tenant:platform",
       "subject":{"id":"agt-custodian","type":"agt"},"action":"sign",
       "resource":{"id":"ssh-cert:actor/agt-custodian","type":"ssh-certificate","system":"ops-warden"},
       "context":{"principals":["agt-custodian"],"actor_type":"agt","ttl_hours":1,
                  "pubkey_fingerprint":"SHA256:smoke"}}'

Expected: effect audit_only (or allow once the zone resolves), reason advisory_would_signing_policy_matched (not unknown_actor_resource).