Finish FLEX-WP-0019 layer-model v0.7 conformance
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Build and Publish Container Image / build-and-push (push) Successful in 57s

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:
tegwick 2026-09-03 23:48:45 +02:00
parent 9689894c15
commit 56940727bf
32 changed files with 1194 additions and 111 deletions

View file

@ -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)