TWO FINDINGS, BOTH FROM READING OTHER REPOSITORIES RATHER THAN OURS.
The §13.1 stance register has a second row. secrets-engine published
pep-stance.yaml -- total over catalog stage plus unknown, runtime-read
and pinned to SHIPPED_STANCE by test. SCOPE.md claimed ops-warden's was
the estate's only published map and that the register's single row was
itself the finding; that is no longer true and is corrected.
docs/stance-register-review.md is the first exercise of the
aggregate-divergence capability flex-auth claimed on 2026-08-29 and then
recorded as unexercised because one row cannot diverge from anything.
Three findings:
The two maps take opposite stances on unknown -- ops-warden fail_open by
versioned build profile under ADR-0009, secrets-engine fail_closed. Both
conformant, neither a defect, and they disagree about the one case nobody
planned for. Reported as an observation for gate-house's register, not as
a request that either repository change: a PDP does not set a consumer's
stance, and §9.3's two-owner split is our own finding.
The rows are not comparable. ops-warden scopes by security-zone,
secrets-engine by catalog-stage. §6.4 permits both, but the register
cannot then answer what the estate's stance is for a z2 workload. Worth
recording before a third row arrives.
secrets-engine's map defines fail_closed in terms of a durable
ActionAuthorization record, which GH-DEC-2026-005 shelved. The stance is
unaffected -- only the artifact name is stale -- but the file is read at
runtime and pinned by test, so the stale name outlives a comment.
SEPARATELY, T04 IS BLOCKED AND THE REASON IS STRUCTURAL. The task assumed
the ops-warden/tenant-engine/user-engine pattern, where the pin's
default-deny NetworkPolicy admits one approved consumer workload.
secrets-engine has no Kubernetes deployment at all -- it is a CLI. There
is no pod selector to write, and inventing one would repeat the error
corrected in T02. Three possible shapes recorded in the workplan and
raised with them; callerAuth, not the NetworkPolicy, becomes the real
boundary if an operator CLI is the caller, so the FLEX-WP-0016 precedent
does not transfer unexamined.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
approval-engine flagged the class one message earlier -- a contract whose
examples contradict its prose gets implemented as its examples -- and
yesterday's package was a fresh instance of it, committed while flagging
it.
The published rule required context.approval.status == "approved" and
counted context.approval.approvals[].subject_id. Neither field exists.
approval-engine's approval_claim.schema.json has `state` (whose operative
value is `valid`, not `approved`) and carries no approver list at all. The
rule was unsatisfiable: every live destroy would have denied
dual_control_required no matter how good the approval was. It failed
closed, so it was never a hole, but it was policy written against a shape
of our own devising rather than a published one.
The rule now consumes valid_now from the real claim, guarded on kind and
issuer. valid_now is the summary predicate that already folds in the
distinct-approver threshold, with reason_code insufficient_approvers for
a claim that failed it -- so this is also the correct layering, not just
the correct shape. Counting approvers here is exactly the duplication
GH-DEC-2026-005 removes; the compensating property is reconstructability
at the issuer under 9.6, which is approval-engine's.
Recorded as a correction section in the package and the vocabulary doc
rather than quietly rewritten. 25 Rego tests and 29 fixtures pass,
covering insufficient_approvers, consumed, revoked, approved-but-not-yet-
valid, foreign issuer, and wrong kind.
Two things the package deliberately does not do, both now written down:
it does not compare binding.pdp_digest, because the request digest is
computed after policy evaluation and a Rego rule cannot see it; and it
makes no cross-check that the claim was approved for this action and
target, because the claim's binding uses approval-engine's vocabulary and
no mapping between the two is published. Inventing one would silently
accept a claim approved for something else. Both belong to the PEP until
a mapping exists, and that is worth closing before SECRETS-WP-0007-T04
makes destroy reachable.
Also swept the other published fixtures on approval-engine's reasoning.
One more instance: the inner decision in examples/caring/action_authorization.json
declared contract_version flex-auth.decision-record.v1 while its
provenance omitted policy_package_digest, registry_snapshot_digest, and
input_claim_digests -- all published contract fields since 2026-09-02.
Completed. The remaining example context vocabularies are consumer-owned
and match their integrations.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
secrets-engine delivered the action vocabulary T01 asked for: twelve
actions read out of cli.py, not the example vocabulary. The gate earned
its keep -- four would have been inferred wrongly, and revoke, the
obvious thirteenth, does not exist as an action at all. It gates as
deactivate, which is also reached from lifecycle deactivate.
docs/secrets-engine-action-vocabulary.md records the list and the four
traps. examples/secrets-engine/ carries the package, both manifests, a
loadable registry snapshot, 26 fixtures, five check requests, and a
README. validate -kind policy is valid with 22/22 Rego tests and 26/26
fixtures.
allow_ttl is 15m, stated in the package rather than inherited from the
engine default: these decisions authorize live secret operations, so the
reliance window belongs where a reviewer sees it. Per FLEX-DEC-2026-004
it is authority to issue, not authority to keep using what the operation
produced.
destroy is the dual-control case, gated on a context.approval claim
marked approved with two distinct approvers, repeated entries counting
once. flex-auth checks what the claim says and deliberately does not
re-derive its temporal validity, signature, or supersession -- those are
approval-engine's to assert and the PEP's to verify against the live
claim, per the split accepted in FLEX-DEC-2026-006. It stays in the
package though its handler raises before the gate, so the rule is
reviewed and fixtured before SECRETS-WP-0007-T04 opens the path.
Following the FLEX-WP-0010-T02 precedent the denial ladder has no
action_not_granted branch: one subject holding all twelve actions could
never reach it, and a rule that cannot fail reads as control that is not
there. Registering a second calling identity is the revisit trigger.
Not deployed. No flex-auth-secrets-engine pin exists yet (T04), so their
policy pin stays unset and fail-closed until T05.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
approval-engine filed APPROVAL-IN-0002: secrets-engine built its PEP
validator against our ActionAuthorization schema, pointed it at
GET /v1/approvals/{id}/claim, and it rejects every response. Both
envelopes declare schema_version 0.1, so it fails late and reads like an
approval-engine outage rather than a contract mismatch.
FLEX-DEC-2026-006 accepts the deferral and argues against flex-auth's own
proposal. The composed object had the PIP republish our decision, which
crosses the same layer boundary we invoked to decline authentication
evidence and to win section 17's schema. The claim-plus-DecisionEnvelope
split drops no check; each verification lands on the layer that owns it.
approval-engine asked, before the decision, whether the open G3 finding
argues for ratifying now. It does not: G3 is already closed the other
way. FLEX-WP-0019 added lifetime to the DecisionEnvelope itself, required
on every allow by schema conditional, published 2026-09-02. The trigger
resolved by adding a field rather than by composition, so the decision
stands alone and needs no bundle.
The provenance.authority == state-hub constant is our defect and is
fixed at source. It came from examples/caring/action_authorization.json,
which contradicted the same contract's ownership section. That fixture
now names approval-engine as the approval fact's authority and flex-auth
as the decision's, and its stale secrets-engine.lifecycle pin is
corrected to the reserved coordinate from FLEX-DEC-2026-005.
The contract doc and schema are marked deferred-not-withdrawn so no
other consumer builds a validator against them. The execute-time half is
untouched: /v1/check, binding, the canonical digest, and
flex-auth.decision-record.v1 stay published.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
Two cross-repo questions arrived in the flex-auth inbox and both are
answered as decision records rather than as prose in a message.
FLEX-DEC-2026-004 answers ops-warden WARDEN-WP-0034-T05. A decision
lifetime shorter than the SSH certificate TTL is meaningful, but only as
authority to issue, never as authority to use an already-issued
certificate. The pre-sign gate is the only consumer of the shorter
lifetime: no replay past expires_at, fresh Check per sign. The lever that
shortens effective access is the requested TTL as a policy input, which
is already deployed as the ttl_out_of_bounds deny. ops-warden's section
9.7.2 window through certificate TTL is correct as written and correctly
owned by the PEP; flex-auth does not want that residue moved to the PDP.
docs/decision-input-freshness.md gains the same boundary as published
contract text, so the ruling is not only in the decision log.
FLEX-DEC-2026-005 answers secrets-engine. A real policy package is
expected and flex-auth authors it here as it does for every consumer; the
reserved coordinate is secrets-engine.catalog-lane.lifecycle v1 and it
does not exist yet. Their choice not to default the pin was correct and
is endorsed explicitly. POST /v1/check is deployed but has no
estate-wide address by design -- per-consumer cluster-local pins with
default-deny ingress -- so their 2026-09-06 probe found the design
working, not an outage.
FLEX-WP-0021 carries that work: obtain the real action vocabulary from
secrets-engine, publish the package with fixtures, confirm the digest
join against a real decision record, then stand up a
flex-auth-secrets-engine pin in warn without moving the other two pins.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JTbVXpEiXA7mNJVpDnEPcB
Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 412054@bnt-lap001
Assistant-Session: 3968fae1-8d59-4209-9bd6-c22594b8ab19
Close the remaining PDP obligations: mechanical layer declaration check,
registry-snapshot digest in provenance, explicit allow TTL, per-input-class
freshness deadlines, and the published decision-record contract. Document
the canonical request digest as the §6.4.2 replay test.
Assistant: grok
Assistant-Session: 01a06256-fb71-7102-b3a9-27e6734257d0
flex-auth stays the only PDP. Zone membership reaches /v1/check as compiled
registry data, never a synchronous lookup. Uncommitted tenant-engine
read/role/grouping policy was discarded, not mixed into this pin.
ops-warden WARDEN-WP-0031 T04 minted audience-flex-auth tokens as
system:serviceaccount:ops-warden:ops-warden and held the warn-log count.
Helm rev 2 is enforce. Live probe 200/401/403. policy.enabled is theirs.
policy.enabled cannot flip against the user-engine or tenant-engine
bindings. Helm release flex-auth-ops-warden rev 1 serves the production
registry in warn. Workstation warden sends no caller token; enforce and
the yaml flip stay ops-warden's.
flex-auth-tenant-engine Helm rev 2 is enforce on sha256:138aa347 after
tenant-engine 2063470 started sending a projected caller token
(sha256:7dc9d7bf). Probe 200/401/403. tenancy.current.A is 2. T04
cancelled: AuthZEN waits for a PEP we do not control.
Helm-adopted the kubectl-managed user-engine pin, promoted sha256:138aa347
warn then enforce. Reviewer volume needs fsGroup 65532; reviewer tokens
must use the API default audiences or TokenReview 401s on this k3s.
Live probe from user-engine-8569d7cb87-fp7px: 200 / 401 / 403. tenant-engine
is unchanged. tenancy.current.A stays 0 until that pin moves.
The sanctioned Helm chart could not promote ADR 0004 at all, and the
emergency manifests selected enforce. That made a FLEX-WP-0011 apply
either a no-op or a global 401. First production pin is now warn, per
consumer, on CI digest sha256:138aa347… . Enforce stays a later
per-consumer flip so USER-WP-0023-T03 can close without waiting on
tenant-engine.
TokenReview-based caller identity with audience-scoped tokens and exact
resource.system to ServiceAccount bindings, per ops-warden's recommendation.
Deletes the unwired tenant-engine live-roles adapter (T03) and adds
make verify-posture (T05). Source implements A2; running digest is still A0
until promotion, so tenancy.current.A stays 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Publishes tenancy.yaml (I1 A0 E2 P n/a R n/a, enables A3), the review
answering rapp-postgres's three asks, and FLEX-WP-0015 to close the gaps.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FLEX-WP-0014. Package tenant-engine.write-api.mutate v1 now carries
nine actions: tenant.guardrail.read and tenant.guardrail.set on
resource type guardrail, verbatim from tenant-engine's mapping.
The read/write split is used now: flex-auth may read ceilings and is
denied action_not_granted on set; tenant-engine may do both. 17/17
Rego tests and 23/23 fixtures pass. Live e2e against a real
tenant-engine FlexAuthWriteAuthorizer matches.
In source only; production still serves the seven-action 9320df39 pin.
Extend the tenant-engine write-API policy package with tenant.update,
tenant.retire, and tenant.reactivate, the three actions TEN-WP-0005
introduced. Until now every lifecycle mutation resolved to deny
unknown_action, blocking TEN-WP-0005-T05 production rollout.
- policy_package.md: three actions in valid_actions and frontmatter;
CARING capabilities extended with EditAny/Archive/Restore
- protected_system_manifest.yaml, subject_manifest.yaml, and the rebuilt
registry_snapshot.json carry all seven actions
- docs/tenant-engine-action-vocabulary.md documents the HTTP surfaces
- 8 new fixture pairs and 5 new Rego tests, including action-drift
guards (tenant.retired, tenant_update -> unknown_action)
T02 decision, recorded in policy_package.md: tenant.retire does NOT get
stricter authorization yet. With one service subject and no assurance
claim in the CheckRequest there is nothing stricter to check, and a
condition the sole caller always satisfies would falsely read as
separate control. Retirement is reversible and non-destructive. Revisit
on KEY-WP-0005 assurance claims or a second operator subject.
Verified: 11/11 Rego tests, 16/16 fixtures, go test ./... green, gofmt
and go vet clean. End-to-end against a live serve with a real
tenant-engine through the unmodified FlexAuthWriteAuthorizer: update
6176c39c2f4d7b15, retire 8a801b8ee8455080, reactivate c64cf3713cecd970
all allow; unregistered actor denied unknown_subject
(59d3e99c6416be89, 403 write_denied).
Handoff revision for TEN-WP-0005-T05: package
tenant-engine.write-api.mutate v1 (ready, caring-0.4.0-rc2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
docs/tenant-engine-{resource-namespace,action-vocabulary}.md: four actions
(tenant.create, tenant.role.grant, tenant.role.revoke, tenant.plan.assign)
matching tenant_engine/src/tenant_engine/authz.py's _RESOURCE_TYPES mapping
exactly. No resource_manifest.yaml -- tenant-engine's resources are created
dynamically, unlike ops-warden's fixed SSH-certificate inventory; documented
as a deliberate deviation.
examples/tenant-engine/: protected_system_manifest.yaml, subject_manifest.yaml
(one registered caller: tenant-engine's own service identity),
policy_package.md (Rego rules + embedded tests), policy_fixtures.yaml (8
allow/deny pairs), assembled registry_snapshot.json, two standalone
check_request examples, README.
Design decision made explicit in the workplan: operator/service-identity
authorization (one known subject, four known actions), not aal2+assurance +
tenant-capability-role checking -- CheckRequest carries no assurance claim
yet (TEN-WP-0003 already flagged actor as a plain request-body field, not a
real auth context), and a tenant's capability roles are the tenant's own
state, not a property of who may call tenant-engine's admin API. Revisit
once KEY-WP-0005 gives callers a real assurance-bearing identity.
Verified for real: built ./cmd/flex-auth, ran test-policy (6 tests + 8
fixtures, all pass), load-registry, check against both example requests.
Then ran a live flex-auth serve with this exact registry+policy and pointed
tenant-engine's real, unmodified FlexAuthCheckClient/FlexAuthWriteAuthorizer
at it over real HTTP: unknown subject -> 403, tenant-engine -> 201. go test
./... still green across the whole repo.
T03 (live-lookup context adapter for other protected systems) stays open --
new Go adapter package, tracked separately.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add production_registry_snapshot.json from ops-warden inventory with CI
coverage for real actors, IAM subject binding, ttl_out_of_bounds, and
unknown_actor_resource. Extend serve contract tests with /healthz and
publish the registry sync contract for operator deployment.
Completes FLEX-WP-0005 T05 and closes the Foundations and Topaz
Alignment workstream.
docs/iam-profile-consumption.md captures flex-auth's input surface
against NetKingdom IAM Profile v0.1:
- boundary (flex-auth consumes verified claims; upstream layer
validates signatures and audiences)
- normalized input envelope (matches Markitect's EnterpriseIdentity)
- required, recommended, and tolerated claim variations
- role-claim location union (top-level / realm_access / resource_access)
- scope encoding (string vs array)
- principal-type detection (human / service / emergency)
- group-overage and freshness expectations
- production vs local-development handling
examples/claims/ ships five contract fixtures:
- key-cape-lightweight.yaml (profile minimum)
- keycloak-heavy.yaml (full variation set + MFA)
- service-account.yaml (svc-* hub-to-hub)
- emergency.yaml (break-glass with incident metadata)
- keycloak-group-overage.yaml (Entra-style hasgroups: true)
All fixtures parse as valid YAML. They become contract tests for the
standalone evaluator (FLEX-WP-0002 P2.4) and the Topaz adapter
(FLEX-WP-0004 T01); both code paths must produce identical normalized
envelopes for the same fixture.
FLEX-WP-0005 workstream marked status=done in this file and completed
in the State Hub. FLEX-WP-0002 is now fully unblocked.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes FLEX-WP-0005 T04. Validates ADR-003's commitment to shape the
standalone core for cheap Topaz adapter work.
Spike output:
- docs/topaz-mapping-spike.md — vocabulary map (subject, group, tenant,
knowledge_base, document, plus parent / owner_team / reader / steward /
member relations), Rego module shape, decision envelope, wire-protocol
ranking (gRPC primary, REST fallback, embedding rejected), schema
restatement recommendation, implications for FLEX-WP-0002 / 0004.
- examples/topaz/ — runnable docker-compose deploying Topaz with the
flex-auth-shaped manifest. seed and probe one-shots cover three
scenarios: alice (steward) allow, bob (group→reader) allow, eve
(outsider) deny. End-to-end green on 2026-05-16:
probe: steward-allow OK (check=true)
probe: reader-allow OK (check=true)
probe: outsider-deny OK (check=false)
probe: all checks passed
Key findings recorded as Implementation Notes in the spike doc:
- Rego input contract bridging (Topaz raw shape ↔ flex-auth canonical
shape) is adapter scope, not core scope.
- Topaz identity objects are a Topaz convention; the adapter
materializes them at directory import time.
- Directory-only permission resolution is sufficient for the common
case; Rego is reserved for context-dependent decisions.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pre-implementation assessment and boundary review
(docs/pre-implementation-assessment.md) lead to three ADRs:
- ADR-001 Go + repo skeleton
- ADR-002 Rego-in-Markdown policy package format
- ADR-003 Topaz-aligned MVP (Topaz spike moves into foundations)
New workplan FLEX-WP-0005 (Foundations and Topaz Alignment) is inserted
between WP-0001 (done) and WP-0002 (core). WP-0002 pins Rego-in-Markdown
for P2.3; WP-0004 P4.1 refocused from Topaz evaluation to Topaz adapter.
Go skeleton at repo root: cmd/flex-auth + internal/{registry,policy,
decision,audit,adapters} + pkg/api + Makefile + .golangci.yml + GitHub
Actions CI. make ci green locally; bin/flex-auth --version works.
INTENT/SCOPE cite the NetKingdom IAM Profile and add the ops-warden /
ops-bridge disjoint-surface clarifications.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>