Withhold uncommitted decision effects and pin accepted layer canon

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
This commit is contained in:
tegwick 2026-10-11 02:21:40 +02:00
parent f5fad7c285
commit 22185e94ec
12 changed files with 269 additions and 65 deletions

View file

@ -254,9 +254,9 @@ type failingRecorder struct{}
func (failingRecorder) Append(api.DecisionEnvelope) error { return errors.New("disk full") }
// FLEX-DEC-2026-018 holds on the readback: an allow whose record fails to
// commit is withheld (503, rendered "could not be checked"), never released.
func TestReadbackWithholdsUncommittedAllow(t *testing.T) {
// GH-DEC-2026-024: both permissive and restrictive results require their
// record. Failure is unavailable (503), not a manufactured denial or allow.
func TestReadbackWithholdsUncommittedEffects(t *testing.T) {
service := newTestReadback(t)
service.SetDecisionLog(failingRecorder{})
server := httptest.NewServer(newReadbackMux(service, callerauth.Disabled()))
@ -267,8 +267,8 @@ func TestReadbackWithholdsUncommittedAllow(t *testing.T) {
t.Fatalf("uncommitted allow = %d, want 503", got)
}
deny, _ := json.Marshal(subjectRequest("user:bob", false, "demo-app"))
if got := postReadback(t, server.URL, string(deny)).StatusCode; got != http.StatusOK {
t.Fatalf("uncommitted deny = %d, want 200 (a restriction is never withheld)", got)
if got := postReadback(t, server.URL, string(deny)).StatusCode; got != http.StatusServiceUnavailable {
t.Fatalf("uncommitted deny = %d, want 503", got)
}
}
@ -343,3 +343,27 @@ func TestReadbackInformedDecisionCatalogue(t *testing.T) {
t.Fatalf("catalogue = %+v", systems)
}
}
// A real restrictive policy result reaches the same unavailable HTTP contract
// as an allow when its mandatory record cannot commit.
func TestEnforcementWithholdsUncommittedDenyHTTP(t *testing.T) {
engine, err := buildEngine(context.Background(), opsPath("registry_snapshot.json"), opsPath("policy_package.md"), "")
if err != nil {
t.Fatal(err)
}
engine.SetDecisionLog(failingRecorder{})
server := httptest.NewServer(newServeMux(engine))
defer server.Close()
body, err := os.ReadFile(opsPath("check_request_deny_ttl_above_max.json"))
if err != nil {
t.Fatal(err)
}
response, err := http.Post(server.URL+"/v1/check", "application/json", bytes.NewReader(body))
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("uncommitted deny status = %d; want 503", response.StatusCode)
}
}