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
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue