From d2d0b6feeb79a9d9145b16ac25114952877a3c7d Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 4 Oct 2026 21:40:39 +0200 Subject: [PATCH] Admit initial decision lifecycle audience and patch evaluator dependencies Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a10780-6d41-7952-9718-dfbc1f41236f --- .github/workflows/ci.yml | 4 +- Containerfile | 4 +- Containerfile.lifecycle | 14 + .../lifecycle-policy-validation.json | 88 +++ .../policy-scan.json | 17 + .../review-policy-validation.json | 232 ++++++ .../attribute_admission_v8.json | 303 ++++++++ .../attribute_fixtures.json | 140 ++++ .../fixtures.json | 21 + .../intake-submitters.json | 12 + .../list_only_records.json | 20 + .../pin.json | 5 + .../policy.md | 708 ++++++++++++++++++ .../records.json | 444 +++++++++++ .../registry.json | 17 + .../informed-decision-lifecycle/fixtures.json | 21 + examples/informed-decision-lifecycle/pin.json | 5 + .../informed-decision-lifecycle/policy.md | 186 +++++ .../informed-decision-lifecycle/registry.json | 29 + go.mod | 28 +- go.sum | 158 ++-- .../informed_decision_lifecycle_test.go | 137 ++++ ...d-decision-lifecycle-review.candidate.yaml | 28 + ...informed-decision-lifecycle.candidate.yaml | 28 + ...LEX-WP-0033-informed-decision-lifecycle.md | 97 +++ 25 files changed, 2644 insertions(+), 102 deletions(-) create mode 100644 Containerfile.lifecycle create mode 100644 docs/evidence/2026-10-04-infd-lifecycle/lifecycle-policy-validation.json create mode 100644 docs/evidence/2026-10-04-infd-lifecycle/policy-scan.json create mode 100644 docs/evidence/2026-10-04-infd-lifecycle/review-policy-validation.json create mode 100644 examples/informed-decision-lifecycle-review/attribute_admission_v8.json create mode 100644 examples/informed-decision-lifecycle-review/attribute_fixtures.json create mode 100644 examples/informed-decision-lifecycle-review/fixtures.json create mode 100644 examples/informed-decision-lifecycle-review/intake-submitters.json create mode 100644 examples/informed-decision-lifecycle-review/list_only_records.json create mode 100644 examples/informed-decision-lifecycle-review/pin.json create mode 100644 examples/informed-decision-lifecycle-review/policy.md create mode 100644 examples/informed-decision-lifecycle-review/records.json create mode 100644 examples/informed-decision-lifecycle-review/registry.json create mode 100644 examples/informed-decision-lifecycle/fixtures.json create mode 100644 examples/informed-decision-lifecycle/pin.json create mode 100644 examples/informed-decision-lifecycle/policy.md create mode 100644 examples/informed-decision-lifecycle/registry.json create mode 100644 internal/policy/informed_decision_lifecycle_test.go create mode 100644 values/informed-decision-lifecycle-review.candidate.yaml create mode 100644 values/informed-decision-lifecycle.candidate.yaml create mode 100644 workplans/FLEX-WP-0033-informed-decision-lifecycle.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a69abc0..82a7264 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version-file: go.mod - name: Verify dependencies run: go mod verify @@ -43,7 +43,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.22" + go-version-file: go.mod - name: golangci-lint uses: golangci/golangci-lint-action@v6 diff --git a/Containerfile b/Containerfile index 54dcf1c..2050c6a 100644 --- a/Containerfile +++ b/Containerfile @@ -1,11 +1,11 @@ -FROM golang:1.23-alpine AS build +FROM golang:1.26.7-alpine@sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468 AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/flex-auth ./cmd/flex-auth -FROM gcr.io/distroless/static-debian12:nonroot +FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab COPY --from=build /out/flex-auth /usr/local/bin/flex-auth COPY examples /opt/flex-auth/examples EXPOSE 8080 diff --git a/Containerfile.lifecycle b/Containerfile.lifecycle new file mode 100644 index 0000000..d510d4d --- /dev/null +++ b/Containerfile.lifecycle @@ -0,0 +1,14 @@ +# Same pinned engine build; candidate policies are included by COPY examples. +FROM golang:1.26.7-alpine@sha256:28d89ee9cc0ff9fec75c82ca201e6bf7fdf9a679d4b7b24dfa04f2bb766bb468 AS build +WORKDIR /src +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/flex-auth ./cmd/flex-auth + +FROM gcr.io/distroless/static-debian12:nonroot@sha256:afa5c872c891853ca7fcf1f12c3edb23f7eeef36189728842dd51042ff57f7ab +COPY --from=build /out/flex-auth /usr/local/bin/flex-auth +COPY examples /opt/flex-auth/examples +EXPOSE 8080 +ENTRYPOINT ["/usr/local/bin/flex-auth"] +CMD ["serve", "--addr", "0.0.0.0:8080", "--registry", "/opt/flex-auth/examples/tenant-engine/registry_snapshot.json", "--policy", "/opt/flex-auth/examples/tenant-engine/policy_package.md"] diff --git a/docs/evidence/2026-10-04-infd-lifecycle/lifecycle-policy-validation.json b/docs/evidence/2026-10-04-infd-lifecycle/lifecycle-policy-validation.json new file mode 100644 index 0000000..fe1c1f4 --- /dev/null +++ b/docs/evidence/2026-10-04-infd-lifecycle/lifecycle-policy-validation.json @@ -0,0 +1,88 @@ +{ + "valid": true, + "caring_findings": [ + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed canonical roles", + "fields": [ + "caring.canonical_roles" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed organization relations", + "fields": [ + "caring.organization_relations" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed scopes", + "fields": [ + "caring.scopes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed planes", + "fields": [ + "caring.planes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed capabilities", + "fields": [ + "caring.capabilities" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed exposure modes", + "fields": [ + "caring.exposure_modes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed conditions", + "fields": [ + "caring.conditions" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed restrictions", + "fields": [ + "caring.restrictions" + ] + } + ], + "tests": [ + { + "name": "test_empty_denied", + "passed": true + } + ], + "fixtures": [ + { + "id": "unknown-request-denied", + "passed": true, + "expected": { + "effect": "deny" + }, + "actual": { + "effect": "deny", + "reason": "lifecycle_scope_or_identity_refused" + } + } + ] +} diff --git a/docs/evidence/2026-10-04-infd-lifecycle/policy-scan.json b/docs/evidence/2026-10-04-infd-lifecycle/policy-scan.json new file mode 100644 index 0000000..6462967 --- /dev/null +++ b/docs/evidence/2026-10-04-infd-lifecycle/policy-scan.json @@ -0,0 +1,17 @@ +{ + "scanner": "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969", + "scanner_version": "0.74.0", + "scanned_at": "2026-10-04T19:35:45.454792771Z", + "artifact": "flex-auth:wp0007-candidate", + "image_id": "sha256:f6ed8af69af766d7e9498465a23c292d91185b79352065736a2132216aac1d37", + "severities": [ + "HIGH", + "CRITICAL" + ], + "vulnerabilities": 0, + "full_report_sha256": "e41d19c961f0e0d3a4051362c6769edc3069b4286612239861818d403c16ee6a", + "warnings": [ + "vendor severity selection" + ], + "scope": "candidate artifact scan; not a general security attestation" +} diff --git a/docs/evidence/2026-10-04-infd-lifecycle/review-policy-validation.json b/docs/evidence/2026-10-04-infd-lifecycle/review-policy-validation.json new file mode 100644 index 0000000..dca2cad --- /dev/null +++ b/docs/evidence/2026-10-04-infd-lifecycle/review-policy-validation.json @@ -0,0 +1,232 @@ +{ + "valid": true, + "caring_findings": [ + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed canonical roles", + "fields": [ + "caring.canonical_roles" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed organization relations", + "fields": [ + "caring.organization_relations" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed scopes", + "fields": [ + "caring.scopes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed planes", + "fields": [ + "caring.planes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed capabilities", + "fields": [ + "caring.capabilities" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed exposure modes", + "fields": [ + "caring.exposure_modes" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed conditions", + "fields": [ + "caring.conditions" + ] + }, + { + "code": "CARING-POLICY-MISSING-DIMENSION", + "severity": "warning", + "message": "policy package should declare governed restrictions", + "fields": [ + "caring.restrictions" + ] + } + ], + "tests": [ + { + "name": "test_allow_does_not_prove_the_pin", + "passed": true + }, + { + "name": "test_approval_id_malformed", + "passed": true + }, + { + "name": "test_attribute_acts_are_eligible_only_when_sets_admit", + "passed": true + }, + { + "name": "test_bad_memo_id_refused", + "passed": true + }, + { + "name": "test_digest_mismatch", + "passed": true + }, + { + "name": "test_exact_pin_mismatch_is_not_rescued", + "passed": true + }, + { + "name": "test_exact_record_ignores_attribute_context", + "passed": true + }, + { + "name": "test_future_mfa_is_identity_refused", + "passed": true + }, + { + "name": "test_human_control_false", + "passed": true + }, + { + "name": "test_initial_release_allows_only_decision_record_class", + "passed": true + }, + { + "name": "test_initial_release_sets", + "passed": true + }, + { + "name": "test_list_does_not_use_attribute_admission", + "passed": true + }, + { + "name": "test_listable_is_exact_union", + "passed": true + }, + { + "name": "test_memo_review_group_is_not_hardcoded", + "passed": true + }, + { + "name": "test_memo_version_refused", + "passed": true + }, + { + "name": "test_requester_mismatch", + "passed": true + }, + { + "name": "test_review_group_must_be_verified", + "passed": true + }, + { + "name": "test_service_subject_is_identity_refused", + "passed": true + }, + { + "name": "test_six_exact_metered_memos_allow_human_review", + "passed": true + }, + { + "name": "test_stale_mfa_on_attribute_branch", + "passed": true + }, + { + "name": "test_t03_records_not_in_act_scope", + "passed": true + }, + { + "name": "test_tool_proof_exact_record_allowed", + "passed": true + }, + { + "name": "test_tool_proof_without_mfa_denied", + "passed": true + }, + { + "name": "test_tool_proof_wrong_digest_denied", + "passed": true + }, + { + "name": "test_tool_proof_wrong_native_id_denied", + "passed": true + }, + { + "name": "test_unknown_class", + "passed": true + }, + { + "name": "test_unknown_request_denied", + "passed": true + }, + { + "name": "test_unknown_submitter", + "passed": true + } + ], + "fixtures": [ + { + "id": "unknown-request-denied", + "passed": true, + "expected": { + "effect": "deny" + }, + "actual": { + "effect": "deny", + "reason": "compact_sitting_scope_or_identity_refused" + } + }, + { + "id": "attribute-non-human-refused", + "passed": true, + "expected": { + "effect": "deny", + "reason": "identity_refused" + }, + "actual": { + "effect": "deny", + "reason": "identity_refused" + } + }, + { + "id": "attribute-stale-mfa", + "passed": true, + "expected": { + "effect": "deny", + "reason": "stale_mfa" + }, + "actual": { + "effect": "deny", + "reason": "stale_mfa" + } + }, + { + "id": "attribute-future-mfa", + "passed": true, + "expected": { + "effect": "deny", + "reason": "identity_refused" + }, + "actual": { + "effect": "deny", + "reason": "identity_refused" + } + } + ] +} diff --git a/examples/informed-decision-lifecycle-review/attribute_admission_v8.json b/examples/informed-decision-lifecycle-review/attribute_admission_v8.json new file mode 100644 index 0000000..67b98d0 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/attribute_admission_v8.json @@ -0,0 +1,303 @@ +{ + "policy_id": "informed-decision.compact-sitting", + "policy_version": "v11", + "note": "Offline request/response fixtures. Shipped admitted sets are empty, so attribute_eligible is not in this file. An allow here is an exact-record pin, not proof of a live approval.", + "cases": [ + { + "id": "exact-record-allow", + "kind": "exact", + "assurance": "now", + "expect": {"effect": "allow", "reason": "operator_admitted_compact_sitting"}, + "request": { + "id": "exact-record-allow", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "accept", + "resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773" + }, + "policy_version": "v8" + } + }, + { + "id": "exact-record-wrong-digest", + "kind": "exact", + "assurance": "now", + "expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"}, + "request": { + "id": "exact-record-wrong-digest", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "accept", + "resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:0000000000000000000000000000000000000000000000000000000000000000" + }, + "policy_version": "v8" + } + }, + { + "id": "exact-record-with-attribute-context", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "allow", "reason": "operator_admitted_compact_sitting"}, + "request": { + "id": "exact-record-with-attribute-context", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "read", + "resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773" + }, + "policy_version": "v8" + } + }, + { + "id": "exact-pin-mismatch-not-rescued", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"}, + "request": { + "id": "exact-pin-mismatch-not-rescued", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "accept", + "resource": {"id": "memo:infd-20260927-b02", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + }, + "policy_version": "v8" + } + }, + { + "id": "attribute-unknown-class", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "deny", "reason": "unknown_class"}, + "request": { + "id": "attribute-unknown-class", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "decline", + "resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + } + }, + { + "id": "attribute-stale-mfa", + "kind": "attribute", + "assurance": "stale", + "expect": {"effect": "deny", "reason": "stale_mfa"}, + "request": { + "id": "attribute-stale-mfa", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "accept", + "resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": false, + "memo_version": 1, + "approval_id": "not-a-uuid", + "approval_binding_digest": "sha256:wrong" + }, + "policy_version": "v8" + } + }, + { + "id": "attribute-review-group", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "deny", "reason": "review_group_not_verified"}, + "request": { + "id": "attribute-review-group", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["other-group"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "discuss", + "resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 2, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + } + }, + { + "id": "attribute-bad-memo-id", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "deny", "reason": "resource_refused"}, + "request": { + "id": "attribute-bad-memo-id", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "return", + "resource": {"id": "memo:NO", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + } + }, + { + "id": "attribute-list-stays-exact", + "kind": "attribute", + "assurance": "now", + "expect": {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"}, + "request": { + "id": "attribute-list-stays-exact", + "tenant": "tenant:platform", + "subject": { + "id": "synthetic-reviewer", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", "methods": ["pwd", "otp"], "at": 0} + } + }, + "action": "list", + "resource": {"id": "memo:intake-new-memo", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + } + } + ] +} diff --git a/examples/informed-decision-lifecycle-review/attribute_fixtures.json b/examples/informed-decision-lifecycle-review/attribute_fixtures.json new file mode 100644 index 0000000..71a8d9d --- /dev/null +++ b/examples/informed-decision-lifecycle-review/attribute_fixtures.json @@ -0,0 +1,140 @@ +[ + { + "id": "attribute-non-human-refused", + "request": { + "id": "attribute-non-human", + "tenant": "tenant:platform", + "subject": { + "id": "not-a-person", + "type": "service", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 1 + } + } + }, + "action": "accept", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "identity_refused" + } + }, + { + "id": "attribute-stale-mfa", + "request": { + "id": "attribute-stale-mfa", + "tenant": "tenant:platform", + "subject": { + "id": "test-human", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 1 + } + } + }, + "action": "accept", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "stale_mfa" + } + }, + { + "id": "attribute-future-mfa", + "request": { + "id": "attribute-future-mfa", + "tenant": "tenant:platform", + "subject": { + "id": "test-human", + "type": "human", + "tenant": "tenant:platform", + "attributes": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "groups": ["net-kingdom-admins"], + "assurance": { + "level": "aal2", + "mfa": true, + "source": "key-cape", + "methods": ["pwd", "otp"], + "at": 9999999999 + } + } + }, + "action": "read", + "resource": { + "id": "memo:intake-new-memo", + "type": "decision-memo", + "system": "informed-decision", + "tenant": "tenant:platform" + }, + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "policy_version": "v8" + }, + "expect": { + "effect": "deny", + "reason": "identity_refused" + } + } +] diff --git a/examples/informed-decision-lifecycle-review/fixtures.json b/examples/informed-decision-lifecycle-review/fixtures.json new file mode 100644 index 0000000..a4b6f93 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/fixtures.json @@ -0,0 +1,21 @@ +[ + { + "id": "unknown-request-denied", + "request": { + "id": "unknown", + "subject": { + "id": "unknown", + "type": "human" + }, + "action": "accept", + "resource": { + "id": "memo:unrelated", + "type": "decision-memo", + "system": "informed-decision" + } + }, + "expect": { + "effect": "deny" + } + } +] diff --git a/examples/informed-decision-lifecycle-review/intake-submitters.json b/examples/informed-decision-lifecycle-review/intake-submitters.json new file mode 100644 index 0000000..5972773 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/intake-submitters.json @@ -0,0 +1,12 @@ +{ + "system:serviceaccount:state-hub:state-hub": { + "submitter_id": "state-hub", + "memo_classes": [ + "decision-record" + ], + "review_groups": [ + "net-kingdom-admins" + ], + "daily_limit": 50 + } +} diff --git a/examples/informed-decision-lifecycle-review/list_only_records.json b/examples/informed-decision-lifecycle-review/list_only_records.json new file mode 100644 index 0000000..5278f13 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/list_only_records.json @@ -0,0 +1,20 @@ +{ + "memo:SECRETS-WP-0010-T03-apply": { + "approval_id": "b5fcbfcc-ad56-456f-bc36-c823052e9917", + "binding_digest": "sha256:03cc5b37437f14e86b686ce4054f976556334eff3fa260b03e8014ed3d9217e5", + "memo_version": 3, + "label": "t03-apply" + }, + "memo:SECRETS-WP-0010-T03-verify": { + "approval_id": "4e9ff881-d59f-4143-9155-929afbe8dd43", + "binding_digest": "sha256:72d9267d038c17107c880ffc9009793ce9c70defbddfe2219628854b811a04b2", + "memo_version": 3, + "label": "t03-verify" + }, + "memo:SECRETS-WP-0010-T03-exec": { + "approval_id": "a24e0898-b3c5-4e50-85a8-18a2c2aa229e", + "binding_digest": "sha256:f2cf0fb53b740900756ccde5587c3578fcff44beef2f1edab17b9a198a4033d8", + "memo_version": 3, + "label": "t03-exec" + } +} diff --git a/examples/informed-decision-lifecycle-review/pin.json b/examples/informed-decision-lifecycle-review/pin.json new file mode 100644 index 0000000..4d7df37 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/pin.json @@ -0,0 +1,5 @@ +{ + "package": "informed-decision.compact-sitting", + "version": "v12", + "package_digest": "sha256:c5387f7d198dc39ab5967e0c218ce05cc779693565c8875e3035e45ec002a2db" +} diff --git a/examples/informed-decision-lifecycle-review/policy.md b/examples/informed-decision-lifecycle-review/policy.md new file mode 100644 index 0000000..9487256 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/policy.md @@ -0,0 +1,708 @@ +--- +id: informed-decision.compact-sitting +name: Compact sitting exact-record human review +namespace: informed-decision:decision-memo +version: v12 +status: ready +package: flexauth.informed_decision.compact_sitting +allow_ttl: 30s +actions: [read, acknowledge, accept, return, discuss, decline, list] +owner: flex-auth +fixtures: [fixtures.json, attribute_fixtures.json] +caring: + profile: caring-0.4.0-rc2 + enforce: false +activation: + mode: local +--- + +# Lifecycle review release candidate + +This v12 candidate retains the deployed v11 exact records and adds only +State Hub / decision-record attribute admission for INFD-WP-0007. The unchanged +sitting directory and deployed values remain the v11 rollback reference. New +lifecycle decisions use this attribute branch without package regeneration. +Review groups remain verified by the consumer and the subject identity. + +# Compact sitting review mandate + +This is a new exact-record package for seven 2026-09-14 sitting memos. +v2 (2026-09-21) adds exactly one record, `memo:infd-20260921-b01` (the +SECRETS-WP-0009-T03 spend envelope), with native pins from informed-decision +`docs/evidence/2026-09-21-budget-approval-create.json`. The seven v1 records +and the identity bar are unchanged. +It is not an expansion of `examples/informed-decision-t03` / FLEX-WP-0027. +`memo:infd-20260914-c01` is omitted (create-client still undecided). + +v3 (2026-09-21, `FLEX-WP-0032`, `FLEX-DEC-2026-017`) adds one action, `list`, +for informed-decision's decision overview (`INFD-IN-0008`). It is a separate +rule, not an extra member of the act set, and it changes nothing else: + +- `read` and the five acts keep the v2 rule unchanged — the same eight records, + the same 900-second MFA window. A `list` allow satisfies no other action. +- `list` covers the **union** of this package's eight records and the three + `SECRETS-WP-0010-T03-*` records from `informed-decision.t03-review`, each + pinned by approval id, binding digest and its own memo version (T03 at 3). + Scope stays exact-record: a memo is listable only once someone admitted it. + The operator declined resource-type-wide scope because the PDP checks no + recipient, and a type-wide rule would have left consumer code — which the + consumer itself says is not an entitlement — as the only scope. +- `list` carries the same identity bar with a **12-hour** MFA window instead of + 900 seconds. Looking back at your own outcomes is not binding a new one. The + bar is lighter, not absent: the PDP still states a bound. +- The KeyCape stale-timestamp defect (`INFD-IN-0005`) is **not** worked around + here. `read` stays strict; that defect is key-cape's to fix. + +v4 (2026-09-27, REINAH-WP-0003-T06) adds exactly one record for the +EUR 10 single-attempt tool proof. Native pins come from informed-decision +`docs/evidence/2026-09-27-tool-budget-approval-create.json`. Existing records, +actions and identity requirements remain unchanged. + +v5 (2026-09-27, REINAH-WP-0003-T06) admits the replacement-window memo +`memo:infd-20260927-b02` with fresh native pins. The accepted b01 record is +preserved; this change does not extend its signed execution window. + +v6 (2026-09-27, REINAH-WP-0003-T06) admits six exact native metered +credential requests, apply/verify/exec per provider and worker lane. All acts +still require the authenticated human and fresh MFA. Ordinary worker approvals +retain their native control type and require exact-ID consumer configuration. + +v7 (2026-10-03, INFD-WP-ADHOC-2026-10-03-T02) adds the exact +`memo:infd-20261003-p01` delivery-proof acceptance record. Native pins come +from informed-decision `docs/evidence/2026-10-03-provisioning-window-approval-create.json`. +Existing records and identity requirements are preserved. + +v8 (2026-10-03, offline attribute admission) adds one branch beside those +exact-record rules. It is not the deployed sitting pin. Requests whose +resource id is in `records` or `listable` still take only the exact-record +rules, including when they also carry attribute fields, so an exact-record +decision does not change. `list` stays on the exact-record rule. + +The attribute branch may allow `read`, `acknowledge`, `accept`, `return`, +`discuss` and `decline` for any other memo id. `attribute_eligible` is +eligibility only. It checks that the carried approval id is a UUID and the +carried digest matches `sha256:` plus 64 lowercase hex digits. It does not +read an approval and it does not prove the memo pin. The effect owner still +reads the approval live and compares that id and the engine's native digest +with the memo. `approval_human_control` must be boolean true. No presentation +hash is read. Context fields are `review_group`, `memo_class`, +`intake_submitter`, `approval_requester`, `approval_human_control`, +`memo_version`, `approval_id` and `approval_binding_digest`. + +v9 (2026-10-04, EMERGENT-WP-0002 profile adoption) adds exactly one record, +`memo:emergent-profile-adoption-0-2-6`. Native pins come from informed-decision +`docs/evidence/2026-10-04-emergent-profile-approval-create.json`, approval +`05751e09-3a5b-4a41-a4e8-b79e96de6484`. Existing exact records stay. The v8 +attribute branch remains beside them and still admits nobody, because the +shipped submitter and class sets are empty. + +v10 (2026-10-04, STATE-WP-0094 production-steps sitting) adds exactly four +records: `memo:state-hub-edge-receiving-rollout-c287ed8`, +`memo:owner-return-pickup-plan-b6222af`, +`memo:freshness-runner-deploy-trial-e77f8b4` and +`memo:freshness-schedule-enable-retire-sweep`. Native pins come from +informed-decision `docs/evidence/2026-10-04-state-hub-wp0094-approval-create.json`, +approvals `974eae4d-782d-41ed-93be-e34433b5fe59`, +`77993081-1db2-4bba-99f2-74ca1f8652b1`, `4ae04c8e-d604-4d8b-b166-693eef855da8` +and `b75752d1-bab6-4cf3-9308-5368c1d5aa91`. Existing exact records, including +v9's, stay. The v8 attribute branch still admits nobody. + +v11 (2026-10-04, IR-WP-0005-T02) lets the three `list_only_records` be +reviewed. `read`, `acknowledge`, `accept`, `return`, `discuss` and +`decline` allow when the memo id, memo version 3, approval id and binding +digest match those records, with the same 900-second KeyCape bar as the +version-1 act rule. `list` is unchanged. A version other than 3, a foreign +approval id, or a different digest is refused. The version-1 `records` set +is unchanged, and these three ids stay out of it. + +`admitted_submitters` and `admitted_classes` are generated from +informed-decision `deploy/intake-submitters.json`. The shipped registry is +empty, so both sets are empty and this branch admits nobody. Regenerate with +`python3 tools/gen_informed_decision_admission.py`; `--check` reports drift. + +The identity bar matches T03: authenticated informed-decision caller, +`net-kingdom-admins`, fresh KeyCape AAL2 MFA. No permission follows from +memo content or presentation state. TokenReview must admit +`system:serviceaccount:informed-decision:review` before this package is +served. Membership tenant provenance may follow the accepted registration +route; it does not assert directory membership in tenant:platform. +Only a real human uses accept. This package neither issues nor consumes +approval. Native pins: `docs/evidence/2026-09-15-sitting-approval-creates.json`. + +```rego +import rego.v1 + +records := { + "memo:state-hub-edge-receiving-rollout-c287ed8": { + "approval_id": "974eae4d-782d-41ed-93be-e34433b5fe59", + "binding_digest": "sha256:84735b88b2db20d9445af369e28a8d40bd495dd20c0303ff3a764666ec976dde" + }, + "memo:owner-return-pickup-plan-b6222af": { + "approval_id": "77993081-1db2-4bba-99f2-74ca1f8652b1", + "binding_digest": "sha256:e745ef64658e77e41b2a5ef569ec0f6986e120d338656d75749425f5f29b7ca0" + }, + "memo:freshness-runner-deploy-trial-e77f8b4": { + "approval_id": "4ae04c8e-d604-4d8b-b166-693eef855da8", + "binding_digest": "sha256:f8ff89dd353c1d2006622c45996c0f05f050865f8c203373dc74da1ba3f05e24" + }, + "memo:freshness-schedule-enable-retire-sweep": { + "approval_id": "b75752d1-bab6-4cf3-9308-5368c1d5aa91", + "binding_digest": "sha256:cc3e8fe6ace5438f19d647d3de226407c8b009985c82ad9a993e81226e6f316f" + }, + "memo:infd-20261003-p01": { + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, + "memo:emergent-profile-adoption-0-2-6": { + "approval_id": "05751e09-3a5b-4a41-a4e8-b79e96de6484", + "binding_digest": "sha256:7843e660694dcd7c806dc02ee0cca05dd3a96488f614b1946291496259a02001" + }, + "memo:infd-20260914-c02": { + "approval_id": "ccfd8007-2061-48fd-9356-99b16279dac1", + "binding_digest": "sha256:942cf7d5e6805987df2077a0cc6cde75b148e1729a96dd0692153287820b6a0a" + }, + "memo:infd-20260914-c03": { + "approval_id": "a0611d0d-b9ef-4b9a-aa79-e94a9e3fcd5f", + "binding_digest": "sha256:95cae135b994acea6dad9da978dd3df1aa3354de900a1f43c2e5a03a20e8e844" + }, + "memo:infd-20260914-c04": { + "approval_id": "22ac6df3-e6a9-472b-a68e-c8caca4289a7", + "binding_digest": "sha256:dec960b30efaf654abbdcd8a2cdd20d4ff6bf52e170744f817dc55845d9cb192" + }, + "memo:infd-20260914-d01": { + "approval_id": "9f7c3506-68de-4826-86ff-e301f428408d", + "binding_digest": "sha256:8cb7cc93f38db9d19b49e201e0554b50af1891fec853fef333a06ea078541d15" + }, + "memo:infd-20260914-d02": { + "approval_id": "b3ce2c01-0a3b-401e-b4ed-c1954af169c9", + "binding_digest": "sha256:be0c7de123216740d2c2eeaed80384e03e6b6d1668104f94bb8f9cbae7dfe30f" + }, + "memo:infd-20260914-d03": { + "approval_id": "3b483e6b-0b92-45ff-83b6-bc5057c0a496", + "binding_digest": "sha256:3c562278e86bb5e7f747452cf2e99d3a136853ab748676e8359262b835cd1425" + }, + "memo:infd-20260914-d04": { + "approval_id": "356e67a3-5539-46a1-922a-f5591fd38ee5", + "binding_digest": "sha256:3c2d7759679d871cb7c5cac8999542a365e64c918d59d0c4ce864ffb837453e4" + }, + "memo:infd-20260921-b01": { + "approval_id": "ba5ce2d8-8b6d-40be-af89-2e8c147029a3", + "binding_digest": "sha256:e9eaf454e94871ec2060855fcfbd327bcaec873d2c61cae031bec9f95a34bcae" + }, + "memo:infd-20260927-b01": { + "approval_id": "e9521804-8fce-4fbc-bfdf-8a1aa959fd61", + "binding_digest": "sha256:7aefcee9b79eb3285997066b323ae1772e13d58f875aacc6eb5e9407bd1c1185" + }, + "memo:infd-20260927-b02": { + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773" + }, + "memo:metered-20260927-provider-apply": { + "approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc", + "binding_digest": "sha256:a36b88244c26b1ffcd765cbadc201ff79260c7bcbd71d20d1584c3c0a3b1c3de" + }, + "memo:metered-20260927-provider-verify": { + "approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb", + "binding_digest": "sha256:98636335ccd36286c02fe018f296fd7d4dfd091ee89d27fe1b629e3db00cf121" + }, + "memo:metered-20260927-provider-exec": { + "approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee", + "binding_digest": "sha256:8b31dd5911552b901b1f807a8cd6348d09547f3dbb2a52e3b42ad9fd948a2b01" + }, + "memo:metered-20260927-worker-apply": { + "approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769", + "binding_digest": "sha256:412b79fb537d4444e52e3358820b6ea204fa17173a4420ab6372c52d010ee018" + }, + "memo:metered-20260927-worker-verify": { + "approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099", + "binding_digest": "sha256:bce6e2091ed076ec0040804d24c822078ac1eda18cd62703801fc9da77898303" + }, + "memo:metered-20260927-worker-exec": { + "approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38", + "binding_digest": "sha256:ae261c9271a21b626be354646054f493d0951f69aecdbde3cfce3ac0c5667e8f" + } +} + +list_only_records := { + "memo:SECRETS-WP-0010-T03-apply": { + "approval_id": "b5fcbfcc-ad56-456f-bc36-c823052e9917", + "binding_digest": "sha256:03cc5b37437f14e86b686ce4054f976556334eff3fa260b03e8014ed3d9217e5", + "memo_version": 3 + }, + "memo:SECRETS-WP-0010-T03-verify": { + "approval_id": "4e9ff881-d59f-4143-9155-929afbe8dd43", + "binding_digest": "sha256:72d9267d038c17107c880ffc9009793ce9c70defbddfe2219628854b811a04b2", + "memo_version": 3 + }, + "memo:SECRETS-WP-0010-T03-exec": { + "approval_id": "a24e0898-b3c5-4e50-85a8-18a2c2aa229e", + "binding_digest": "sha256:f2cf0fb53b740900756ccde5587c3578fcff44beef2f1edab17b9a198a4033d8", + "memo_version": 3 + } +} + +# listable is the union list may range over. Sitting records are memo version 1. +# T03 records carry memo version 3 and are acted on only through list_only_records. +listable := object.union( + {id: object.union(r, {"memo_version": 1}) | some id, r in records}, + list_only_records, +) + +# BEGIN GENERATED admitted_sets (tools/gen_informed_decision_admission.py; do not edit by hand) +admitted_submitters := { + "state-hub", +} + +admitted_classes := { + "decision-record", +} +# END GENERATED admitted_sets + +exact_id if { + id := object.get(object.get(input, "resource", {}), "id", "") + listable[id] +} + +attribute_attempt if { + not exact_id + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"} + ctx := object.get(input, "context", {}) + some key in {"review_group", "memo_class", "intake_submitter", "approval_requester", "approval_human_control"} + object.get(ctx, key, null) != null +} + +identity_core if { + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 +} + +mfa_fresh if { + identity_core + age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at + age >= -30 + age <= 900 +} + +stale_mfa if { + identity_core + age := time.now_ns() / 1000000000 - input.subject.attributes.assurance.at + age > 900 +} + +resource_ok if { + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + is_string(input.resource.id) + regex.match(`^memo:[a-z0-9][a-z0-9-]{2,62}$`, input.resource.id) +} + +review_group_ok if { + group := input.context.review_group + is_string(group) + group != "" + group in input.subject.attributes.groups +} + +class_ok if { + is_string(input.context.memo_class) + input.context.memo_class in admitted_classes +} + +submitter_ok if { + is_string(input.context.intake_submitter) + input.context.intake_submitter in admitted_submitters +} + +requester_ok if { + input.context.approval_requester == "informed-decision" +} + +human_control_ok if { + input.context.approval_human_control == true +} + +approval_id_ok if { + is_string(input.context.approval_id) + regex.match(`^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`, input.context.approval_id) +} + +digest_ok if { + is_string(input.context.approval_binding_digest) + regex.match(`^sha256:[0-9a-f]{64}$`, input.context.approval_binding_digest) +} + +version_ok if { + is_number(input.context.memo_version) + input.context.memo_version >= 1 +} + +decision := {"effect": "allow", "reason": "operator_admitted_compact_sitting"} if { + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 900 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := records[input.resource.id] + input.context.memo_version == 1 + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"} +} else := {"effect": "allow", "reason": "operator_admitted_t03_review_act"} if { + input.action in {"read", "acknowledge", "accept", "return", "discuss", "decline"} + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 900 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := list_only_records[input.resource.id] + input.context.memo_version == record.memo_version + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest +} else := {"effect": "allow", "reason": "operator_admitted_overview_list"} if { + input.action == "list" + input.tenant == "tenant:platform" + input.subject.tenant == "tenant:platform" + input.subject.type == "human" + is_string(input.subject.id) + input.subject.id != "" + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + assurance.at > 0 + age := time.now_ns() / 1000000000 - assurance.at + age >= -30 + age <= 43200 + input.resource.tenant == "tenant:platform" + input.resource.system == "informed-decision" + input.resource.type == "decision-memo" + record := listable[input.resource.id] + input.context.memo_version == record.memo_version + input.context.approval_id == record.approval_id + input.context.approval_binding_digest == record.binding_digest +} else := {"effect": "allow", "reason": "attribute_eligible"} if { + attribute_attempt + mfa_fresh + resource_ok + review_group_ok + class_ok + submitter_ok + requester_ok + human_control_ok + approval_id_ok + digest_ok + version_ok +} else := {"effect": "deny", "reason": "identity_refused"} if { + attribute_attempt + not identity_core +} else := {"effect": "deny", "reason": "stale_mfa"} if { + attribute_attempt + stale_mfa +} else := {"effect": "deny", "reason": "identity_refused"} if { + attribute_attempt + not mfa_fresh +} else := {"effect": "deny", "reason": "resource_refused"} if { + attribute_attempt + not resource_ok +} else := {"effect": "deny", "reason": "review_group_not_verified"} if { + attribute_attempt + not review_group_ok +} else := {"effect": "deny", "reason": "unknown_class"} if { + attribute_attempt + not class_ok +} else := {"effect": "deny", "reason": "unknown_submitter"} if { + attribute_attempt + not submitter_ok +} else := {"effect": "deny", "reason": "requester_mismatch"} if { + attribute_attempt + not requester_ok +} else := {"effect": "deny", "reason": "human_control_false"} if { + attribute_attempt + not human_control_ok +} else := {"effect": "deny", "reason": "approval_id_malformed"} if { + attribute_attempt + not approval_id_ok +} else := {"effect": "deny", "reason": "digest_mismatch"} if { + attribute_attempt + not digest_ok +} else := {"effect": "deny", "reason": "memo_version_refused"} if { + attribute_attempt + not version_ok +} else := {"effect": "deny", "reason": "compact_sitting_scope_or_identity_refused"} if { + true +} +``` + +```rego test +package flexauth.informed_decision.compact_sitting_test +import rego.v1 +import data.flexauth.informed_decision.compact_sitting + +test_unknown_request_denied if { + compact_sitting.decision.effect == "deny" with input as {} +} + +test_listable_is_exact_union if { + count(compact_sitting.listable) == count(compact_sitting.records) + count(compact_sitting.list_only_records) +} + +test_t03_records_not_in_act_scope if { + every id, _ in compact_sitting.list_only_records { + not compact_sitting.records[id] + } +} + +tool_proof_input := { + "tenant": "tenant:platform", + "subject": {"id": "test-human", "type": "human", "tenant": "tenant:platform", + "attributes": {"principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", + "methods": ["pwd", "otp"], "at": time.now_ns() / 1000000000}}}, + "resource": {"tenant": "tenant:platform", "system": "informed-decision", + "type": "decision-memo", "id": "memo:infd-20260927-b02"}, + "action": "accept", + "context": {"memo_version": 1, + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "approval_binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773"}, +} + +test_tool_proof_exact_record_allowed if { + compact_sitting.decision.effect == "allow" with input as tool_proof_input +} + +test_tool_proof_wrong_native_id_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.context.approval_id as "another-approval" +} + +test_tool_proof_wrong_digest_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.context.approval_binding_digest as "sha256:wrong" +} + +test_tool_proof_without_mfa_denied if { + compact_sitting.decision.effect == "deny" with input as tool_proof_input + with input.subject.attributes.assurance.mfa as false +} + + +test_six_exact_metered_memos_allow_human_review if { + every id in ["memo:metered-20260927-provider-apply", "memo:metered-20260927-provider-verify", "memo:metered-20260927-provider-exec", "memo:metered-20260927-worker-apply", "memo:metered-20260927-worker-verify", "memo:metered-20260927-worker-exec"] { + record := compact_sitting.records[id] + request := object.union(tool_proof_input, { + "resource": object.union(tool_proof_input.resource, {"id": id}), + "context": {"memo_version": 1, "approval_id": record.approval_id, + "approval_binding_digest": record.binding_digest}, + }) + compact_sitting.decision.effect == "allow" with input as request + } +} + +attribute_input := { + "tenant": "tenant:platform", + "subject": {"id": "test-human", "type": "human", "tenant": "tenant:platform", + "attributes": {"principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", "groups": ["net-kingdom-admins"], + "assurance": {"level": "aal2", "mfa": true, "source": "key-cape", + "methods": ["pwd", "otp"], "at": time.now_ns() / 1000000000}}}, + "resource": {"tenant": "tenant:platform", "system": "informed-decision", + "type": "decision-memo", "id": "memo:intake-new-memo"}, + "action": "accept", + "context": { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "memo_version": 1, + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "approval_binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4" + }, +} + +patched_context(req, patch) := object.union(req, {"context": object.union(req.context, patch)}) + +with_at(req, at) := object.union(req, {"subject": object.union(req.subject, {"attributes": object.union(req.subject.attributes, {"assurance": object.union(req.subject.attributes.assurance, {"at": at})})})}) + +eligible(req) := result if { + result := compact_sitting.decision with input as req + with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"} + with data.flexauth.informed_decision.compact_sitting.admitted_submitters as {"state-hub"} +} + +classes_only(req) := result if { + result := compact_sitting.decision with input as req + with data.flexauth.informed_decision.compact_sitting.admitted_classes as {"credential-lane"} +} + +test_initial_release_sets if { + compact_sitting.admitted_classes == {"decision-record"} + compact_sitting.admitted_submitters == {"state-hub"} + compact_sitting.decision.reason == "unknown_class" with input as attribute_input +} + +test_initial_release_allows_only_decision_record_class if { + every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] { + req := object.union(patched_context(attribute_input, {"memo_class": "decision-record"}), {"action": action}) + compact_sitting.decision.reason == "attribute_eligible" with input as req + } +} + +test_attribute_acts_are_eligible_only_when_sets_admit if { + every action in ["read", "acknowledge", "accept", "return", "discuss", "decline"] { + eligible(object.union(attribute_input, {"action": action})).reason == "attribute_eligible" + } +} + +test_allow_does_not_prove_the_pin if { + other := "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + attribute_input.context.approval_binding_digest != other + eligible(attribute_input).reason == "attribute_eligible" + eligible(patched_context(attribute_input, {"approval_binding_digest": other})).reason == "attribute_eligible" +} + +test_unknown_class if { + eligible(patched_context(attribute_input, {"memo_class": "not-a-class"})).reason == "unknown_class" +} + +test_unknown_submitter if { + classes_only(patched_context(attribute_input, {"intake_submitter": "unknown"})).reason == "unknown_submitter" +} + +test_requester_mismatch if { + eligible(patched_context(attribute_input, {"approval_requester": "other-service"})).reason == "requester_mismatch" +} + +test_human_control_false if { + eligible(patched_context(attribute_input, {"approval_human_control": false})).reason == "human_control_false" + eligible(patched_context(attribute_input, {"approval_human_control": "true"})).reason == "human_control_false" +} + +test_digest_mismatch if { + eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:wrong"})).reason == "digest_mismatch" + eligible(patched_context(attribute_input, {"approval_binding_digest": "sha256:C8A1A9C8FB22616E3AACFB877BA7974FEB5EEDAED491F856A6C90719C502EBE4"})).reason == "digest_mismatch" +} + +test_approval_id_malformed if { + eligible(patched_context(attribute_input, {"approval_id": "not-a-uuid"})).reason == "approval_id_malformed" +} + +test_memo_version_refused if { + eligible(patched_context(attribute_input, {"memo_version": 0})).reason == "memo_version_refused" +} + +test_stale_mfa_on_attribute_branch if { + eligible(with_at(attribute_input, time.now_ns() / 1000000000 - 901)).reason == "stale_mfa" +} + +test_future_mfa_is_identity_refused if { + eligible(with_at(attribute_input, time.now_ns() / 1000000000 + 300)).reason == "identity_refused" +} + +test_service_subject_is_identity_refused if { + eligible(object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"type": "service"})})).reason == "identity_refused" +} + +test_review_group_must_be_verified if { + req := object.union(attribute_input, {"subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["other-group"]})})}) + eligible(req).reason == "review_group_not_verified" +} + +test_memo_review_group_is_not_hardcoded if { + req := object.union(attribute_input, { + "subject": object.union(attribute_input.subject, {"attributes": object.union(attribute_input.subject.attributes, {"groups": ["cabinet-reviewers"]})}), + "context": object.union(attribute_input.context, {"review_group": "cabinet-reviewers"}), + }) + eligible(req).reason == "attribute_eligible" +} + +test_bad_memo_id_refused if { + eligible(object.union(attribute_input, {"resource": object.union(attribute_input.resource, {"id": "memo:NO"})})).reason == "resource_refused" +} + +test_list_does_not_use_attribute_admission if { + got := eligible(object.union(attribute_input, {"action": "list"})) + got.effect == "deny" + got.reason == "compact_sitting_scope_or_identity_refused" +} + +test_exact_record_ignores_attribute_context if { + req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "view_hash": "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + })}) + got := compact_sitting.decision with input as req + got.effect == "allow" + got.reason == "operator_admitted_compact_sitting" +} + +test_exact_pin_mismatch_is_not_rescued if { + req := object.union(tool_proof_input, {"context": object.union(tool_proof_input.context, { + "review_group": "net-kingdom-admins", + "memo_class": "credential-lane", + "intake_submitter": "state-hub", + "approval_requester": "informed-decision", + "approval_human_control": true, + "approval_binding_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + })}) + got := eligible(req) + got.effect == "deny" + got.reason == "compact_sitting_scope_or_identity_refused" +} + +``` diff --git a/examples/informed-decision-lifecycle-review/records.json b/examples/informed-decision-lifecycle-review/records.json new file mode 100644 index 0000000..f50b1c4 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/records.json @@ -0,0 +1,444 @@ +{ + "memo:infd-20260914-c02": { + "approval_id": "ccfd8007-2061-48fd-9356-99b16279dac1", + "binding_digest": "sha256:942cf7d5e6805987df2077a0cc6cde75b148e1729a96dd0692153287820b6a0a", + "label": "c02", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "railiance-platform", + "purpose": "Accept provisioning of the secrets-engine service JWT login on the reviewed credential lane", + "target": { + "id": "rpf-wp-0035-t02", + "type": "credential-lane", + "system": "railiance-platform" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-c03": { + "approval_id": "a0611d0d-b9ef-4b9a-aa79-e94a9e3fcd5f", + "binding_digest": "sha256:95cae135b994acea6dad9da978dd3df1aa3354de900a1f43c2e5a03a20e8e844", + "label": "c03", + "binding": { + "action": "apply", + "actor": "informed-decision", + "principal": "netkingdom", + "purpose": "Apply the live OpenBao role addition already specified for the operator-tunneled browser callback", + "target": { + "id": "nk-wp-0032-t03", + "type": "openbao-role", + "system": "netkingdom" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-c04": { + "approval_id": "22ac6df3-e6a9-472b-a68e-c8caca4289a7", + "binding_digest": "sha256:dec960b30efaf654abbdcd8a2cdd20d4ff6bf52e170744f817dc55845d9cb192", + "label": "c04", + "binding": { + "action": "attend", + "actor": "informed-decision", + "principal": "ops-warden", + "purpose": "Attend the graded lockdown / break-glass seal for ops-warden trust-root work", + "target": { + "id": "warden-wp-0027-t02", + "type": "trust-root", + "system": "ops-warden" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-d01": { + "approval_id": "9f7c3506-68de-4826-86ff-e301f428408d", + "binding_digest": "sha256:8cb7cc93f38db9d19b49e201e0554b50af1891fec853fef333a06ea078541d15", + "label": "d01", + "binding": { + "action": "retire", + "actor": "informed-decision", + "principal": "the-custodian", + "purpose": "After HA failover and restore drills pass, retire WSL2 as a State Hub fallback", + "target": { + "id": "cust-wp-0038-t08", + "type": "operating-model", + "system": "the-custodian" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-d02": { + "approval_id": "b3ce2c01-0a3b-401e-b4ed-c1954af169c9", + "binding_digest": "sha256:be0c7de123216740d2c2eeaed80384e03e6b6d1668104f94bb8f9cbae7dfe30f", + "label": "d02", + "binding": { + "action": "confirm", + "actor": "informed-decision", + "principal": "helixforge-factory", + "purpose": "Confirm the existing operator-group claim for CCR-2026-0019 as required by the factory identity/audit/approval path", + "target": { + "id": "ccr-2026-0019", + "type": "ccr", + "system": "helixforge-factory" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-d03": { + "approval_id": "3b483e6b-0b92-45ff-83b6-bc5057c0a496", + "binding_digest": "sha256:3c562278e86bb5e7f747452cf2e99d3a136853ab748676e8359262b835cd1425", + "label": "d03", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "mason", + "purpose": "Accept the fluid-telegram operator credential lane plan so construction may proceed", + "target": { + "id": "mason-wp-0005", + "type": "workplan", + "system": "mason" + } + }, + "memo_version": 1 + }, + "memo:infd-20260914-d04": { + "approval_id": "356e67a3-5539-46a1-922a-f5591fd38ee5", + "binding_digest": "sha256:3c2d7759679d871cb7c5cac8999542a365e64c918d59d0c4ce864ffb837453e4", + "label": "d04", + "binding": { + "action": "confirm", + "actor": "informed-decision", + "principal": "railiance-clock", + "purpose": "Confirm ecosystem ownership and the security role for Railiance Clock as specified", + "target": { + "id": "rclk-wp-0002-t01", + "type": "workplan", + "system": "railiance-clock" + } + }, + "memo_version": 1 + }, + "memo:infd-20260921-b01": { + "approval_id": "ba5ce2d8-8b6d-40be-af89-2e8c147029a3", + "binding_digest": "sha256:e9eaf454e94871ec2060855fcfbd327bcaec873d2c61cae031bec9f95a34bcae", + "label": "b01", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "helixforge-factory", + "purpose": "Accept the spend envelope for metered Glas runs on the Anthropic key", + "target": { + "id": "spend-envelope:hfact-glas-anthropic-2026-09", + "type": "spend-envelope", + "system": "rein-aharness" + } + }, + "memo_version": 1 + }, + "memo:infd-20260927-b01": { + "approval_id": "e9521804-8fce-4fbc-bfdf-8a1aa959fd61", + "binding_digest": "sha256:7aefcee9b79eb3285997066b323ae1772e13d58f875aacc6eb5e9407bd1c1185", + "label": "20260927-b01", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "helixforge-factory", + "purpose": "Accept one EUR 10 metered tool proof; no retry or publication; 2026-09-27T16:35:00Z through 2026-09-27T17:35:00Z", + "target": { + "id": "spend-envelope:hfact-glas-tool-proof-20260927-one-attempt", + "system": "rein-aharness", + "type": "spend-envelope" + } + }, + "memo_version": 1 + }, + "memo:infd-20260927-b02": { + "approval_id": "1e73e514-ef74-4bff-83e7-fa88a3eaba0c", + "binding_digest": "sha256:3e5951847e1909abe1561f561b4591228848a2e1a939b9bf5acab61e2db42773", + "label": "20260927-b02", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "helixforge-factory", + "purpose": "Accept one EUR 10 metered tool proof; no retry or publication; 2026-09-27T18:20:00Z through 2026-09-28T18:20:00Z", + "target": { + "id": "spend-envelope:hfact-glas-tool-proof-20260927-renewed-one-attempt", + "system": "rein-aharness", + "type": "spend-envelope" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-provider-apply": { + "approval_id": "0c05bd0a-f81f-451d-840c-5565628e2edc", + "binding_digest": "sha256:a36b88244c26b1ffcd765cbadc201ff79260c7bcbd71d20d1584c3c0a3b1c3de", + "label": "metered-20260927-provider-apply", + "binding": { + "action": "apply", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Owner-admitted glas-harness agt run through the reviewed local profile; no caller-facing key fetch", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "fields": [], + "policy_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "stage": "prod" + }, + "id": "catalog:glas-claude-agent-dev-anthropic", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-provider-verify": { + "approval_id": "47f118a3-a86c-43ad-969d-42e09a0f45bb", + "binding_digest": "sha256:98636335ccd36286c02fe018f296fd7d4dfd091ee89d27fe1b629e3db00cf121", + "label": "metered-20260927-provider-verify", + "binding": { + "action": "verify", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Owner-admitted glas-harness agt run through the reviewed local profile; no caller-facing key fetch", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "fields": [ + "ANTHROPIC_API_KEY" + ], + "policy_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "stage": "prod" + }, + "id": "catalog:glas-claude-agent-dev-anthropic", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-provider-exec": { + "approval_id": "7b32443a-a817-400c-a130-01b9ef04c8ee", + "binding_digest": "sha256:8b31dd5911552b901b1f807a8cd6348d09547f3dbb2a52e3b42ad9fd948a2b01", + "label": "metered-20260927-provider-exec", + "binding": { + "action": "exec", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Owner-admitted glas-harness agt run through the reviewed local profile; no caller-facing key fetch", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "fields": [ + "ANTHROPIC_API_KEY" + ], + "policy_targets": [ + "se-prod-glas-claude-agent-dev-anthropic" + ], + "stage": "prod" + }, + "id": "catalog:glas-claude-agent-dev-anthropic", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-worker-apply": { + "approval_id": "2ce76d7f-01c3-4b63-8476-8d1230679769", + "binding_digest": "sha256:412b79fb537d4444e52e3358820b6ea204fa17173a4420ab6372c52d010ee018", + "label": "metered-20260927-worker-apply", + "binding": { + "action": "apply", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Claim the admitted hfact-metered ops_run as rein-aharness-metered@railiance01 inside the catalog-bound Glas metered owner", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "fields": [], + "policy_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "stage": "prod" + }, + "id": "catalog:activity-core-metered-worker-token", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-worker-verify": { + "approval_id": "c2af4bcf-15b4-4305-aac1-acc7e4dcb099", + "binding_digest": "sha256:bce6e2091ed076ec0040804d24c822078ac1eda18cd62703801fc9da77898303", + "label": "metered-20260927-worker-verify", + "binding": { + "action": "verify", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Claim the admitted hfact-metered ops_run as rein-aharness-metered@railiance01 inside the catalog-bound Glas metered owner", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "fields": [ + "token" + ], + "policy_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "stage": "prod" + }, + "id": "catalog:activity-core-metered-worker-token", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:metered-20260927-worker-exec": { + "approval_id": "dc22666a-d5d7-46bc-9490-ebe9fe09dc38", + "binding_digest": "sha256:ae261c9271a21b626be354646054f493d0951f69aecdbde3cfce3ac0c5667e8f", + "label": "metered-20260927-worker-exec", + "binding": { + "action": "exec", + "actor": "secrets-engine", + "principal": "secrets-engine", + "purpose": "Claim the admitted hfact-metered ops_run as rein-aharness-metered@railiance01 inside the catalog-bound Glas metered owner", + "target": { + "attributes": { + "auth_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "fields": [ + "token" + ], + "policy_targets": [ + "se-prod-activity-core-metered-worker-token" + ], + "stage": "prod" + }, + "id": "catalog:activity-core-metered-worker-token", + "system": "secrets-engine", + "type": "secret-catalog-lane" + } + }, + "memo_version": 1 + }, + "memo:emergent-profile-adoption-0-2-6": { + "approval_id": "05751e09-3a5b-4a41-a4e8-b79e96de6484", + "binding_digest": "sha256:7843e660694dcd7c806dc02ee0cca05dd3a96488f614b1946291496259a02001", + "label": "emergent-profile-0-2-6", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "emergent-security", + "purpose": "Accept only the non-normative knowledge profile boundary", + "target": { + "id": "emergent-security:knowledge:0.2.6:8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02", + "type": "knowledge-profile", + "system": "emergent-security" + } + }, + "memo_version": 1 + }, + "memo:infd-20261003-p01": { + "approval_id": "706f4205-aaa7-44f6-be30-d0f57664d69c", + "binding_digest": "sha256:c8a1a9c8fb22616e3aacfb877ba7974feb5eedaed491f856a6c90719c502ebe4", + "label": "p01", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "railiance-platform", + "purpose": "Accept the 2026-09-13 scoped delivery proof for the CCR-2026-0019 secrets-engine approval-client reader", + "target": { + "id": "ccr:CCR-2026-0019-delivery-proof", + "type": "credential-lane", + "system": "railiance-platform" + } + }, + "memo_version": 1 + }, + "memo:state-hub-edge-receiving-rollout-c287ed8": { + "approval_id": "974eae4d-782d-41ed-93be-e34433b5fe59", + "binding_digest": "sha256:84735b88b2db20d9445af369e28a8d40bd495dd20c0303ff3a764666ec976dde", + "label": "wp0094-m1-state-hub-c287ed8", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "state-hub", + "purpose": "Production rollout of tested source; rollback is helm rollback to revision 72 without schema downgrade.", + "target": { + "id": "state-hub:source:c287ed82594313394ca8e67ce40714c90e744af6", + "system": "state-hub", + "type": "service-release" + } + }, + "memo_version": 1 + }, + "memo:owner-return-pickup-plan-b6222af": { + "approval_id": "77993081-1db2-4bba-99f2-74ca1f8652b1", + "binding_digest": "sha256:e745ef64658e77e41b2a5ef569ec0f6986e120d338656d75749425f5f29b7ca0", + "label": "wp0094-m2-owner-return-b6222af", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "activity-core", + "purpose": "Plan approval plus a gated single-repo trial; the definition stays disabled and wakeups never clear waits.", + "target": { + "id": "activity-core:source:b6222af2cfd1094360a0d430e3907e8788a3acf9:owner-return-pickup", + "system": "activity-core", + "type": "activity-plan" + } + }, + "memo_version": 1 + }, + "memo:freshness-runner-deploy-trial-e77f8b4": { + "approval_id": "4ae04c8e-d604-4d8b-b166-693eef855da8", + "binding_digest": "sha256:f8ff89dd353c1d2006622c45996c0f05f050865f8c203373dc74da1ba3f05e24", + "label": "wp0094-m3-freshness-runner-e77f8b4", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "repo-manager", + "purpose": "Deployment and limited data-writing trials; no schedule.", + "target": { + "id": "repo-manager:source:e77f8b457647ff23697e45a74615edde5f42d39d", + "system": "repo-manager", + "type": "service-release" + } + }, + "memo_version": 1 + }, + "memo:freshness-schedule-enable-retire-sweep": { + "approval_id": "b75752d1-bab6-4cf3-9308-5368c1d5aa91", + "binding_digest": "sha256:cc3e8fe6ace5438f19d647d3de226407c8b009985c82ad9a993e81226e6f316f", + "label": "wp0094-m4-freshness-schedule", + "binding": { + "action": "accept", + "actor": "informed-decision", + "principal": "activity-core", + "purpose": "Recurring schedule activation after clean trials, and permanent retirement of the legacy sweep.", + "target": { + "id": "activity-core:definition:repo-manager-bounded-freshness:b2c6c5613218dea9dbaef303aff22a8d1b07f074", + "system": "activity-core", + "type": "activity-schedule" + } + }, + "memo_version": 1 + } +} diff --git a/examples/informed-decision-lifecycle-review/registry.json b/examples/informed-decision-lifecycle-review/registry.json new file mode 100644 index 0000000..0536a09 --- /dev/null +++ b/examples/informed-decision-lifecycle-review/registry.json @@ -0,0 +1,17 @@ +{ + "subjects": [ + { + "id": "sitting-reviewer", + "type": "Human", + "display_name": "Compact sitting reviewer", + "organization_relation": "ServiceProvider", + "groups": ["net-kingdom-admins"], + "claims": { + "assurance": "aal2", + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied" + } + } + ], + "resources": [] +} diff --git a/examples/informed-decision-lifecycle/fixtures.json b/examples/informed-decision-lifecycle/fixtures.json new file mode 100644 index 0000000..3343918 --- /dev/null +++ b/examples/informed-decision-lifecycle/fixtures.json @@ -0,0 +1,21 @@ +[ + { + "id": "unknown-request-denied", + "request": { + "id": "unknown", + "subject": { + "id": "unknown", + "type": "human" + }, + "action": "lifecycle.register", + "resource": { + "id": "memo:unknown", + "type": "decision-memo", + "system": "informed-decision" + } + }, + "expect": { + "effect": "deny" + } + } +] diff --git a/examples/informed-decision-lifecycle/pin.json b/examples/informed-decision-lifecycle/pin.json new file mode 100644 index 0000000..08ae27b --- /dev/null +++ b/examples/informed-decision-lifecycle/pin.json @@ -0,0 +1,5 @@ +{ + "package": "informed-decision.lifecycle", + "version": "v1", + "package_digest": "sha256:cee6a79f74e81d0d389a2c2e2451a92be3434ea9dfcee4986e8c7e6cfdbb1827" +} diff --git a/examples/informed-decision-lifecycle/policy.md b/examples/informed-decision-lifecycle/policy.md new file mode 100644 index 0000000..b9dbfb7 --- /dev/null +++ b/examples/informed-decision-lifecycle/policy.md @@ -0,0 +1,186 @@ +--- +id: informed-decision.lifecycle +name: Decision lifecycle authoring and collaboration +namespace: informed-decision:decision-memo +version: v1 +status: ready +package: flexauth.informed_decision.lifecycle +allow_ttl: 30s +actions: [lifecycle.register, lifecycle.read, lifecycle.search, lifecycle.revise, lifecycle.source, lifecycle.evaluation, lifecycle.comment, lifecycle.events, lifecycle.packet, lifecycle.publish, lifecycle.request_review, lifecycle.outcomes] +owner: flex-auth +fixtures: [fixtures.json] +caring: + profile: caring-0.4.0-rc2 + enforce: false +activation: + mode: local +--- + +# Initial lifecycle audience + +Founder direction, 2026-10-04, INFD-WP-0007: net-kingdom-admins humans and +system:serviceaccount:state-hub:state-hub. This package is evaluated by Access +Engine; the consumer supplies authenticated identity and immutable record facts. +The evaluator's workload caller remains informed-decision/review, audience +flex-auth. The State Hub subject is authenticated separately for audience +informed-decision-lifecycle using CA-verified TokenReview, never a browser token. + +Humans require KeyCape AAL2 MFA within 12 hours for authoring. Actual human binding +retains the separate review policy's 900-second MFA requirement. This package +has no accept action. An initial caller can register and search; record access +requires original authorship or an explicit publication audience. Published +collaborators may research, discuss and refine. Publication and requesting review +require original authorship or an admitted human collaborator. Publication may +name only the initial human group; explicit subjects still require admitted +identity. Review routing is fixed to State Hub, decision-record class and +net-kingdom-admins. Every operation is fresh; neither content nor evaluation +assertions confer permission. Command digests correlate commands, not signatures. +No presentation/view hash enters this policy. + +The sibling registry declares identity contract profiles, following the existing +review packages. These are not real human or workload identifiers and mint no +identity. Authentication-derived values still come from the enforced consumer +boundary (KeyCape sessions or CA-verified TokenReview). This remains a consumer +claim trust boundary; a registry declaration is not independent verification. + +```rego +import rego.v1 + +common if { + input.tenant == "tenant:platform" + input.subject.tenant == input.tenant + input.resource.tenant == input.tenant + input.resource.system == "informed-decision" + input.context.contract == "informed-decision.lifecycle.v1" + regex.match("^sha256:[0-9a-f]{64}$", input.context.command_digest) + input.subject.attributes.principal_type_source == "authentication-derived" + input.subject.attributes.tenant_source in {"registration-supplied", "directory-asserted"} +} + +human if { + input.subject.type == "human" + "net-kingdom-admins" in input.subject.attributes.groups + assurance := input.subject.attributes.assurance + assurance.level == "aal2" + assurance.mfa == true + assurance.source == "key-cape" + assurance.methods == ["pwd", "otp"] + is_number(assurance.at) + age := time.now_ns()/1000000000 - assurance.at + age >= -30 + age <= 43200 +} + +workload if { + input.subject.type == "service" + input.subject.id == "system:serviceaccount:state-hub:state-hub" + input.subject.attributes.authentication_audience == "informed-decision-lifecycle" + is_string(input.subject.attributes.workload_uid) + input.subject.attributes.workload_uid != "" +} + +identity if human +identity if workload + +memo_resource if { + input.resource.type == "decision-memo" + regex.match("^memo:[A-Za-z0-9._:-]{1,256}$", input.resource.id) +} + +owner if { + input.context.record.registered == true + some author in [input.context.record.registered_by, input.context.record.revision_by] + author.sub == input.subject.id + author.kind == "person" + human +} +owner if { + input.context.record.registered == true + some author in [input.context.record.registered_by, input.context.record.revision_by] + author.sub == input.subject.id + author.kind == "service" + workload +} + +readable if owner +readable if { + input.context.record.registered == true + input.subject.id in input.context.record.published_audience.subjects +} +readable if { + human + "net-kingdom-admins" in input.context.record.published_audience.groups +} + +may_publish if owner +may_publish if { + human + readable +} + +audience_ok if { + a := input.context.publication_audience + is_array(a.subjects) + is_array(a.groups) + count(a.subjects) <= 32 + count(a.groups) <= 32 + count(a.subjects)+count(a.groups) > 0 + every group in a.groups { group == "net-kingdom-admins" } + every subject in a.subjects { is_string(subject); subject != ""; subject != "*" } +} + +operation if { + input.action == "lifecycle.register" + memo_resource + not input.context.record.registered + input.context.memo_version == 1 +} +operation if { + input.action == "lifecycle.register" + memo_resource + owner + input.context.memo_version == 1 +} +operation if { + input.action == "lifecycle.search" + input.resource.type == "decision-collection" + input.resource.id == "decisions" +} +operation if { + input.action in {"lifecycle.read", "lifecycle.events", "lifecycle.packet", "lifecycle.outcomes", + "lifecycle.revise", "lifecycle.source", "lifecycle.evaluation", "lifecycle.comment"} + memo_resource + readable +} +operation if { + input.action == "lifecycle.publish" + memo_resource + may_publish + audience_ok +} +operation if { + input.action == "lifecycle.request_review" + memo_resource + may_publish + routing := input.context.review_routing + routing.submitter == "state-hub" + routing.memo_class == "decision-record" + routing.review_group == "net-kingdom-admins" +} + +decision := {"effect":"allow", "reason":"lifecycle_collaboration"} if { + common + identity + operation +} else := {"effect":"deny", "reason":"lifecycle_scope_or_identity_refused"} +``` + +```rego test +package flexauth.informed_decision.lifecycle_test +import rego.v1 +import data.flexauth.informed_decision.lifecycle + +test_empty_denied if { + lifecycle.decision.effect == "deny" with input as {} +} +``` diff --git a/examples/informed-decision-lifecycle/registry.json b/examples/informed-decision-lifecycle/registry.json new file mode 100644 index 0000000..173a288 --- /dev/null +++ b/examples/informed-decision-lifecycle/registry.json @@ -0,0 +1,29 @@ +{ + "subjects": [ + { + "id": "lifecycle-human-profile", + "type": "Human", + "display_name": "Human identity contract profile", + "groups": [ + "net-kingdom-admins" + ], + "claims": { + "assurance": "aal2", + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied" + } + }, + { + "id": "lifecycle-workload-profile", + "type": "Service", + "display_name": "TokenReview identity contract profile", + "claims": { + "principal_type_source": "authentication-derived", + "tenant_source": "registration-supplied", + "authentication_audience": "informed-decision-lifecycle", + "workload_uid": "TokenReview response UID" + } + } + ], + "resources": [] +} diff --git a/go.mod b/go.mod index f472555..bf11465 100644 --- a/go.mod +++ b/go.mod @@ -1,41 +1,41 @@ module github.com/netkingdom/flex-auth -go 1.22 +go 1.26.7 require ( - github.com/open-policy-agent/opa v0.70.0 + github.com/open-policy-agent/opa v1.4.0 gopkg.in/yaml.v3 v3.0.1 ) require ( - github.com/OneOfOne/xxhash v1.2.8 // indirect - github.com/agnivade/levenshtein v1.2.0 // indirect + github.com/agnivade/levenshtein v1.2.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/go-ini/ini v1.67.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.5 // indirect + github.com/prometheus/client_golang v1.21.1 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect - github.com/tchap/go-patricia/v2 v2.3.1 // indirect + github.com/tchap/go-patricia/v2 v2.3.2 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/yashtewari/glob-intersection v0.2.0 // indirect - go.opentelemetry.io/otel v1.28.0 // indirect - go.opentelemetry.io/otel/metric v1.28.0 // indirect - go.opentelemetry.io/otel/sdk v1.28.0 // indirect - go.opentelemetry.io/otel/trace v1.28.0 // indirect - golang.org/x/sys v0.26.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/sdk v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + golang.org/x/sys v0.42.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index e6512e1..e40e7e2 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8= -github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= -github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY= -github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= +github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= +github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -10,22 +8,20 @@ github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HV github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= -github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y= +github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA= +github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= +github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= -github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= @@ -35,35 +31,25 @@ github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= -github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= +github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -72,78 +58,82 @@ github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/open-policy-agent/opa v0.70.0 h1:B3cqCN2iQAyKxK6+GI+N40uqkin+wzIrM7YA60t9x1U= -github.com/open-policy-agent/opa v0.70.0/go.mod h1:Y/nm5NY0BX0BqjBriKUiV81sCl8XOjjvqQG7dXrggtI= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/open-policy-agent/opa v1.4.0 h1:IGO3xt5HhQKQq2axfa9memIFx5lCyaBlG+fXcgHpd3A= +github.com/open-policy-agent/opa v1.4.0/go.mod h1:DNzZPKqKh4U0n0ANxcCVlw8lCSv2c+h5G/3QvSYdWZ8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ= github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= -github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tchap/go-patricia/v2 v2.3.2 h1:xTHFutuitO2zqKAQ5rCROYgUb7Or/+IC3fts9/Yc7nM= +github.com/tchap/go-patricia/v2 v2.3.2/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/yashtewari/glob-intersection v0.2.0 h1:8iuHdN88yYuCzCdjt0gDe+6bAhUwBeEWqThExu54RFg= github.com/yashtewari/glob-intersection v0.2.0/go.mod h1:LK7pIC3piUjovexikBbJ26Yml7g8xa5bsjfx2v1fwok= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= -go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= -go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8= -google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= +google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/policy/informed_decision_lifecycle_test.go b/internal/policy/informed_decision_lifecycle_test.go new file mode 100644 index 0000000..e436e61 --- /dev/null +++ b/internal/policy/informed_decision_lifecycle_test.go @@ -0,0 +1,137 @@ +package policy_test + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + "github.com/netkingdom/flex-auth/internal/policy" + "github.com/netkingdom/flex-auth/pkg/api" +) + +func TestInformedDecisionLifecycle(t *testing.T) { + pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "informed-decision-lifecycle", "policy.md")) + if err != nil { + t.Fatal(err) + } + if !pkg.Valid { + t.Fatalf("invalid package: %+v", pkg.Validation) + } + base := map[string]any{ + "id": "fixture", "tenant": "tenant:platform", "policy_version": "v1", + "subject": map[string]any{"id": "human-one", "type": "human", "tenant": "tenant:platform", "attributes": map[string]any{ + "principal_type_source": "authentication-derived", "tenant_source": "registration-supplied", "groups": []any{"net-kingdom-admins"}, + "assurance": map[string]any{"level": "aal2", "mfa": true, "source": "key-cape", "methods": []any{"pwd", "otp"}, "at": time.Now().Unix()}, + }}, + "resource": map[string]any{"id": "memo:new-record", "type": "decision-memo", "system": "informed-decision", "tenant": "tenant:platform"}, + "action": "lifecycle.register", + "context": map[string]any{"contract": "informed-decision.lifecycle.v1", "memo_version": 1, "command_digest": "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "record": map[string]any{"registered": false}}, + } + human := func(d map[string]any) map[string]any { return d["subject"].(map[string]any) } + ctx := func(d map[string]any) map[string]any { return d["context"].(map[string]any) } + attrs := func(d map[string]any) map[string]any { return human(d)["attributes"].(map[string]any) } + owned := func(d map[string]any) { + ctx(d)["record"] = map[string]any{"registered": true, "registered_by": map[string]any{"sub": "human-one", "kind": "person"}, "revision_by": nil, "published_audience": nil, "current_version": 1} + d["action"] = "lifecycle.read" + } + hub := func(d map[string]any) { + human(d)["id"] = "system:serviceaccount:state-hub:state-hub" + human(d)["type"] = "service" + human(d)["attributes"] = map[string]any{"principal_type_source": "authentication-derived", "tenant_source": "registration-supplied", "workload_uid": "verified-uid", "authentication_audience": "informed-decision-lifecycle"} + } + tests := []struct { + name string + effect string + change func(map[string]any) + }{ + {"human registration", "allow", func(d map[string]any) {}}, + {"workload registration", "allow", hub}, + {"foreign workload", "deny", func(d map[string]any) { hub(d); human(d)["id"] = "system:serviceaccount:foreign:foreign" }}, + {"wrong workload audience", "deny", func(d map[string]any) { hub(d); attrs(d)["authentication_audience"] = "informed-decision-intake" }}, + {"missing workload UID", "deny", func(d map[string]any) { hub(d); delete(attrs(d), "workload_uid") }}, + {"wrong tenant", "deny", func(d map[string]any) { d["tenant"] = "tenant:foreign" }}, + {"outside human group", "deny", func(d map[string]any) { attrs(d)["groups"] = []any{"other"} }}, + {"stale author MFA", "deny", func(d map[string]any) { attrs(d)["assurance"].(map[string]any)["at"] = time.Now().Unix() - 43201 }}, + {"future author MFA", "deny", func(d map[string]any) { attrs(d)["assurance"].(map[string]any)["at"] = time.Now().Unix() + 301 }}, + {"asserted identity", "deny", func(d map[string]any) { attrs(d)["principal_type_source"] = "asserted" }}, + {"owner read", "allow", owned}, + {"foreign unpublished read", "deny", func(d map[string]any) { owned(d); human(d)["id"] = "another-admin" }}, + {"published collaborator", "allow", func(d map[string]any) { + owned(d) + human(d)["id"] = "another-admin" + ctx(d)["record"].(map[string]any)["published_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"net-kingdom-admins"}} + }}, + {"new revision editor", "allow", func(d map[string]any) { + owned(d) + human(d)["id"] = "editor" + ctx(d)["record"].(map[string]any)["revision_by"] = map[string]any{"sub": "editor", "kind": "person"} + }}, + {"unknown action", "deny", func(d map[string]any) { owned(d); d["action"] = "lifecycle.delete" }}, + {"agent acceptance", "deny", func(d map[string]any) { hub(d); d["action"] = "accept" }}, + {"human acceptance excluded", "deny", func(d map[string]any) { owned(d); d["action"] = "lifecycle.accept" }}, + {"publish initial group", "allow", func(d map[string]any) { + owned(d) + d["action"] = "lifecycle.publish" + ctx(d)["publication_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"net-kingdom-admins"}} + }}, + {"publish other group", "deny", func(d map[string]any) { + owned(d) + d["action"] = "lifecycle.publish" + ctx(d)["publication_audience"] = map[string]any{"subjects": []any{}, "groups": []any{"other"}} + }}, + {"review routing", "allow", func(d map[string]any) { + owned(d) + d["action"] = "lifecycle.request_review" + ctx(d)["review_routing"] = map[string]any{"submitter": "state-hub", "memo_class": "decision-record", "review_group": "net-kingdom-admins"} + }}, + {"wrong review routing", "deny", func(d map[string]any) { + owned(d) + d["action"] = "lifecycle.request_review" + ctx(d)["review_routing"] = map[string]any{"submitter": "unknown", "memo_class": "decision-record", "review_group": "net-kingdom-admins"} + }}, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + encoded, _ := json.Marshal(base) + var d map[string]any + json.Unmarshal(encoded, &d) + tc.change(d) + encoded, _ = json.Marshal(d) + var request api.CheckRequest + if err := json.Unmarshal(encoded, &request); err != nil { + t.Fatal(err) + } + got, err := pkg.Evaluate(context.Background(), request) + if err != nil { + t.Fatal(err) + } + if string(got.Effect) != tc.effect { + t.Fatalf("got %+v, want %s", got, tc.effect) + } + }) + } +} + +func TestInformedDecisionLifecycleReleasePins(t *testing.T) { + for _, name := range []string{"informed-decision-lifecycle", "informed-decision-lifecycle-review"} { + dir := filepath.Join(repoRoot(t), "examples", name) + pkg, err := policy.LoadAndValidateFile(context.Background(), filepath.Join(dir, "policy.md")) + if err != nil || !pkg.Valid { + t.Fatalf("%s invalid: %v %+v", name, err, pkg) + } + raw, err := os.ReadFile(filepath.Join(dir, "pin.json")) + if err != nil { + t.Fatal(err) + } + var pin map[string]string + if err := json.Unmarshal(raw, &pin); err != nil { + t.Fatal(err) + } + if pin["package_digest"] != pkg.Digest() || pin["version"] != pkg.Metadata.Version || pin["package"] != pkg.Metadata.ID { + t.Fatalf("%s stale pin: %+v", name, pin) + } + } +} diff --git a/values/informed-decision-lifecycle-review.candidate.yaml b/values/informed-decision-lifecycle-review.candidate.yaml new file mode 100644 index 0000000..b31eaae --- /dev/null +++ b/values/informed-decision-lifecycle-review.candidate.yaml @@ -0,0 +1,28 @@ +# INFD-WP-0007 / FLEX-WP-0033 review proposal; no live grant. +name: flex-auth-informed-decision-sitting +image: + repository: forgejo.coulomb.social/coulomb/flex-auth + digest: sha256:f6ed8af69af766d7e9498465a23c292d91185b79352065736a2132216aac1d37 +args: +- serve +- --addr +- 0.0.0.0:8080 +- --registry +- /opt/flex-auth/examples/informed-decision-lifecycle-review/registry.json +- --policy +- /opt/flex-auth/examples/informed-decision-lifecycle-review/policy.md +callerAuth: + mode: enforce + kubernetesURL: https://10.43.0.1 + binding: informed-decision=system:serviceaccount:informed-decision:review +consumer: + isolated: false + namespace: informed-decision + podName: informed-decision +resources: + requests: + cpu: 5m + memory: 32Mi + limits: + cpu: 300m + memory: 192Mi diff --git a/values/informed-decision-lifecycle.candidate.yaml b/values/informed-decision-lifecycle.candidate.yaml new file mode 100644 index 0000000..f348866 --- /dev/null +++ b/values/informed-decision-lifecycle.candidate.yaml @@ -0,0 +1,28 @@ +# INFD-WP-0007 / FLEX-WP-0033 review proposal; no live grant. +name: flex-auth-informed-decision-lifecycle +image: + repository: forgejo.coulomb.social/coulomb/flex-auth + digest: sha256:f6ed8af69af766d7e9498465a23c292d91185b79352065736a2132216aac1d37 +args: +- serve +- --addr +- 0.0.0.0:8080 +- --registry +- /opt/flex-auth/examples/informed-decision-lifecycle/registry.json +- --policy +- /opt/flex-auth/examples/informed-decision-lifecycle/policy.md +callerAuth: + mode: enforce + kubernetesURL: https://10.43.0.1 + binding: informed-decision=system:serviceaccount:informed-decision:review +consumer: + isolated: false + namespace: informed-decision + podName: informed-decision +resources: + requests: + cpu: 5m + memory: 32Mi + limits: + cpu: 300m + memory: 192Mi diff --git a/workplans/FLEX-WP-0033-informed-decision-lifecycle.md b/workplans/FLEX-WP-0033-informed-decision-lifecycle.md new file mode 100644 index 0000000..992e7a0 --- /dev/null +++ b/workplans/FLEX-WP-0033-informed-decision-lifecycle.md @@ -0,0 +1,97 @@ +--- +id: FLEX-WP-0033 +type: workplan +title: "Initial Informed Decision lifecycle policy and production candidate" +domain: infotech +repo: access-engine +status: active +owner: codex +topic_slug: netkingdom +created: "2026-10-04" +updated: "2026-10-04" +related: [INFD-WP-0007, INFD-WP-0005, FLEX-WP-0028] +--- + +Founder selected net-kingdom-admins humans and the State Hub workload for the +first release. Source work is on infd-wp0007-lifecycle; no live grants applied. + +## Define and prove the initial lifecycle authorization contract + +```task +id: FLEX-WP-0033-T01 +status: done +priority: high +``` + +Separate informed-decision.lifecycle v1 package admits twelve authoring/retrieval +operations. It requires platform tenant, authenticated identity provenance, +KeyCape AAL2 for humans or the exact State Hub service account and lifecycle +audience for workloads. Immutable owner/editor/publication facts scope access. +No accept action. Contract profiles declare fact keys; they do not authenticate +real identities. Consumer attribution remains unsigned (FLEX-WP-0024). + +21 Go cases and actual HTTP evaluator integration prove authoring, publication, +human refinement, successor draft access, restart and rejection of other callers. + +## Prepare human review admission without per-decision policy rollout + +```task +id: FLEX-WP-0033-T02 +status: done +priority: high +``` + +examples/informed-decision-lifecycle-review is the v12 candidate of compact-sitting. +It preserves all v11 exact mandates and adds generated state-hub / decision-record +sets from the candidate intake registry. The deployed v11 directory/values remain +unchanged rollback references. New records are admitted by verified attributes, +not ids. Consumer checks the live Approval Engine requester/human-control and pin. +28 embedded tests and four fixtures pass; policy pins are checked by Go tests. + +## Produce a scanned immutable policy image and deployment proposal + +```task +id: FLEX-WP-0033-T03 +status: done +priority: high +``` + +The inherited production binary failed its HIGH/CRITICAL scan (25 findings in Go, +OPA and telemetry). Fix dependency/build pins, run full engine regression, rebuild, +scan and retain immutable image/package pins. Render the separate lifecycle PDP +and v12 review proposal. CARING remains advisory as in deployed review packages; +no new claim of signed attribution or complete production close-down. + +## Approved policy deployment and native verification + +```task +id: FLEX-WP-0033-T04 +status: todo +priority: high +``` + +Part of the concrete INFD-WP-0007 rollout approval. Verify actual consumer caller +TokenReview and positive/negative lifecycle/human review behavior. No agent binds. +Retain v11 rollback while preserving decisions and audit evidence. Do not mark +finished while live acceptance or security findings remain open. + + +## Published candidate evidence — 2026-10-04 + +Patched Go 1.26.7, OPA 1.4.0 and telemetry SDK 1.43.0; pinned builder/runtime +images and aligned CI setup with go.mod. Full engine tests, race-enabled tests and vet pass. Both +container-loaded policy packages validate; review has 28 tests/four fixtures. +Actual consumer-to-PDP tests pass after rebuilding this binary. + +Published image: +forgejo.coulomb.social/coulomb/flex-auth@sha256:f6ed8af69af766d7e9498465a23c292d91185b79352065736a2132216aac1d37 + +HIGH/CRITICAL scan: zero findings, retained under +docs/evidence/2026-10-04-infd-lifecycle/. Candidate Helm values passed server +dry-run for both PDPs (12 objects); no cluster state changed. Full review packet +and migration/recovery procedure are in informed-decision deploy/lifecycle. + +The existing chart's port-only API egress, advisory CARING dimensions and unsigned +consumer assertions are explicitly retained limitations, not newly enforced +controls. T04 remains open; requester projection and State Hub production +forwarding also remain prerequisites in their owner workplans.