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
|
|
@ -6,6 +6,7 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
|
|
@ -198,6 +199,112 @@ func TestExplainUsesRecordedDecision(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCheckRecordsRegistrySnapshotDigest(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
engine := newTestEngineWithStore(t, store)
|
||||
want := store.Digest()
|
||||
|
||||
first, err := engine.Check(context.Background(), api.CheckRequest{
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", System: "markitect-tool"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check: %v", err)
|
||||
}
|
||||
if first.Provenance.RegistrySnapshotDigest != want {
|
||||
t.Fatalf("digest = %q; want %q", first.Provenance.RegistrySnapshotDigest, want)
|
||||
}
|
||||
if first.Provenance.PolicyPackageDigest == "" || !strings.HasPrefix(first.Provenance.PolicyPackageDigest, "sha256:") {
|
||||
t.Fatalf("policy package digest = %q", first.Provenance.PolicyPackageDigest)
|
||||
}
|
||||
|
||||
second, err := engine.Check(context.Background(), api.CheckRequest{
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", System: "markitect-tool"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check again: %v", err)
|
||||
}
|
||||
if second.Provenance.RegistrySnapshotDigest != want {
|
||||
t.Fatal("two decisions over the same snapshot disagree on digest")
|
||||
}
|
||||
|
||||
if err := store.ImportResourceManifest(api.ResourceManifest{
|
||||
ID: "markitect-extra",
|
||||
System: "markitect-tool",
|
||||
Resources: []api.Resource{
|
||||
{ID: "document:other-note", Type: "document"},
|
||||
},
|
||||
}); err != nil {
|
||||
t.Fatalf("ImportResourceManifest: %v", err)
|
||||
}
|
||||
changedEngine := newTestEngineWithStore(t, store)
|
||||
changed, err := changedEngine.Check(context.Background(), api.CheckRequest{
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", System: "markitect-tool"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Check after snapshot change: %v", err)
|
||||
}
|
||||
if changed.Provenance.RegistrySnapshotDigest == want {
|
||||
t.Fatal("changed snapshot kept the same digest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckAllowLifetimes(t *testing.T) {
|
||||
fixed := time.Date(2026, 8, 29, 12, 0, 0, 0, time.UTC)
|
||||
request := api.CheckRequest{
|
||||
Subject: api.SubjectRef{ID: "user:alice"},
|
||||
Action: "read",
|
||||
Resource: api.ResourceRef{ID: "document:internal-note", System: "markitect-tool"},
|
||||
}
|
||||
|
||||
defaulted := newTestEngine(t)
|
||||
defaulted.SetClock(func() time.Time { return fixed })
|
||||
got, err := defaulted.Check(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("default Check: %v", err)
|
||||
}
|
||||
if got.Effect != api.DecisionEffectAllow || got.Lifetime == nil || got.Lifetime.TTL != "15m" {
|
||||
t.Fatalf("defaulted allow = %+v", got)
|
||||
}
|
||||
if got.Lifetime.ExpiresAt != "2026-08-29T12:15:00Z" {
|
||||
t.Fatalf("default expires_at = %q", got.Lifetime.ExpiresAt)
|
||||
}
|
||||
if got.ContractVersion != api.DecisionRecordContractV1 {
|
||||
t.Fatalf("contract_version = %q", got.ContractVersion)
|
||||
}
|
||||
|
||||
declared := newTestEngineWithTTL(t, "5m")
|
||||
declared.SetClock(func() time.Time { return fixed })
|
||||
got, err = declared.Check(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("declared Check: %v", err)
|
||||
}
|
||||
if got.Effect != api.DecisionEffectAllow || got.Lifetime == nil || got.Lifetime.TTL != "5m" {
|
||||
t.Fatalf("declared allow = %+v", got)
|
||||
}
|
||||
if got.Lifetime.ExpiresAt != "2026-08-29T12:05:00Z" {
|
||||
t.Fatalf("declared expires_at = %q", got.Lifetime.ExpiresAt)
|
||||
}
|
||||
|
||||
unstated := newTestEngineWithTTL(t, "none")
|
||||
unstated.SetClock(func() time.Time { return fixed })
|
||||
got, err = unstated.Check(context.Background(), request)
|
||||
if err != nil {
|
||||
t.Fatalf("unstated Check: %v", err)
|
||||
}
|
||||
if got.Effect != api.DecisionEffectDeny || got.Reason != api.ReasonAllowLifetimeUnstated {
|
||||
t.Fatalf("unstated allow = %s/%s; want deny/%s", got.Effect, got.Reason, api.ReasonAllowLifetimeUnstated)
|
||||
}
|
||||
if got.Lifetime != nil {
|
||||
t.Fatalf("unstated deny still has lifetime %+v", got.Lifetime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckWritesDecisionLog(t *testing.T) {
|
||||
engine := newTestEngine(t)
|
||||
log := audit.NewJSONLDecisionLog(filepath.Join(t.TempDir(), "decisions.jsonl"))
|
||||
|
|
@ -240,11 +347,22 @@ func newTestEngine(t *testing.T) *decision.Engine {
|
|||
|
||||
func newTestEngineWithStore(t *testing.T, store *registry.Store) *decision.Engine {
|
||||
t.Helper()
|
||||
return newTestEngineWithStoreAndTTL(t, store, "")
|
||||
}
|
||||
|
||||
func newTestEngineWithTTL(t *testing.T, allowTTL string) *decision.Engine {
|
||||
t.Helper()
|
||||
return newTestEngineWithStoreAndTTL(t, newTestStore(t), allowTTL)
|
||||
}
|
||||
|
||||
func newTestEngineWithStoreAndTTL(t *testing.T, store *registry.Store, allowTTL string) *decision.Engine {
|
||||
t.Helper()
|
||||
|
||||
policyPackage, err := policy.LoadAndValidateFile(context.Background(), filepath.Join("..", "..", "examples", "caring", "policy_package.md"))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadAndValidateFile policy: %v", err)
|
||||
}
|
||||
policyPackage.Metadata.AllowTTL = allowTTL
|
||||
engine, err := decision.NewEngine(store, policyPackage)
|
||||
if err != nil {
|
||||
t.Fatalf("NewEngine: %v", err)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue