diff --git a/examples/informed-decision-sitting/attribute_admission_v8.json b/examples/informed-decision-sitting/attribute_admission_v8.json index 15cd095..12b24d8 100644 --- a/examples/informed-decision-sitting/attribute_admission_v8.json +++ b/examples/informed-decision-sitting/attribute_admission_v8.json @@ -1,6 +1,6 @@ { "policy_id": "informed-decision.compact-sitting", - "policy_version": "v9", + "policy_version": "v10", "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": [ { diff --git a/examples/informed-decision-sitting/policy.md b/examples/informed-decision-sitting/policy.md index c67d8c5..fe19c8e 100644 --- a/examples/informed-decision-sitting/policy.md +++ b/examples/informed-decision-sitting/policy.md @@ -2,7 +2,7 @@ id: informed-decision.compact-sitting name: Compact sitting exact-record human review namespace: informed-decision:decision-memo -version: v9 +version: v10 status: ready package: flexauth.informed_decision.compact_sitting allow_ttl: 30s @@ -88,6 +88,17 @@ v9 (2026-10-04, EMERGENT-WP-0002 profile adoption) adds exactly one record, 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. + `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 @@ -106,6 +117,22 @@ approval. Native pins: `docs/evidence/2026-09-15-sitting-approval-creates.json`. 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" diff --git a/examples/informed-decision-sitting/records.json b/examples/informed-decision-sitting/records.json index 3963b5f..f50b1c4 100644 --- a/examples/informed-decision-sitting/records.json +++ b/examples/informed-decision-sitting/records.json @@ -372,5 +372,73 @@ } }, "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/internal/policy/informed_decision_attribute_test.go b/internal/policy/informed_decision_attribute_test.go index b1bcaa7..5feee4e 100644 --- a/internal/policy/informed_decision_attribute_test.go +++ b/internal/policy/informed_decision_attribute_test.go @@ -23,8 +23,8 @@ func TestInformedDecisionAttributeAdmission(t *testing.T) { if !pkg.Valid { t.Fatalf("package invalid: %+v", pkg.Validation) } - if pkg.Metadata.Version != "v9" { - t.Fatalf("version = %s; want v9", pkg.Metadata.Version) + if pkg.Metadata.Version != "v10" { + t.Fatalf("version = %s; want v10", pkg.Metadata.Version) } if strings.Contains(pkg.RegoModule, "view_hash") { t.Fatal("rule module reads view_hash") @@ -50,7 +50,7 @@ func TestInformedDecisionAttributeAdmission(t *testing.T) { if err := json.Unmarshal(raw, &doc); err != nil { t.Fatal(err) } - if doc.PolicyVersion != "v9" { + if doc.PolicyVersion != "v10" { t.Fatalf("fixture policy_version = %s", doc.PolicyVersion) } exactKeys := map[string]bool{"memo_version": true, "approval_id": true, "approval_binding_digest": true} diff --git a/internal/policy/informed_decision_wp0094_test.go b/internal/policy/informed_decision_wp0094_test.go new file mode 100644 index 0000000..3def27c --- /dev/null +++ b/internal/policy/informed_decision_wp0094_test.go @@ -0,0 +1,113 @@ +package policy_test + +import ( + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/netkingdom/flex-auth/internal/policy" + "github.com/netkingdom/flex-auth/pkg/api" +) + +// v10 admits exactly the four STATE-WP-0094 sitting memos. Pins come from +// informed-decision docs/evidence/2026-10-04-state-hub-wp0094-approval-create.json. +var wp0094Records = map[string][2]string{ + "memo:state-hub-edge-receiving-rollout-c287ed8": {"974eae4d-782d-41ed-93be-e34433b5fe59", "sha256:84735b88b2db20d9445af369e28a8d40bd495dd20c0303ff3a764666ec976dde"}, + "memo:owner-return-pickup-plan-b6222af": {"77993081-1db2-4bba-99f2-74ca1f8652b1", "sha256:e745ef64658e77e41b2a5ef569ec0f6986e120d338656d75749425f5f29b7ca0"}, + "memo:freshness-runner-deploy-trial-e77f8b4": {"4ae04c8e-d604-4d8b-b166-693eef855da8", "sha256:f8ff89dd353c1d2006622c45996c0f05f050865f8c203373dc74da1ba3f05e24"}, + "memo:freshness-schedule-enable-retire-sweep": {"b75752d1-bab6-4cf3-9308-5368c1d5aa91", "sha256:cc3e8fe6ace5438f19d647d3de226407c8b009985c82ad9a993e81226e6f316f"}, +} + +func TestWP0094ExactRecords(t *testing.T) { + pkg, err := policy.LoadAndValidateFile(context.Background(), sittingPolicyPath(t)) + if err != nil || !pkg.Valid { + t.Fatalf("load: %v %+v", err, pkg) + } + var doc struct { + Cases []struct { + ID string `json:"id"` + Assurance string `json:"assurance"` + Request api.CheckRequest `json:"request"` + } `json:"cases"` + } + raw, err := os.ReadFile(filepath.Join(sittingDir(t), "attribute_admission_v8.json")) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(raw, &doc); err != nil { + t.Fatal(err) + } + templates := map[string]api.CheckRequest{} + for _, c := range doc.Cases { + req := c.Request + stampAssurance(t, &req, c.Assurance) + templates[c.ID] = req + } + exact, ok := templates["exact-record-allow"] + if !ok { + t.Fatal("exact-record-allow template missing") + } + attr, ok := templates["attribute-unknown-class"] + if !ok { + t.Fatal("attribute-unknown-class template missing") + } + + var shipped map[string]struct { + ApprovalID string `json:"approval_id"` + BindingDigest string `json:"binding_digest"` + } + if err := json.Unmarshal(mustRead(t, filepath.Join(sittingDir(t), "records.json")), &shipped); err != nil { + t.Fatal(err) + } + + eval := func(name string, req api.CheckRequest, effect, reason string) { + t.Helper() + got, err := pkg.Evaluate(context.Background(), req) + if err != nil { + t.Fatalf("%s: %v", name, err) + } + if string(got.Effect) != effect || got.Reason != reason { + t.Fatalf("%s: got %s %s; want %s %s", name, got.Effect, got.Reason, effect, reason) + } + } + + for memo, pin := range wp0094Records { + if r := shipped[memo]; r.ApprovalID != pin[0] || r.BindingDigest != pin[1] { + t.Fatalf("records.json pin for %s = %+v", memo, r) + } + req := exact + req.Resource.ID = memo + req.Context = copyContext(exact.Context) + req.Context["approval_id"] = pin[0] + req.Context["approval_binding_digest"] = pin[1] + eval(memo+" exact", req, "allow", "operator_admitted_compact_sitting") + + wrong := req + wrong.Context = copyContext(req.Context) + wrong.Context["approval_binding_digest"] = "sha256:0000000000000000000000000000000000000000000000000000000000000000" + eval(memo+" wrong digest", wrong, "deny", "compact_sitting_scope_or_identity_refused") + + swapped := req + swapped.Context = copyContext(req.Context) + swapped.Context["approval_id"] = "05751e09-3a5b-4a41-a4e8-b79e96de6484" + eval(memo+" foreign approval id", swapped, "deny", "compact_sitting_scope_or_identity_refused") + } + + // v9's record stays admitted. + v9 := exact + v9.Resource.ID = "memo:emergent-profile-adoption-0-2-6" + v9.Context = copyContext(exact.Context) + v9.Context["approval_id"] = "05751e09-3a5b-4a41-a4e8-b79e96de6484" + v9.Context["approval_binding_digest"] = "sha256:7843e660694dcd7c806dc02ee0cca05dd3a96488f614b1946291496259a02001" + eval("v9 record", v9, "allow", "operator_admitted_compact_sitting") + + // An unlisted sibling memo in attribute shape is not admitted. + unlisted := attr + unlisted.Resource.ID = "memo:freshness-schedule-enable-retire-sweep-v2" + unlisted.Context = copyContext(attr.Context) + unlisted.Context["approval_id"] = "b75752d1-bab6-4cf3-9308-5368c1d5aa91" + unlisted.Context["approval_binding_digest"] = "sha256:cc3e8fe6ace5438f19d647d3de226407c8b009985c82ad9a993e81226e6f316f" + eval("unlisted memo", unlisted, "deny", "unknown_class") +}