diff --git a/adapters/go/approvalfact/README.md b/adapters/go/approvalfact/README.md new file mode 100644 index 0000000..fdffd1f --- /dev/null +++ b/adapters/go/approvalfact/README.md @@ -0,0 +1,55 @@ +# Approval fact data adapter + +Provider-owned pure Go module for Access's Approval B2 source reader. No network, +credentials, subprocess, policy evaluator, server or consume mutation exists here. +Python `approval_engine.binding` remains the authoritative native format. + +API: + +- `CanonicalBinding(rawJSON)` returns native five-field canonical UTF-8 bytes + and sha256 digest; extra binding metadata is excluded. +- `ValidateClaim(rawClaim, Expected, now)` validates one exact human/PDP-path + claim against independently owner-derived `Expected.NativeBindingJSON`, exact + approval ID and actual Access approval-free PDP digest. +- `MatchObserved(submitted, observed)` requires two validated facts at the same + captured instant and identical nonfreshness authority facts. No request is + rewritten. `Fact.RawJSON()` returns a detached original byte copy; deadlines + preserve each input's own freshness and approval expiry. + +`Fact` means validated data, never authenticated provider origin. Access must +fetch provider bytes through its independently admitted fixed origin/identity, +then wrap this data in its own unforgeable source handle. A caller-supplied claim +cannot become source truth merely by passing this helper. Access remains the +single evaluator and must bound decision/operation/caller lifetimes separately. +PEP consume CAS and live one-shot journal remain required before any effect. + +Expected native act bytes must preserve the actual owner/compiler/normalization +representation. Marshaling float64-decoded CheckRequest maps loses 1.0/1 and +large integers. Raw submitted resource is sufficient only when Access proves +the active registry cannot override that target and applies its exact existing +normalization. Otherwise authoritative raw enrichment/compiler evidence is +needed. This helper supplies no fallback or invented restricted numeric domain. + +The strict parser retains integer/floating lexical types, arbitrary integer +magnitudes, finite IEEE-754 float semantics and valid Unicode scalar values. +Canonical float/string formatting follows Python source, including negative +floating zero, exponent switching/padding, HTML and U+2028/U+2029. Duplicate +object keys, nonfinite/overflow numbers, invalid UTF-8 and lone surrogates refuse. +Inputs are bounded to 1 MiB and 128 container levels. Timestamp precision is at +most nanoseconds with strict zoned RFC3339; current producer emits whole seconds. +These explicit parser/resource limits fail closed, never normalize authority. + +`go test -race ./...` checks the portable producer vectors, typed fact pairing, +strict JSON/Unicode and exact deadline semantics. From the owner repo root, +`python3 scripts/check_go_approvalfact.py` runs a deterministic 14,114-case +development-only differential corpus against the actual Python producer, +including random finite IEEE bit patterns, exponent boundaries/subnormals and +Unicode. It uses a test-only local oracle file; production imports no oracle or +Python runtime. Sample conformance is evidence, not a formal proof over all JSON. + +The module pins Go 1.26.7 to match the existing Access consumer toolchain. +`testdata/owner-vectors.json` is an unchanged portable copy of the Approval owner +fixture, SHA256 `dfb0a8810cd91847d7af6a7af6a51607a67a66fad7b86fc57365308c36bd9323`. +Transport cases requiring HTTP remain the Access adapter's tests; this module +checks their duplicate/nonfinite bodies only. Import/deployment/native identity +admission and positive policy gates require separate owner evidence. diff --git a/adapters/go/approvalfact/canonical.go b/adapters/go/approvalfact/canonical.go new file mode 100644 index 0000000..06f14b5 --- /dev/null +++ b/adapters/go/approvalfact/canonical.go @@ -0,0 +1,295 @@ +// Package approvalfact provides data-only Approval claim correspondence. +// It neither authenticates a provider nor decides permission or consumes objects. +package approvalfact + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "io" + "math" + "math/big" + "sort" + "strconv" + "strings" + "unicode/utf8" +) + +const MaxJSONBytes = 1 << 20 + +var errJSON = errors.New("invalid Approval JSON") + +// DecodeJSON retains numeric lexical types, refuses duplicates and invalid +// Unicode, and bounds work. A Go map previously decoded into float64 cannot +// recover the producer's integer-versus-floating identity; pass source bytes. +func DecodeJSON(raw []byte) (any, error) { + if len(raw) > MaxJSONBytes || !utf8.Valid(raw) || !validSurrogates(raw) { + return nil, errJSON + } + d := json.NewDecoder(bytes.NewReader(raw)) + d.UseNumber() + var read func(int) (any, error) + read = func(depth int) (any, error) { + if depth > 128 { + return nil, errJSON + } + t, e := d.Token() + if e != nil { + return nil, errJSON + } + switch x := t.(type) { + case json.Delim: + if x == '{' { + m := map[string]any{} + for d.More() { + k, e := d.Token() + if e != nil { + return nil, errJSON + } + s, ok := k.(string) + if !ok { + return nil, errJSON + } + if _, ok = m[s]; ok { + return nil, errJSON + } + v, e := read(depth + 1) + if e != nil { + return nil, e + } + m[s] = v + } + end, e := d.Token() + if e != nil || end != json.Delim('}') { + return nil, errJSON + } + return m, nil + } + if x == '[' { + a := []any{} + for d.More() { + v, e := read(depth + 1) + if e != nil { + return nil, e + } + a = append(a, v) + } + end, e := d.Token() + if e != nil || end != json.Delim(']') { + return nil, errJSON + } + return a, nil + } + return nil, errJSON + case json.Number: + if _, e := pythonNumber(x); e != nil { + return nil, e + } + return x, nil + case string, bool, nil: + return x, nil + default: + return nil, errJSON + } + } + v, e := read(0) + if e != nil { + return nil, e + } + if _, e = d.Token(); e != io.EOF { + return nil, errJSON + } + return v, nil +} + +// encoding/json replaces lone UTF-16 surrogates, whereas the Python producer +// cannot encode them as UTF-8. Reject before the decoder can erase that fact. +func validSurrogates(raw []byte) bool { + in := false + for i := 0; i < len(raw); i++ { + if raw[i] == '"' { + in = !in + continue + } + if !in || raw[i] != '\\' { + continue + } + i++ + if i >= len(raw) { + return false + } + if raw[i] != 'u' { + continue + } + if i+4 >= len(raw) { + return false + } + n, e := strconv.ParseUint(string(raw[i+1:i+5]), 16, 16) + if e != nil { + return false + } + i += 4 + if n >= 0xdc00 && n <= 0xdfff { + return false + } + if n < 0xd800 || n > 0xdbff { + continue + } + if i+6 >= len(raw) || raw[i+1] != '\\' || raw[i+2] != 'u' { + return false + } + low, e := strconv.ParseUint(string(raw[i+3:i+7]), 16, 16) + if e != nil || low < 0xdc00 || low > 0xdfff { + return false + } + i += 6 + } + return !in +} + +func pythonNumber(n json.Number) (string, error) { + s := string(n) + if !strings.ContainsAny(s, ".eE") { + i, ok := new(big.Int).SetString(s, 10) + if !ok { + return "", errJSON + } + return i.String(), nil + } + f, e := strconv.ParseFloat(s, 64) + if e != nil || math.IsInf(f, 0) || math.IsNaN(f) { + return "", errJSON + } + // Python repr uses shortest round-trip digits, fixed form for exponents + // -4..15, a mandatory .0 for integral floats, and two exponent digits. + sci := strconv.FormatFloat(f, 'e', -1, 64) + at := strings.IndexByte(sci, 'e') + exp, _ := strconv.Atoi(sci[at+1:]) + if exp >= -4 && exp < 16 { + s = strconv.FormatFloat(f, 'f', -1, 64) + if !strings.Contains(s, ".") { + s += ".0" + } + return s, nil + } + sign := "+" + if exp < 0 { + sign = "-" + exp = -exp + } + digits := strconv.Itoa(exp) + if len(digits) < 2 { + digits = "0" + digits + } + return sci[:at] + "e" + sign + digits, nil +} + +func quote(s string) string { + var b strings.Builder + b.WriteByte('"') + for _, r := range s { + switch r { + case '"', '\\': + b.WriteByte('\\') + b.WriteRune(r) + case '\b': + b.WriteString(`\b`) + case '\f': + b.WriteString(`\f`) + case '\n': + b.WriteString(`\n`) + case '\r': + b.WriteString(`\r`) + case '\t': + b.WriteString(`\t`) + default: + if r < 32 { + b.WriteString(`\u00`) + const h = "0123456789abcdef" + b.WriteByte(h[byte(r)>>4]) + b.WriteByte(h[byte(r)&15]) + } else { + b.WriteRune(r) + } + } + } + b.WriteByte('"') + return b.String() +} + +func canonical(v any) (string, error) { + switch x := v.(type) { + case nil: + return "null", nil + case bool: + if x { + return "true", nil + } + return "false", nil + case string: + return quote(x), nil + case json.Number: + return pythonNumber(x) + case []any: + a := make([]string, len(x)) + for i, v := range x { + s, e := canonical(v) + if e != nil { + return "", e + } + a[i] = s + } + return "[" + strings.Join(a, ",") + "]", nil + case map[string]any: + keys := make([]string, 0, len(x)) + for k := range x { + keys = append(keys, k) + } + sort.Strings(keys) + a := make([]string, 0, len(keys)) + for _, k := range keys { + s, e := canonical(x[k]) + if e != nil { + return "", e + } + a = append(a, quote(k)+":"+s) + } + return "{" + strings.Join(a, ",") + "}", nil + default: + return "", errJSON + } +} + +// CanonicalBinding reproduces approval_engine.binding.binding_digest over the +// five native act fields. Extra binding metadata is excluded by that contract. +func CanonicalBinding(raw []byte) ([]byte, string, error) { + v, e := DecodeJSON(raw) + if e != nil { + return nil, "", e + } + m, ok := v.(map[string]any) + if !ok { + return nil, "", errJSON + } + act := map[string]any{} + for _, k := range []string{"action", "actor", "principal", "purpose"} { + s, ok := m[k].(string) + if !ok || s == "" { + return nil, "", errJSON + } + act[k] = s + } + target, ok := m["target"].(map[string]any) + if !ok { + return nil, "", errJSON + } + act["target"] = target + s, e := canonical(act) + if e != nil { + return nil, "", e + } + sum := sha256.Sum256([]byte(s)) + return []byte(s), "sha256:" + hex.EncodeToString(sum[:]), nil +} diff --git a/adapters/go/approvalfact/claim.go b/adapters/go/approvalfact/claim.go new file mode 100644 index 0000000..169643f --- /dev/null +++ b/adapters/go/approvalfact/claim.go @@ -0,0 +1,170 @@ +package approvalfact + +import ( + "encoding/json" + "errors" + "regexp" + "strconv" + "time" +) + +var errClaim = errors.New("Approval fact does not match required claim") +var digestPattern = regexp.MustCompile(`^sha256:[0-9a-f]{64}$`) +var uuidPattern = regexp.MustCompile(`^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`) +var timePattern = regexp.MustCompile(`^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\.[0-9]{1,9})?(Z|[+-]([01][0-9]|2[0-3]):[0-5][0-9])$`) + +// Expected contains independently owner-derived native act bytes and the actual +// Access approval-free digest. Marshaling an already float64-decoded Go request +// is not an authoritative reconstruction of the native act. +type Expected struct { + ApprovalID string + NativeBindingJSON []byte + PDPDigest string +} + +// Fact is validated data shape/correspondence, NOT authenticated provider origin +// or execution authority. Access must wrap it only after its own source admission. +type Fact struct { + raw []byte + stable string + digest string + observed, timeLimit, validatedAt time.Time +} + +func (f Fact) RawJSON() []byte { return append([]byte(nil), f.raw...) } +func (f Fact) ObservedAt() time.Time { return f.observed } +func (f Fact) Deadline() time.Time { return f.timeLimit } +func (f Fact) NativeDigest() string { return f.digest } + +func members(m map[string]any, required, optional []string) bool { + allowed := map[string]bool{} + for _, k := range required { + allowed[k] = true + if _, ok := m[k]; !ok { + return false + } + } + for _, k := range optional { + allowed[k] = true + } + for k := range m { + if !allowed[k] { + return false + } + } + return true +} +func object(v any) (map[string]any, bool) { m, ok := v.(map[string]any); return m, ok } +func stamp(v any) (time.Time, error) { + s, ok := v.(string) + if !ok || !timePattern.MatchString(s) { + return time.Time{}, errClaim + } + t, e := time.Parse(time.RFC3339Nano, s) + if e != nil { + return time.Time{}, errClaim + } + return t, nil +} + +// ValidateClaim checks one original or fetched schema-0.1 human/PDP-path claim +// at a caller-captured instant, using the existing Approval producer semantics. +// It has no I/O, policy evaluator, credentials, cache, or consume mutation. +func ValidateClaim(raw []byte, expected Expected, now time.Time) (Fact, error) { + if now.IsZero() || !uuidPattern.MatchString(expected.ApprovalID) || !digestPattern.MatchString(expected.PDPDigest) { + return Fact{}, errClaim + } + _, want, e := CanonicalBinding(expected.NativeBindingJSON) + if e != nil { + return Fact{}, errClaim + } + v, e := DecodeJSON(raw) + if e != nil { + return Fact{}, errClaim + } + m, ok := object(v) + if !ok || !members(m, []string{"schema_version", "kind", "issuer", "approval_id", "state", "valid_now", "consumed", "binding", "freshness", "validity", "reason_code"}, []string{"yields_to"}) { + return Fact{}, errClaim + } + if y, ok := m["yields_to"]; ok { + if _, ok = y.(string); !ok { + return Fact{}, errClaim + } + } + if m["schema_version"] != "0.1" || m["kind"] != "approval-claim" || m["issuer"] != "approval-engine" || m["approval_id"] != expected.ApprovalID || m["state"] != "valid" || m["valid_now"] != true || m["consumed"] != false || m["reason_code"] != "ok" { + return Fact{}, errClaim + } + b, ok := object(m["binding"]) + if !ok || !members(b, []string{"action", "actor", "principal", "purpose", "target", "digest", "pdp_digest", "pdp_path", "human_control"}, nil) { + return Fact{}, errClaim + } + if b["digest"] != want || b["pdp_digest"] != expected.PDPDigest || b["pdp_path"] != true || b["human_control"] != true { + return Fact{}, errClaim + } + text, e := canonical(b) + if e != nil { + return Fact{}, errClaim + } + _, got, e := CanonicalBinding([]byte(text)) + if e != nil || got != want { + return Fact{}, errClaim + } + f, ok := object(m["freshness"]) + if !ok || !members(f, []string{"observed_at", "ttl_seconds", "not_after"}, nil) { + return Fact{}, errClaim + } + observed, e := stamp(f["observed_at"]) + if e != nil { + return Fact{}, e + } + notAfter, e := stamp(f["not_after"]) + if e != nil { + return Fact{}, e + } + ttl, ok := f["ttl_seconds"].(json.Number) + if !ok { + return Fact{}, errClaim + } + seconds, e := strconv.ParseInt(string(ttl), 10, 64) + if e != nil || seconds < 1 { + return Fact{}, errClaim + } + // Bound arithmetic to time.Duration's representable seconds, not a new + // semantic freshness grant or configured TTL. Unsupported extremes refuse. + if seconds > int64((1<<63-1)/int64(time.Second)) || !observed.Add(time.Duration(seconds)*time.Second).Equal(notAfter) || now.Before(observed) || !now.Before(notAfter) { + return Fact{}, errClaim + } + validity, ok := object(m["validity"]) + if !ok || !members(validity, []string{"not_before", "expires_at"}, nil) { + return Fact{}, errClaim + } + before, e := stamp(validity["not_before"]) + if e != nil { + return Fact{}, e + } + expires, e := stamp(validity["expires_at"]) + if e != nil || now.Before(before) || !now.Before(expires) { + return Fact{}, errClaim + } + limit := notAfter + if expires.Before(limit) { + limit = expires + } + delete(m, "freshness") + delete(m, "yields_to") + stable, e := canonical(m) + if e != nil { + return Fact{}, errClaim + } + return Fact{raw: append([]byte(nil), raw...), stable: stable, digest: want, observed: observed, timeLimit: limit, validatedAt: now}, nil +} + +// MatchObserved requires independently validated original/fetched facts at the +// same captured instant and exact nonfreshness authority correspondence. It +// never rewrites the original request or extends its original deadline. +func MatchObserved(submitted, observed Fact) error { + if submitted.stable == "" || observed.stable == "" || !submitted.validatedAt.Equal(observed.validatedAt) || submitted.stable != observed.stable { + return errClaim + } + return nil +} diff --git a/adapters/go/approvalfact/claim_test.go b/adapters/go/approvalfact/claim_test.go new file mode 100644 index 0000000..26623c8 --- /dev/null +++ b/adapters/go/approvalfact/claim_test.go @@ -0,0 +1,144 @@ +package approvalfact + +import ( + "bytes" + "encoding/json" + "os" + "strings" + "testing" + "time" +) + +type vectors struct { + Native []struct { + Binding json.RawMessage `json:"binding"` + Canonical string `json:"canonical_binding_utf8"` + Digest string `json:"native_digest"` + } `json:"native_binding_vectors"` + Claims []struct { + Name string `json:"name"` + Now time.Time `json:"validation_time"` + Submitted json.RawMessage `json:"submitted_claim"` + Observed json.RawMessage `json:"observed_claim"` + Binding json.RawMessage `json:"expected_binding"` + PDP string `json:"expected_pdp_digest"` + Accept bool `json:"expected_accept"` + } `json:"claim_cases"` + Transport []struct { + Name string `json:"name"` + Body string `json:"body_utf8"` + } `json:"transport_cases"` +} + +func load(t *testing.T) vectors { + t.Helper() + raw, e := os.ReadFile("testdata/owner-vectors.json") + if e != nil { + t.Fatal(e) + } + var v vectors + if e = json.Unmarshal(raw, &v); e != nil { + t.Fatal(e) + } + return v +} + +func TestProducerCanonicalVectors(t *testing.T) { + for _, v := range load(t).Native { + t.Run(v.Digest, func(t *testing.T) { + raw, d, e := CanonicalBinding(v.Binding) + if e != nil || string(raw) != v.Canonical || d != v.Digest { + t.Fatalf("producer parity: %q %s %v, want %q %s", raw, d, e, v.Canonical, v.Digest) + } + }) + } +} + +func TestProducerClaimVectors(t *testing.T) { + for _, v := range load(t).Claims { + t.Run(v.Name, func(t *testing.T) { + var id struct { + ID string `json:"approval_id"` + } + if e := json.Unmarshal(v.Submitted, &id); e != nil { + t.Fatal(e) + } + expected := Expected{id.ID, v.Binding, v.PDP} + old, e1 := ValidateClaim(v.Submitted, expected, v.Now) + fresh, e2 := ValidateClaim(v.Observed, expected, v.Now) + accepted := e1 == nil && e2 == nil && MatchObserved(old, fresh) == nil + if accepted != v.Accept { + t.Fatalf("accepted=%v want=%v original=%v observed=%v", accepted, v.Accept, e1, e2) + } + if accepted { + raw := old.RawJSON() + if !bytes.Equal(raw, v.Submitted) { + t.Fatal("original bytes changed") + } + raw[0] = 'x' + if old.RawJSON()[0] != '{' { + t.Fatal("mutable fact storage") + } + if !old.Deadline().Before(fresh.Deadline()) { + t.Fatal("refresh must not extend original deadline") + } + } + }) + } +} + +func TestStrictJSONRefusals(t *testing.T) { + for _, raw := range []string{`{"a":1,"a":2}`, `{"a":{"x":1,"x":2}}`, `{"a":NaN}`, `{"a":1e999}`, `{"a":"\ud800"}`, `{"a":"\udfff"}`, `{"a":"\ud800x"}`, `{"a":1} {}`, `{"a":"` + string([]byte{0xff}) + `"}`, strings.Repeat("[", 130) + strings.Repeat("]", 130), strings.Repeat(" ", MaxJSONBytes+1)} { + if _, e := DecodeJSON([]byte(raw)); e == nil { + t.Fatalf("accepted malformed JSON %.80q", raw) + } + } +} + +func TestUnicodeAndControlEncoding(t *testing.T) { + for _, raw := range []string{`{"v":"\ud83d\ude00"}`, `{"v":"escaped \\\" quote"}`, `{"v":"\u0000\b\f\n\r\t"}`} { + if _, e := DecodeJSON([]byte(raw)); e != nil { + t.Fatalf("refused valid Unicode %q", raw) + } + } +} + +func TestGoldenDuplicateAndNonfiniteBodies(t *testing.T) { + for _, v := range load(t).Transport { + if v.Name == "duplicate_valid_now" || v.Name == "nonfinite_json" { + if _, e := DecodeJSON([]byte(v.Body)); e == nil { + t.Fatal(v.Name) + } + } + } +} + +func TestInvalidExpectedAndZeroFacts(t *testing.T) { + v := load(t).Claims[0] + if _, e := ValidateClaim(v.Submitted, Expected{}, v.Now); e == nil { + t.Fatal("missing expected act accepted") + } + if MatchObserved(Fact{}, Fact{}) == nil { + t.Fatal("zero facts accepted") + } + expected := Expected{"11111111-1111-4111-8111-111111111111", v.Binding, v.PDP} + a, e := ValidateClaim(v.Submitted, expected, v.Now) + if e != nil { + t.Fatal(e) + } + b, e := ValidateClaim(v.Observed, expected, v.Now.Add(time.Second)) + if e != nil { + t.Fatal(e) + } + if MatchObserved(a, b) == nil { + t.Fatal("different validation instants accepted") + } +} + +func TestTimestampPrecisionAndOffsetRefusals(t *testing.T) { + for _, value := range []string{"2026-08-29T12:00:00,1Z", "2026-08-29T12:00:00+24:00", "2026-08-29T12:00:00+00:60", "2026-08-29T12:00:00.0000000001Z", "2026-08-29T12:00:00"} { + if _, err := stamp(value); err == nil { + t.Fatalf("accepted unsupported timestamp %q", value) + } + } +} diff --git a/adapters/go/approvalfact/go.mod b/adapters/go/approvalfact/go.mod new file mode 100644 index 0000000..bcb1783 --- /dev/null +++ b/adapters/go/approvalfact/go.mod @@ -0,0 +1,3 @@ +module github.com/netkingdom/approval-engine/adapters/go/approvalfact + +go 1.26.7 diff --git a/adapters/go/approvalfact/oracle_test.go b/adapters/go/approvalfact/oracle_test.go new file mode 100644 index 0000000..b845388 --- /dev/null +++ b/adapters/go/approvalfact/oracle_test.go @@ -0,0 +1,42 @@ +package approvalfact + +import ( + "bufio" + "encoding/json" + "os" + "testing" +) + +// This test-only input is produced by scripts/check_go_approvalfact.py using +// the actual Python owner primitive. Production has no subprocess or oracle. +func TestPythonDifferential(t *testing.T) { + path := os.Getenv("APPROVAL_TEST_ORACLE") + if path == "" { + t.Skip("run scripts/check_go_approvalfact.py for Python differential corpus") + } + f, e := os.Open(path) + if e != nil { + t.Fatal(e) + } + defer f.Close() + s := bufio.NewScanner(f) + count := 0 + for s.Scan() { + var v struct{ Raw, Canonical, Digest string } + if e = json.Unmarshal(s.Bytes(), &v); e != nil { + t.Fatal(e) + } + raw, d, e := CanonicalBinding([]byte(v.Raw)) + if e != nil || string(raw) != v.Canonical || d != v.Digest { + t.Fatalf("Python differential row %d: got %q %s %v want %q %s", count, raw, d, e, v.Canonical, v.Digest) + } + count++ + } + if e = s.Err(); e != nil { + t.Fatal(e) + } + if count < 10000 { + t.Fatalf("incomplete differential corpus: %d", count) + } + t.Logf("matched %d Python source canonical bytes and digests", count) +} diff --git a/adapters/go/approvalfact/testdata/owner-vectors.json b/adapters/go/approvalfact/testdata/owner-vectors.json new file mode 100644 index 0000000..81caf34 --- /dev/null +++ b/adapters/go/approvalfact/testdata/owner-vectors.json @@ -0,0 +1,2305 @@ +{ + "producer_revision": "eece796b8bbee19551f6aa07ad389cbf83cabcdb", + "fixture_only": true, + "source": "Engine.claim with fixed synthetic identities; mutated consumer refusal cases explicitly labeled; no native identity admission", + "schema_version": "0.1", + "claim_cases": [ + { + "name": "fresh_out_of_band_observation_unchanged_original", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": true, + "reason": "matching_valid_facts_original_digest_unchanged" + }, + { + "name": "requested", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "requested", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "requested" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "insufficient_distinct_humans", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "requested", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "requested" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "foreign_issuer", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "foreign", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "foreign_id", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "22222222-2222-4222-8222-222222222222", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "nonhuman_control", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": false + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "legacy_missing_human_control", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "human_control_integer", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": 1 + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "wrong_pdp_digest", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:abababababababababababababababababababababababababababababababab", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "null_pdp_digest", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": null, + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "undeclared_pdp_path", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": false, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "valid_now_integer", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": 1, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "wrong_native_digest", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:abababababababababababababababababababababababababababababababab", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "wrong_act", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "other", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "future_observation", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:06+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "inconsistent_ttl", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 31, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "zero_ttl", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 0, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "naive_observation", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "original_observation_expired_even_when_provider_fresh", + "validation_time": "2026-08-29T12:00:30+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "refetch_cannot_extend_original_freshness", + "validation_time": "2026-08-29T12:00:30+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:30+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:01:00+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "consumed_source", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "consumed", + "valid_now": false, + "consumed": true, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:05+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:35+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "consumed" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "expired_after_consumption_preserves_consumed_true", + "validation_time": "2026-08-30T12:00:00+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "valid", + "valid_now": true, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "ok" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "11111111-1111-4111-8111-111111111111", + "state": "expired", + "valid_now": false, + "consumed": true, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-30T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-30T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "expired" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "source_fact_refusal" + }, + { + "name": "source_revoked", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "6302eb08-215e-4a17-8e6f-66717874c6af", + "state": "revoked", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "revoked" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "6302eb08-215e-4a17-8e6f-66717874c6af", + "state": "revoked", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "revoked" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "terminal_source_refusal" + }, + { + "name": "source_superseded", + "validation_time": "2026-08-29T12:00:05+00:00", + "submitted_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "8c8f9c0f-b2dd-4a1f-964a-54ac7022803b", + "state": "superseded", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "superseded" + }, + "observed_claim": { + "schema_version": "0.1", + "kind": "approval-claim", + "yields_to": "net-kingdom taxonomy request-claim schema (statute §17; unassigned)", + "issuer": "approval-engine", + "approval_id": "8c8f9c0f-b2dd-4a1f-964a-54ac7022803b", + "state": "superseded", + "valid_now": false, + "consumed": false, + "binding": { + "action": "secrets.kv.destroy", + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "digest": "sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b", + "pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "pdp_path": true, + "human_control": true + }, + "freshness": { + "observed_at": "2026-08-29T12:00:00+00:00", + "ttl_seconds": 30, + "not_after": "2026-08-29T12:00:30+00:00" + }, + "validity": { + "not_before": "2026-08-29T11:00:00+00:00", + "expires_at": "2026-08-29T15:00:00+00:00" + }, + "reason_code": "superseded" + }, + "expected_binding": { + "action": "secrets.kv.destroy", + "target": { + "id": "lane-openbao-root", + "stage": "prod" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "expected_pdp_digest": "sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd", + "expected_accept": false, + "reason": "terminal_source_refusal" + } + ], + "native_binding_vectors": [ + { + "input_target_json": "{\"value\":true}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": true + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":true}}", + "native_digest": "sha256:e1f537daa849e5da023efb5f3d62cc33d94cce20f9ca9c5a708c76e225fd702f" + }, + { + "input_target_json": "{\"value\":1}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1}}", + "native_digest": "sha256:3a3d2da6267e4b917505a7c2247e6ce36fd8ee074eef37251bcc66839b602332" + }, + { + "input_target_json": "{\"value\":1.0}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1.0 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1.0}}", + "native_digest": "sha256:21c2cdb9b161071ebfe361e50703511b2abf6c6f327f153282d247aa92c3898e" + }, + { + "input_target_json": "{\"value\":false}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": false + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":false}}", + "native_digest": "sha256:278cbcd013f7923db5258dd860f911d688505a8259f15ccb77983b044f312c74" + }, + { + "input_target_json": "{\"value\":0}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 0 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":0}}", + "native_digest": "sha256:e691b8fb55d4bf82a310dee37c045331100c8031e26ea6f51bdcceeeb3b1f20e" + }, + { + "input_target_json": "{\"value\":-0.0}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": -0.0 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":-0.0}}", + "native_digest": "sha256:eccde458c686bff67506bb2d94c750e648a311bf23967cb22b37f11e84b6844f" + }, + { + "input_target_json": "{\"value\":1e-7}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1e-07 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1e-07}}", + "native_digest": "sha256:779042b6c0e675f6aacdcd3a02199aab66f534bb84dd410a95c18657afae563c" + }, + { + "input_target_json": "{\"value\":1e-6}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1e-06 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1e-06}}", + "native_digest": "sha256:d021f402231fcd59e764b31e6f08a5298ebb328dfd93da58ecde8feb11fde0b5" + }, + { + "input_target_json": "{\"value\":1e20}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1e+20 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1e+20}}", + "native_digest": "sha256:14270ffca4ab10e9d9302d52e988b4aa3e2facd494c12a60e2edafef70277a20" + }, + { + "input_target_json": "{\"value\":1e21}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 1e+21 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":1e+21}}", + "native_digest": "sha256:75a60a595b2578430b0be466b6bf9a46b62ddc99bbbcebd7fa0e8d22e3078c0a" + }, + { + "input_target_json": "{\"value\":2.5e-7}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 2.5e-07 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":2.5e-07}}", + "native_digest": "sha256:50af37ff75a3e7a22629e746f814995c0c2cd96adb7f1ed5b26999b71d0ac498" + }, + { + "input_target_json": "{\"value\":9007199254740993}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "value": 9007199254740993 + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"value\":9007199254740993}}", + "native_digest": "sha256:765360dc5a1d949bd114c6b749d83069123d8b4b6b1d939f831428a800f50dab" + }, + { + "input_target_json": "{\"html\":\"<>&\", \"unicode\":\"é雪😀\", \"separator\":\"\\u2028\\u2029\"}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "html": "<>&", + "unicode": "é雪😀", + "separator": "

" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"html\":\"<>&\",\"separator\":\"

\",\"unicode\":\"é雪😀\"}}", + "native_digest": "sha256:c203d934743e2954342038a3746b6c37a0f3101e886ef855b1c6ad824adf5dea" + }, + { + "input_target_json": "{\"b\":[true,1,1.0,null,{\"z\":\"x\",\"a\":\"y\"}],\"a\":{}}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "b": [ + true, + 1, + 1.0, + null, + { + "z": "x", + "a": "y" + } + ], + "a": {} + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"a\":{},\"b\":[true,1,1.0,null,{\"a\":\"y\",\"z\":\"x\"}]}}", + "native_digest": "sha256:56306505c882af6edfae5f3d2a5b42e92737a0859236fe5574a21d1874655d51" + } + ], + "transport_cases": [ + { + "name": "duplicate_valid_now", + "status": 200, + "content_type": "application/json", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":30,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\",\"valid_now\":false}", + "expected_accept": false + }, + { + "name": "nonfinite_json", + "status": 200, + "content_type": "application/json", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":NaN,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\"}", + "expected_accept": false + }, + { + "name": "redirect", + "status": 302, + "location": "https://foreign.example/claim", + "expected_accept": false, + "required_secondary_requests": 0 + }, + { + "name": "wrong_media_type", + "status": 200, + "content_type": "text/plain", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":30,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\"}", + "expected_accept": false + }, + { + "name": "provider_unavailable", + "status": 503, + "expected_accept": false + }, + { + "name": "declared_body_over_limit", + "status": 200, + "content_length": 1048577, + "consumer_limit_bytes": 1048576, + "expected_accept": false + }, + { + "name": "environment_proxy_configured", + "required_environment_proxy_requests": 0, + "expected_accept": false, + "note": "If fixed origin is unavailable, proxy cannot supply a replacement fact." + } + ] +} diff --git a/docs/access-claim-fact-reader.md b/docs/access-claim-fact-reader.md new file mode 100644 index 0000000..4669f64 --- /dev/null +++ b/docs/access-claim-fact-reader.md @@ -0,0 +1,137 @@ +# Thin Access Approval fact reader — owner source contract proposal + +Owner scope: Approval Engine / PIP. Work: APPROVAL-WP-0005-T01, selected source +subset of APPROVAL-WP-0002-T01/T05 under CUST-WP-0080. This interprets the existing +claim schema 0.1, not a new API/schema/signature or policy grant. Access's B2 +implementation stays DENY until its separately reviewed complete successor. + +## Authoritative source and guarantee + +Only server-installed, independently admitted provider origin and transport may +GET `/v1/approvals/{id}/claim`, with least-scope `approval:read` credential and +exact admitted store tenant. No caller URL/opener/key/verified flag, redirects, +environment proxy, caller JSON or public same-channel key bootstrap can supply +this provenance. Owner transport must enforce total deadline/size, exact 200 +application/json UTF-8, no encoding, duplicate-key/nonfinite/malformed JSON +refusal, no retry and bounded errors without bearer/body leakage. Provider +unavailability refuses this source join. Never invoke a credential-carrying +Python subprocess as the Go adapter; reuse Go's existing transport boundary. + +At the provider observation, `valid_now: true` AND `binding.human_control: true` +means the configured required count of distinct entries is met and every entry +has verified `principal_type=human`. `/entries` obtains identities/types only from +its authenticator, rejects non-human binds for this declaration, and persists +UNIQUE(approval_id, subject_id); `_evaluate` repeats the threshold/human check. +It does NOT mean required_count is two, requester/actor/principal is human, +directory-derived tenant membership, a particular MFA/assurance level, policy +eligibility, uncompromised source, native issuer admission, or continuing +validity after the observation. Consumer trusts this owner evaluation instead +of fetching identities/counting approvers or revalidating human JWTs. Native +issuer/profile acceptance is still T01, not established by synthetic vectors. + +## Minimal typed fact validation + +The submitted claim and fetched observation must each satisfy existing schema +0.1 with exact JSON types; `human_control` must be present and exactly true on +this lane despite being historically optional in the generic schema. Require +schema_version=0.1, kind=approval-claim, issuer=approval-engine, exact expected +approval UUID, state=valid, valid_now=true, consumed=false, reason_code=ok, +pdp_path=true and non-null lowercase sha256 pdp_digest. Reject decision-shaped +keys and unknown schema members (except optional informational yields_to). +The signed/requesting service must never become a human approver by inference. + +At one captured UTC validation instant now, require zoned RFC3339 timestamps, +positive integral ttl_seconds, observed_at<=now&\", \"unicode\":\"é雪😀\", \"separator\":\"\\u2028\\u2029\"}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "html": "<>&", + "unicode": "é雪😀", + "separator": "

" + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"html\":\"<>&\",\"separator\":\"

\",\"unicode\":\"é雪😀\"}}", + "native_digest": "sha256:c203d934743e2954342038a3746b6c37a0f3101e886ef855b1c6ad824adf5dea" + }, + { + "input_target_json": "{\"b\":[true,1,1.0,null,{\"z\":\"x\",\"a\":\"y\"}],\"a\":{}}", + "binding": { + "action": "secrets.kv.destroy", + "target": { + "b": [ + true, + 1, + 1.0, + null, + { + "z": "x", + "a": "y" + } + ], + "a": {} + }, + "actor": "agt-secrets-engine", + "principal": "bernd", + "purpose": "rotate-exposed-key" + }, + "canonical_binding_utf8": "{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"a\":{},\"b\":[true,1,1.0,null,{\"a\":\"y\",\"z\":\"x\"}]}}", + "native_digest": "sha256:56306505c882af6edfae5f3d2a5b42e92737a0859236fe5574a21d1874655d51" + } + ], + "transport_cases": [ + { + "name": "duplicate_valid_now", + "status": 200, + "content_type": "application/json", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":30,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\",\"valid_now\":false}", + "expected_accept": false + }, + { + "name": "nonfinite_json", + "status": 200, + "content_type": "application/json", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":NaN,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\"}", + "expected_accept": false + }, + { + "name": "redirect", + "status": 302, + "location": "https://foreign.example/claim", + "expected_accept": false, + "required_secondary_requests": 0 + }, + { + "name": "wrong_media_type", + "status": 200, + "content_type": "text/plain", + "body_utf8": "{\"schema_version\":\"0.1\",\"kind\":\"approval-claim\",\"yields_to\":\"net-kingdom taxonomy request-claim schema (statute §17; unassigned)\",\"issuer\":\"approval-engine\",\"approval_id\":\"11111111-1111-4111-8111-111111111111\",\"state\":\"valid\",\"valid_now\":true,\"consumed\":false,\"binding\":{\"action\":\"secrets.kv.destroy\",\"actor\":\"agt-secrets-engine\",\"principal\":\"bernd\",\"purpose\":\"rotate-exposed-key\",\"target\":{\"id\":\"lane-openbao-root\",\"stage\":\"prod\"},\"digest\":\"sha256:0982954d4ecbaad0c7368c207ec530e51d7508c3304b67c10885d112175f929b\",\"pdp_digest\":\"sha256:cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd\",\"pdp_path\":true,\"human_control\":true},\"freshness\":{\"observed_at\":\"2026-08-29T12:00:05+00:00\",\"ttl_seconds\":30,\"not_after\":\"2026-08-29T12:00:35+00:00\"},\"validity\":{\"not_before\":\"2026-08-29T11:00:00+00:00\",\"expires_at\":\"2026-08-29T15:00:00+00:00\"},\"reason_code\":\"ok\"}", + "expected_accept": false + }, + { + "name": "provider_unavailable", + "status": 503, + "expected_accept": false + }, + { + "name": "declared_body_over_limit", + "status": 200, + "content_length": 1048577, + "consumer_limit_bytes": 1048576, + "expected_accept": false + }, + { + "name": "environment_proxy_configured", + "required_environment_proxy_requests": 0, + "expected_accept": false, + "note": "If fixed origin is unavailable, proxy cannot supply a replacement fact." + } + ] +} diff --git a/scripts/check_go_approvalfact.py b/scripts/check_go_approvalfact.py new file mode 100644 index 0000000..c71f576 --- /dev/null +++ b/scripts/check_go_approvalfact.py @@ -0,0 +1,53 @@ +"""Development-only Go parity check against the actual Python producer.""" +from pathlib import Path +import json +import math +import os +import random +import struct +import subprocess +import sys +import tempfile + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +from approval_engine.binding import binding_digest, canonical_binding, canonical_json + + +def main(): + rng = random.Random(20261016) + values = [0.0, -0.0, 5e-324, -5e-324, sys.float_info.max, + sys.float_info.min, 1.0, 1e-4, 1e-5, 1e15, 1e16, 1e20, 1e21] + for _ in range(12000): + value = struct.unpack(">d", rng.getrandbits(64).to_bytes(8, "big"))[0] + if math.isfinite(value): + values.append(value) + for power in range(-323, 309): + value = float("1e" + str(power)) + values.extend([value, math.nextafter(value, 0.0), math.nextafter(value, math.inf)]) + targets = [{"value": value} for value in values if math.isfinite(value)] + targets += [{"value": n} for n in [0, -1, 1, 2**53+1, 10**100, -(10**100)]] + targets += [json.loads('{"value":' + token + '}') for token in + ["-0", "-0.0", "1E0", "1.000", "100e-2", "1e-07", "1e+20"]] + targets += [{"text": "".join(chr(i) for i in range(32)) + '<>&é雪😀\u2028\u2029'}] + for _ in range(200): + points = [rng.randrange(0x110000) for _ in range(20)] + targets.append({"text": "".join(chr(c) for c in points if not 0xD800 <= c <= 0xDFFF)}) + with tempfile.TemporaryDirectory(prefix="approval-go-oracle-") as tmp: + path = Path(tmp) / "oracle.jsonl" + with path.open("w") as stream: + for target in targets: + act = dict(action="execute-ccr", actor="service:secrets-engine", + principal="principal", purpose="differential", target=target) + row = {"Raw": json.dumps(act, ensure_ascii=False, allow_nan=False), + "Canonical": canonical_json(canonical_binding(act)).decode(), + "Digest": binding_digest(act)} + stream.write(json.dumps(row, ensure_ascii=True, allow_nan=False) + "\n") + env = dict(os.environ, APPROVAL_TEST_ORACLE=str(path)) + subprocess.run(["go", "test", "-count=1", "-run", "TestPythonDifferential", "-v", "./..."], + cwd=ROOT / "adapters/go/approvalfact", env=env, check=True) + print(f"Python producer corpus: {len(targets)} cases") + + +if __name__ == "__main__": + main() diff --git a/tests/test_access_claim_vectors.py b/tests/test_access_claim_vectors.py new file mode 100644 index 0000000..72e5191 --- /dev/null +++ b/tests/test_access_claim_vectors.py @@ -0,0 +1,42 @@ +"""Producer facts/canonical vectors only; not a Go consumer acceptance test.""" +import json +from pathlib import Path + +import jsonschema + +from approval_engine.binding import binding_digest, canonical_binding, canonical_json +from approval_engine.store import Engine +from tests.conftest import FROZEN, binding, validity + +ROOT = Path(__file__).resolve().parents[1] +VECTORS = json.loads((ROOT / "examples/access-claim-reader-vectors.json").read_text()) + + +def test_native_vectors_match_actual_producer_bytes_and_digest(): + for case in VECTORS["native_binding_vectors"]: + act = binding(target=json.loads(case["input_target_json"])) + assert act == case["binding"] + assert canonical_json(canonical_binding(act)).decode() == case["canonical_binding_utf8"] + assert binding_digest(act) == case["native_digest"] + digests = [v["native_digest"] for v in VECTORS["native_binding_vectors"]] + assert len(set(digests)) == len(digests) + + +def test_positive_claim_vector_matches_actual_provider_and_schema(): + case = VECTORS["claim_cases"][0] + engine = Engine(clock=lambda: FROZEN) + obj = engine.create(binding(), validity(), approval_id=case["submitted_claim"]["approval_id"], + required_count=2, pdp_digest=case["expected_pdp_digest"], + pdp_path=True, human_control=True) + engine.add_entry(obj.id, "human:one", principal_type="human") + assert engine.claim(obj.id)["valid_now"] is False + engine.add_entry(obj.id, "human:two", principal_type="human") + assert engine.claim(obj.id) == case["submitted_claim"] + from datetime import timedelta + engine.clock = lambda: FROZEN + timedelta(seconds=5) + assert engine.claim(obj.id) == case["observed_claim"] + assert case["submitted_claim"]["freshness"] != case["observed_claim"]["freshness"] + schema = json.loads((ROOT / "schemas/approval_claim.schema.json").read_text()) + jsonschema.Draft202012Validator(schema, format_checker=jsonschema.FormatChecker()).validate( + case["observed_claim"]) + engine.close() diff --git a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md index 856ebc7..2a95d0d 100644 --- a/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md +++ b/workplans/APPROVAL-WP-0002-production-readiness-and-consumer-adoption.md @@ -26,6 +26,12 @@ state_hub_workstream_id: "4fa25ad5-f5d0-5592-aa59-085f8ee3edaf" # APPROVAL-WP-0002 — Production readiness and consumer adoption +2026-10-16 additional scoped demand before Access B2 implementation: +CUST-WP-0080 selects T01/T05's authoritative claim/human-control/freshness and +exact-source consumer contract review and producer fixtures only, tracked in +APPROVAL-WP-0005. Existing residual/native statuses and authority gates remain; +this does not activate other residual work, a consumer policy or live transport. + Move the completed first-cut engine spine into an authenticated, durable, observable production service and prove one PEP integration end to end. This is the residual production scope deliberately excluded from APPROVAL-WP-0001. diff --git a/workplans/APPROVAL-WP-0005-access-claim-fact-contract.md b/workplans/APPROVAL-WP-0005-access-claim-fact-contract.md new file mode 100644 index 0000000..abdb945 --- /dev/null +++ b/workplans/APPROVAL-WP-0005-access-claim-fact-contract.md @@ -0,0 +1,83 @@ +--- +id: APPROVAL-WP-0005 +type: workplan +title: "Approval fact contract and reusable Go data adapter" +domain: infotech +repo: approval-engine +status: active +flavor: implementation +owner: codex +topic_slug: netkingdom +created: "2026-10-16" +updated: "2026-10-16" +depends_on: [] +--- + +CUST-WP-0080 explicitly selects the necessary Approval-WP-0002-T01/T05 source +contract subset for the Access service HTTP B2 join. Initial scope is owner +review and producer fixtures; subsequent explicit CUST-WP-0080 demand selects +T03's pure reusable data adapter, agreed with Access before implementation. +No broad residual activation, credential transport runtime, trust admission, +policy eligibility or live action is included. + +## Review authoritative claim guarantees and publish thin consumer vectors + +```task +id: APPROVAL-WP-0005-T01 +status: done +priority: high +``` + +INTENT basis: factual exact-bound approval claims supplied to Access, never a +second PDP. Source contract documents verified human-control semantics and their +issuer admission limit, both original/subsequent observation bounds, exact +native/PDP correspondence and immutable original submitted context. Producer +vectors cover current lifecycle and canonical types. The superseded proposal's +request-refresh design requires change; fresh facts are verified out-of-band. + +## Independently integrate owner contract and coordinate Access reader conformance + +```task +id: APPROVAL-WP-0005-T02 +status: todo +priority: high +``` + +Coordinator reviews/integrates these source records. Access owns the thin Go +reader/normalization and conformance implementation under FLEX-WP-0024-T06; +Secrets owns strict immutable handoff and Platform owns provider origin/custody +and final client pin. Native issuer/profile and served-PEP obligations remain +live under APPROVAL-WP-0002-T01/T05, with audit under T04. No new native residual +is created, promoted or marked complete by these source-only fixtures. + +## Supply producer-owned reusable Go claim and native binding data adapter + +```task +id: APPROVAL-WP-0005-T03 +status: done +priority: high +``` + +Explicit bounded implementation demand after Access confirmed the exact module +and raw-JSON API: adapters/go/approvalfact contains the existing Python-native +five-field canonicalization port and strict human/PDP-path typed claim validation +plus original/fresh fact matching. It is pure data code, not a source transport, +credential runtime, policy evaluator or consume service. Fact means validated +shape/correspondence, never authenticated provider origin. + +Fourteen pinned native vectors and 24 claim cases pass in Go, with strict +duplicate/Unicode/type/expiry defenses and detached original bytes. A 14,114-case +deterministic differential corpus matches actual Python producer canonical bytes +and digests; Go race tests pass. This is source sample evidence, not exhaustive +proof or native acceptance. An initial module toolchain selection failure and +shared cache sandbox refusal are retained in preparation evidence; final module +uses Access's existing Go1.26.7 and completed tests. + +Access explicitly retains the expected-native-raw-source correspondence gate: +its current decoded float64 maps cannot recover Python float/int identity. +The current pilot's raw resource may be used only with proof of no active target +registry override plus exact existing tenant normalization. Other enrichment +needs authoritative raw source/compiler representation. No numeric restriction, +digest substitution, foreign review, runtime enable or live proof is invented. +T02 still owns independent source integration/handoff; existing T01/T05 native +residual obligations and actual Access conformance/integration remain unchanged.