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:
parent
f5fad7c285
commit
22185e94ec
12 changed files with 269 additions and 65 deletions
|
|
@ -611,7 +611,7 @@ func mountEmission(mux *http.ServeMux, outbox *emission.Outbox) {
|
|||
func writeHTTP(w http.ResponseWriter, value any, err error) {
|
||||
w.Header().Set("content-type", "application/json")
|
||||
if errors.Is(err, decisioncore.ErrRecordNotCommitted) {
|
||||
// The consumer's declared stance applies (FLEX-DEC-2026-018).
|
||||
// Unavailable evaluation is not an authorization verdict (GH-DEC-2026-024).
|
||||
http.Error(w, err.Error(), http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,22 +43,22 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// FLEX-DEC-2026-018: withholding a deny turns it into an error an open-stance
|
||||
// consumer reads as proceed, so a restriction is released and counted.
|
||||
func TestUncommittedDenyIsReleasedAndCounted(t *testing.T) {
|
||||
// GH-DEC-2026-024: a restriction is not exempt from required commitment.
|
||||
func TestUncommittedDenyIsWithheld(t *testing.T) {
|
||||
engine := newTestEngine(t)
|
||||
recorder := &failingRecorder{}
|
||||
engine.SetDecisionLog(recorder)
|
||||
|
||||
got, err := engine.Check(context.Background(), deniedRead)
|
||||
if err != nil {
|
||||
t.Fatalf("Check: %v; a restriction must never be withheld", err)
|
||||
if !errors.Is(err, decision.ErrRecordNotCommitted) || got.ID != "" {
|
||||
t.Fatalf("Check = %+v, %v; want unavailable and no decision", got, err)
|
||||
}
|
||||
if got.Effect != api.DecisionEffectDeny {
|
||||
t.Fatalf("effect = %q; want deny", got.Effect)
|
||||
if len(recorder.uncommitted) != 0 {
|
||||
t.Fatalf("withheld restriction counted as released: %v", recorder.uncommitted)
|
||||
}
|
||||
if len(recorder.uncommitted) != 1 || recorder.uncommitted[0] != api.DecisionEffectDeny {
|
||||
t.Fatalf("released_uncommitted = %v; want [deny]", recorder.uncommitted)
|
||||
engine.SetDecisionLog(nil)
|
||||
committed, err := engine.Check(context.Background(), deniedRead)
|
||||
if err != nil || committed.Effect != api.DecisionEffectDeny {
|
||||
t.Fatalf("normal deny = %+v, %v", committed, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -99,7 +99,19 @@ func TestBatchCommitsOnceAndWithholdsOnAnyUncommittedAllow(t *testing.T) {
|
|||
Action: "read",
|
||||
Resources: []api.ResourceRef{deniedRead.Resource},
|
||||
})
|
||||
if err != nil || len(decisions) != 1 {
|
||||
t.Fatalf("restriction-only batch: decisions=%v err=%v; want it released", decisions, err)
|
||||
if !errors.Is(err, decision.ErrRecordNotCommitted) || len(decisions) != 0 {
|
||||
t.Fatalf("restriction-only batch: decisions=%v err=%v; want withheld", decisions, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListAllowedWithholdsWhenAllCandidateDenialsFailCommit(t *testing.T) {
|
||||
engine := newTestEngine(t)
|
||||
recorder := &failingRecorder{}
|
||||
engine.SetDecisionLog(recorder)
|
||||
got, err := engine.ListAllowed(context.Background(), decision.ListAllowedRequest{
|
||||
Subject: deniedRead.Subject, Action: "unknown-action", System: "markitect-tool",
|
||||
})
|
||||
if !errors.Is(err, decision.ErrRecordNotCommitted) || got != nil || recorder.batches != 1 {
|
||||
t.Fatalf("ListAllowed = %v, %v; batches=%d; want unavailable", got, err, recorder.batches)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
51
internal/decision/emission_release_test.go
Normal file
51
internal/decision/emission_release_test.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package decision
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/netkingdom/access-engine/pkg/api"
|
||||
)
|
||||
|
||||
type commitProbe struct {
|
||||
fail bool
|
||||
committed []api.DecisionEnvelope
|
||||
released int
|
||||
}
|
||||
|
||||
func (p *commitProbe) Append(d api.DecisionEnvelope) error {
|
||||
if p.fail {
|
||||
return errors.New("disk unavailable")
|
||||
}
|
||||
p.committed = append(p.committed, d)
|
||||
return nil
|
||||
}
|
||||
func (p *commitProbe) NoteReleasedUncommitted(api.DecisionEffect) { p.released++ }
|
||||
|
||||
// Every source-owned emission class is load-bearing. An outbox failure must
|
||||
// hide every effect from both the response and Explain, including restrictions.
|
||||
func TestRequiredCommitForEveryEffect(t *testing.T) {
|
||||
for _, effect := range api.DecisionEffects() {
|
||||
t.Run(string(effect), func(t *testing.T) {
|
||||
probe := &commitProbe{fail: true}
|
||||
engine := &Engine{history: make(map[string]api.DecisionEnvelope), log: probe}
|
||||
d := api.DecisionEnvelope{ID: "decision", Effect: effect}
|
||||
if err := engine.recordDecisions([]api.DecisionEnvelope{d}); !errors.Is(err, ErrRecordNotCommitted) {
|
||||
t.Fatalf("commit failure = %v; want unavailable", err)
|
||||
}
|
||||
if _, err := engine.Explain(d.ID); err == nil {
|
||||
t.Fatal("uncommitted decision exposed by Explain")
|
||||
}
|
||||
if probe.released != 0 || len(probe.committed) != 0 {
|
||||
t.Fatal("failed commit claimed a release")
|
||||
}
|
||||
probe.fail = false
|
||||
if err := engine.recordDecisions([]api.DecisionEnvelope{d}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := engine.Explain(d.ID); err != nil || got.Effect != effect {
|
||||
t.Fatalf("committed explanation = %+v, %v", got, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -42,15 +42,9 @@ type BatchRecorder interface {
|
|||
AppendBatch([]api.DecisionEnvelope) error
|
||||
}
|
||||
|
||||
// UncommittedNoter counts restrictions released after their record failed to
|
||||
// commit (FLEX-DEC-2026-018).
|
||||
type UncommittedNoter interface {
|
||||
NoteReleasedUncommitted(api.DecisionEffect)
|
||||
}
|
||||
|
||||
// ErrRecordNotCommitted means a permissive decision was withheld because its
|
||||
// record did not commit. The service answers 503, and the consumer's declared
|
||||
// stance applies (FLEX-DEC-2026-018).
|
||||
// ErrRecordNotCommitted means a decision was withheld because its required
|
||||
// record did not commit (GH-DEC-2026-024). The service answers 503; this is
|
||||
// unavailability, never a substitute authorization verdict.
|
||||
var ErrRecordNotCommitted = errors.New("decision withheld: record not committed")
|
||||
|
||||
// ListAllowedRequest describes a deterministic list_allowed call.
|
||||
|
|
@ -134,8 +128,7 @@ func (e *Engine) decide(ctx context.Context, request api.CheckRequest) (api.Deci
|
|||
}
|
||||
|
||||
// Check evaluates one subject/action/resource request. The decision is
|
||||
// released only after its record commits, except that a restriction is never
|
||||
// withheld (FLEX-DEC-2026-018).
|
||||
// released only after its required record commits (GH-DEC-2026-024).
|
||||
func (e *Engine) Check(ctx context.Context, request api.CheckRequest) (api.DecisionEnvelope, error) {
|
||||
decision, err := e.decide(ctx, request)
|
||||
if err != nil {
|
||||
|
|
@ -441,34 +434,22 @@ func callerProvenance(ctx context.Context) *api.CallerProvenance {
|
|||
return out
|
||||
}
|
||||
|
||||
// recordDecisions commits decisions and applies FLEX-DEC-2026-018 when the
|
||||
// commit fails: restrictions are released and counted, and any permissive
|
||||
// decision withholds the call. In a batch that withholds the batch's
|
||||
// restrictions too; no live consumer calls batch_check (user-engine loops
|
||||
// /v1/check), and the residual is stated in the ruling.
|
||||
// recordDecisions releases no effect until its required record commits.
|
||||
// GH-DEC-2026-024 refuses the former restrictive-effect failure exception.
|
||||
// Failed decisions must not enter Explain's history as if they were released.
|
||||
func (e *Engine) recordDecisions(decisions []api.DecisionEnvelope) error {
|
||||
e.mu.Lock()
|
||||
for _, decision := range decisions {
|
||||
e.history[decision.ID] = decision
|
||||
}
|
||||
e.mu.RLock()
|
||||
log := e.log
|
||||
e.mu.Unlock()
|
||||
if log == nil || len(decisions) == 0 {
|
||||
return nil
|
||||
}
|
||||
err := appendAll(log, decisions)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
for _, decision := range decisions {
|
||||
if !decision.Effect.Restricts() {
|
||||
e.mu.RUnlock()
|
||||
if log != nil && len(decisions) != 0 {
|
||||
if err := appendAll(log, decisions); err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrRecordNotCommitted, err)
|
||||
}
|
||||
}
|
||||
if noter, ok := log.(UncommittedNoter); ok {
|
||||
for _, decision := range decisions {
|
||||
noter.NoteReleasedUncommitted(decision.Effect)
|
||||
}
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
for _, decision := range decisions {
|
||||
e.history[decision.ID] = decision
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,11 +59,10 @@ func Vocabulary() []string { return append([]string(nil), canonicalLayers...) }
|
|||
// visibly stale in every run's output — which is the point (kings-guard's
|
||||
// VALIDATED_AGAINST pattern, adopted as the reference by GH-DEC-2026-020).
|
||||
//
|
||||
// It names the ACCEPTED v0.7 text plus the gate-house decisions this checker
|
||||
// enforces beyond it, pinned by commit (GH-DEC-2026-021 §2). v0.8 is held under
|
||||
// GH-DEC-2026-019, so a run must not claim a check against it; the re-point to
|
||||
// v0.8 belongs to the post-flip commit (GH-WP-0004-T11).
|
||||
const ValidatedAgainst = "net-kingdom/canon/standards/security-layer-model_v0.7.md (net-kingdom@66dc491) as amended by GH-DEC-2026-017, GH-DEC-2026-020, GH-DEC-2026-021 (gate-house@39d9287)"
|
||||
// It names the accepted v0.8 artifact and its exact source digest. The
|
||||
// canonical flip was accepted through native informed-decision memo revision2;
|
||||
// this checker remains source-scoped and makes no runtime acceptance claim.
|
||||
const ValidatedAgainst = "net-kingdom/canon/standards/security-layer-model_v0.8.md (sha256:3d8298560069ef471fba28d2b1245fb2309328f231dad1a667cdf0ce5a5a7c8b)"
|
||||
|
||||
// Engine roles from §3.3. An Engine declaration must state one.
|
||||
var validEngineRoles = map[string]bool{
|
||||
|
|
@ -135,6 +134,10 @@ func Check(root string) error {
|
|||
} else if len(pins) > 0 {
|
||||
return fmt.Errorf("layer declaration carries a standard or companion version (A12 r2, GH-DEC-2026-020 §1-§2): %s", strings.Join(pins, "; "))
|
||||
}
|
||||
if err := checkDerivedDeclarations(root, decl); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hits, err := ScanToolingClients(root)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -145,6 +148,43 @@ func Check(root string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Optional sidecars are derived declarations, not another governing source.
|
||||
// A discrepancy remains a finding even though INTENT.md has precedence.
|
||||
func checkDerivedDeclarations(root string, governing Declaration) error {
|
||||
for _, name := range []string{"layer.yaml", "layer.yml"} {
|
||||
path := filepath.Join(root, name)
|
||||
data, err := os.ReadFile(path)
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("read derived declaration %s: %w", name, err)
|
||||
}
|
||||
var sidecar struct {
|
||||
Layer string `yaml:"layer"`
|
||||
Derived bool `yaml:"derived"`
|
||||
DerivedFrom string `yaml:"derived_from"`
|
||||
}
|
||||
if err := yaml.Unmarshal(data, &sidecar); err != nil {
|
||||
return fmt.Errorf("parse derived declaration %s: %w", name, err)
|
||||
}
|
||||
if !sidecar.Derived || sidecar.DerivedFrom != "INTENT.md" {
|
||||
return fmt.Errorf("%s must declare derived: true and derived_from: INTENT.md under §11", name)
|
||||
}
|
||||
expected, _ := CanonicalLayer(governing.Layer)
|
||||
actual, valid := CanonicalLayer(sidecar.Layer)
|
||||
if !valid || expected != actual {
|
||||
return fmt.Errorf("DECLARATION DISAGREEMENT: INTENT.md layer %q governs; %s derived layer %q remains a §11 finding", governing.Layer, name, sidecar.Layer)
|
||||
}
|
||||
if pins, err := DeclarationVersionPins(path); err != nil {
|
||||
return err
|
||||
} else if len(pins) > 0 {
|
||||
return fmt.Errorf("derived layer declaration %s carries a standard or companion pin: %s", name, strings.Join(pins, "; "))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadDeclaration reads YAML frontmatter from INTENT.md.
|
||||
func LoadDeclaration(path string) (Declaration, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
|
|
|
|||
|
|
@ -307,3 +307,36 @@ func TestOwnDeclarationCarriesNoVersionAnywhere(t *testing.T) {
|
|||
t.Fatal("the checker must state the version it validates against on every run")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDerivedDeclarationAgreementAndProvenance(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, sidecar string
|
||||
wantError bool
|
||||
}{
|
||||
{"case folding agrees", "layer: eNgInE\nderived: true\nderived_from: INTENT.md\n", false},
|
||||
{"different layer remains finding", "layer: Staff\nderived: true\nderived_from: INTENT.md\n", true},
|
||||
{"missing derived provenance", "layer: Engine\n", true},
|
||||
{"wrong governing source", "layer: Engine\nderived: true\nderived_from: README.md\n", true},
|
||||
{"derived declaration pin refused", "layer: Engine\nderived: true\nderived_from: INTENT.md\nstandard: security-layer-model_v0.8.md\n", true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
intent := "---\nlayer: Engine\nrole: PDP\nconformance_record: record.md\nsource_of_evidence: false\n---\n"
|
||||
for name, body := range map[string]string{"INTENT.md": intent, "record.md": "derived record", "layer.yaml": tc.sidecar} {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(body), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
err := layer.Check(dir)
|
||||
if (err != nil) != tc.wantError {
|
||||
t.Fatalf("Check error=%v; wantError=%v", err, tc.wantError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatedAgainstAcceptedCut(t *testing.T) {
|
||||
if !strings.Contains(layer.ValidatedAgainst, "security-layer-model_v0.8.md") || !strings.Contains(layer.ValidatedAgainst, "sha256:3d829856") {
|
||||
t.Fatal("checker must identify the accepted exact cut")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ func (s *Service) SetDecisionLog(log decisioncore.DecisionRecorder) {
|
|||
|
||||
// Evaluate answers one row per requested system. Any evaluation or record
|
||||
// error fails the whole request: the consumer renders every row unavailable,
|
||||
// and a withheld allow (FLEX-DEC-2026-018) is never released.
|
||||
// and no effect is released without its required record (GH-DEC-2026-024).
|
||||
func (s *Service) Evaluate(ctx context.Context, request Request) (Response, error) {
|
||||
if err := validate(request); err != nil {
|
||||
return Response{}, err
|
||||
|
|
|
|||
|
|
@ -241,9 +241,8 @@ func DecisionEffects() []DecisionEffect {
|
|||
}
|
||||
}
|
||||
|
||||
// Restricts reports whether an effect withholds authority. A restriction is
|
||||
// released even when its record fails to commit, because withholding it turns
|
||||
// it into an error an open-stance consumer reads as proceed (FLEX-DEC-2026-018).
|
||||
// Restricts reports whether an effect withholds authority. This classification
|
||||
// does not exempt an effect from required durable emission (GH-DEC-2026-024).
|
||||
func (e DecisionEffect) Restricts() bool {
|
||||
switch e {
|
||||
case DecisionEffectDeny, DecisionEffectRedact, DecisionEffectNotApplicable:
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ func main() {
|
|||
os.Exit(2)
|
||||
}
|
||||
fmt.Printf("Validated against: %s\n", layer.ValidatedAgainst)
|
||||
fmt.Printf("Scope: this repository only — %s/INTENT.md frontmatter (the §11 declaration), the files it names, and production Go sources under cmd/, internal/, pkg/. Stance, claims and classification maps are not read.\n", filepath.Base(root))
|
||||
fmt.Printf("Scope: this repository only — %s/INTENT.md frontmatter (the §11 declaration), optional derived layer.yaml/layer.yml declarations, the files INTENT names, and production Go sources under cmd/, internal/, pkg/. Stance, claims and classification maps are not read.\n", filepath.Base(root))
|
||||
if _, err := os.Stat(filepath.Join(root, "INTENT.md")); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "INTENT.md not found in %s\n", root)
|
||||
os.Exit(2)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ related_workplans:
|
|||
- FLEX-WP-0030
|
||||
- FLEX-WP-0019
|
||||
created: "2026-09-21"
|
||||
updated: "2026-10-04"
|
||||
updated: "2026-10-10"
|
||||
state_hub_workstream_id: "84f5d9fe-b4c9-584a-b964-efe3e48af095"
|
||||
---
|
||||
|
||||
|
|
@ -279,3 +279,43 @@ implementation and its native acceptance. See
|
|||
preparation no longer wait on another repository, but admission, statutory
|
||||
ruling, valid reconciliation time basis and approved native acceptance do.
|
||||
`cadence.yaml` remains `declared-not-yet-emitting` and G2 remains open.
|
||||
|
||||
## 9. Enforce the accepted mandatory-emission ruling in source
|
||||
|
||||
```task
|
||||
id: FLEX-WP-0031-T09
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "02a8d006-cc70-5a0e-b773-6924f05540ed"
|
||||
```
|
||||
|
||||
2026-10-10: implemented `GH-DEC-2026-024` §3, the accepted refusal of
|
||||
load-bearing failure-path release. This corrects the source release behavior
|
||||
described under T01/T03; those dated entries remain historical evidence of
|
||||
`FLEX-DEC-2026-018`, not the current implementation. No new human decision is
|
||||
needed to implement the accepted doctrine in source.
|
||||
|
||||
With a required recorder attached, a failed durable append now withholds every
|
||||
effect, including deny/redact/not_applicable. Single checks, restriction-only
|
||||
batches, list-allowed and application readback retain the existing
|
||||
`ErrRecordNotCommitted` / HTTP 503 availability representation. No replacement
|
||||
allow or deny is manufactured. Uncommitted results are not inserted into
|
||||
Explain's history, and no released-uncommitted counter is incremented. Existing
|
||||
historical outbox counters and their reporting remain readable. Standalone
|
||||
operation without an attached recorder remains unchanged; no source change
|
||||
enables production emission or claims that an unconfigured pin is conformant.
|
||||
|
||||
Tests inject commit failure across the complete five-effect vocabulary, check
|
||||
that Explain cannot expose uncommitted results and can expose committed ones,
|
||||
exercise actual restrictive single/batch/list policy paths, and verify
|
||||
restrictive enforcement and readback HTTP 503. Affected package suites pass:
|
||||
`go test ./internal/decision ./internal/emission ./internal/readback
|
||||
./cmd/access-engine ./pkg/api`.
|
||||
|
||||
This source fix is not deployed. Consumer outage behavior remains governed by
|
||||
each declared PEP stance: a consumer with an open-for-scope stance can treat
|
||||
unavailable evaluation as proceed, so the exact rollout must review those
|
||||
stances rather than claim that a 503 enforces denial. T02 native sender/custody,
|
||||
T06 compatible reconciliation windows, T08 exact approved rollout and silence
|
||||
proof, and T04 G2 closure remain unfinished. `cadence.yaml` remains declared,
|
||||
and the emission completeness gap remains open until native acceptance.
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ type: workplan
|
|||
title: "Per-identity application access readback and readable protected-system registry for user-engine"
|
||||
domain: infotech
|
||||
repo: access-engine
|
||||
status: active
|
||||
status: finished
|
||||
owner: claude
|
||||
topic_slug: netkingdom
|
||||
created: "2026-10-09"
|
||||
updated: "2026-10-09"
|
||||
updated: "2026-10-10"
|
||||
related: [USER-WP-0026, USER-WP-0028]
|
||||
state_hub_workstream_id: "cb9755fa-6824-5288-81c4-3aa2fbff1a9f"
|
||||
---
|
||||
|
|
@ -191,3 +191,27 @@ without a token, `/v1/check` returns 404 on the readback pin, anonymous lifecycl
|
|||
user-engine's live check with its own token is still to come.
|
||||
The pin ships with the image that contains `serve-readback`. Hand user-engine
|
||||
the evidence that unblocks `USER-WP-0026-T03`.
|
||||
|
||||
## Completion review — 2026-10-10
|
||||
|
||||
All five tasks are done. Fresh read-only verification on railiance01 (node
|
||||
92.205.62.239) confirms the lifecycle and application-readback deployments
|
||||
are each Ready 1/1 on identical immutable image
|
||||
`sha256:e8c57c18a4a9d5583be099146984f7e9cd881446218b65aeefd7619b18aa9229`.
|
||||
Readback ingress is restricted to the `user-engine` namespace and
|
||||
`app.kubernetes.io/name=user-engine` pods on port 8080. The source regression
|
||||
run `go test ./cmd/access-engine ./pkg/api -run 'TestReadback|TestPurpose'
|
||||
-count=1` passes, including participant image/package parity, non-transferable
|
||||
readback purpose, unauthorized-caller refusal, enforcement-route isolation,
|
||||
MFA denial, response schema and outbox failure handling.
|
||||
|
||||
The earlier task records retain founder rollout authority and native anonymous
|
||||
route/health receipts; this review performed no deployment or credential action.
|
||||
|
||||
The consumer integration and actual user-engine token probe remain live in the
|
||||
existing owner task `USER-WP-0026-T03` (and the related consumer UI task
|
||||
`USER-WP-0028-T02`). Those checks do not require unfinished Access Engine
|
||||
implementation. The service contract and URL are in this workplan and
|
||||
`docs/application-access-readback.md`. No new residual workplan is needed: the
|
||||
receiving owner records already carry the work. This closure does not assert
|
||||
that the consumer has integrated or that other applications opted in.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue