Finish FLEX-WP-0019 layer-model v0.7 conformance
Close the remaining PDP obligations: mechanical layer declaration check, registry-snapshot digest in provenance, explicit allow TTL, per-input-class freshness deadlines, and the published decision-record contract. Document the canonical request digest as the §6.4.2 replay test. Assistant: grok Assistant-Session: 01a06256-fb71-7102-b3a9-27e6734257d0
This commit is contained in:
parent
9689894c15
commit
56940727bf
32 changed files with 1194 additions and 111 deletions
|
|
@ -5,7 +5,9 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
|
|
@ -71,6 +73,12 @@ func TestDecisionAndAuditExamplesParse(t *testing.T) {
|
|||
if decision.Effect != api.DecisionEffectAllow {
|
||||
t.Errorf("Decision.Effect = %q; want allow", decision.Effect)
|
||||
}
|
||||
if decision.Lifetime == nil || decision.Lifetime.Kind != api.DecisionLifetimeTTL {
|
||||
t.Fatalf("Decision.Lifetime = %+v; want ttl", decision.Lifetime)
|
||||
}
|
||||
if decision.ContractVersion != api.DecisionRecordContractV1 {
|
||||
t.Errorf("Decision.ContractVersion = %q", decision.ContractVersion)
|
||||
}
|
||||
if decision.Caring == nil || decision.Caring.Profile != api.CaringProfileCaring040RC2 {
|
||||
t.Fatalf("Decision.Caring = %+v; want CARING profile metadata", decision.Caring)
|
||||
}
|
||||
|
|
@ -112,6 +120,80 @@ func TestActionAuthorizationExampleParses(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRequestDigestIgnoresIDAndChangesWithAction(t *testing.T) {
|
||||
request := api.CheckRequest{
|
||||
ID: "check:one",
|
||||
Tenant: "tenant:alpha",
|
||||
Subject: api.SubjectRef{ID: "user:alice", Type: api.SubjectTypeHuman},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{
|
||||
ID: "document:internal-note",
|
||||
Type: "document",
|
||||
System: "markitect-tool",
|
||||
},
|
||||
Context: map[string]any{"purpose": "project-delivery"},
|
||||
}
|
||||
first := api.RequestDigest(request)
|
||||
if !strings.HasPrefix(first, "sha256:") || len(first) != len("sha256:")+64 {
|
||||
t.Fatalf("RequestDigest = %q", first)
|
||||
}
|
||||
|
||||
same := request
|
||||
same.ID = "check:other"
|
||||
same.PolicyVersion = "v9"
|
||||
if api.RequestDigest(same) != first {
|
||||
t.Fatal("digest changed when only id/policy_version changed")
|
||||
}
|
||||
|
||||
changed := request
|
||||
changed.Action = "destroy"
|
||||
if api.RequestDigest(changed) == first {
|
||||
t.Fatal("digest did not change when action changed")
|
||||
}
|
||||
|
||||
binding := api.NewDecisionBinding(request)
|
||||
if binding.RequestDigest != first {
|
||||
t.Fatalf("binding digest %q != RequestDigest %q", binding.RequestDigest, first)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAllowLifetimeDefaultDeclaredAndNone(t *testing.T) {
|
||||
now := mustParseTime(t, "2026-08-29T12:00:00Z")
|
||||
|
||||
allow := api.DecisionEnvelope{Effect: api.DecisionEffectAllow, Reason: "reader_relation"}
|
||||
api.ApplyAllowLifetime(&allow, "", now)
|
||||
if allow.Effect != api.DecisionEffectAllow || allow.Lifetime == nil {
|
||||
t.Fatalf("default TTL denied or skipped: %+v", allow)
|
||||
}
|
||||
if allow.Lifetime.TTL != "15m" || allow.Lifetime.ExpiresAt != "2026-08-29T12:15:00Z" {
|
||||
t.Fatalf("default lifetime = %+v", allow.Lifetime)
|
||||
}
|
||||
|
||||
declared := api.DecisionEnvelope{Effect: api.DecisionEffectAllow, Reason: "reader_relation"}
|
||||
api.ApplyAllowLifetime(&declared, "5m", now)
|
||||
if declared.Lifetime == nil || declared.Lifetime.TTL != "5m" || declared.Lifetime.ExpiresAt != "2026-08-29T12:05:00Z" {
|
||||
t.Fatalf("declared lifetime = %+v", declared.Lifetime)
|
||||
}
|
||||
|
||||
unstated := api.DecisionEnvelope{Effect: api.DecisionEffectAllow, Reason: "reader_relation"}
|
||||
api.ApplyAllowLifetime(&unstated, "none", now)
|
||||
if unstated.Effect != api.DecisionEffectDeny || unstated.Reason != api.ReasonAllowLifetimeUnstated {
|
||||
t.Fatalf("unstated allow = %+v; want deny", unstated)
|
||||
}
|
||||
if unstated.Lifetime != nil {
|
||||
t.Fatalf("unstated allow still has lifetime %+v", unstated.Lifetime)
|
||||
}
|
||||
}
|
||||
|
||||
func mustParseTime(t *testing.T, value string) time.Time {
|
||||
t.Helper()
|
||||
parsed, err := time.Parse(time.RFC3339, value)
|
||||
if err != nil {
|
||||
t.Fatalf("parse time %q: %v", value, err)
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func TestSchemaFilesAreJSON(t *testing.T) {
|
||||
schemaDir := filepath.Join("..", "..", "schemas")
|
||||
entries, err := os.ReadDir(schemaDir)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue