Persist ComboNeedsConfirm plans at confirm tier without executing writes, then run task.run via OwnerMux and a stable idempotency key. Co-authored-by: Cursor <cursoragent@cursor.com>
52 lines
1.3 KiB
Go
52 lines
1.3 KiB
Go
package intentflow
|
|
|
|
import "context"
|
|
|
|
// QueryGuard only authorizes low-risk query actions opened from auto_read.
|
|
// Write/generate keys fail closed; OwnerMux routes those to Task.
|
|
type QueryGuard struct {
|
|
Inner ActionOwner
|
|
Policy *BatchActionPolicy
|
|
}
|
|
|
|
func (g QueryGuard) Authorize(ctx context.Context, exec StepExec) error {
|
|
policy := g.Policy
|
|
if policy == nil {
|
|
policy = DefaultBatchActionPolicy()
|
|
}
|
|
row, err := policy.Lookup(exec.ActionKey)
|
|
if err != nil || row.RejectPlan || row.RequiresBatchConfirm {
|
|
return ErrPlanActionForbidden
|
|
}
|
|
if inner, ok := g.Inner.(StepAuthorizer); ok {
|
|
return inner.Authorize(ctx, exec)
|
|
}
|
|
if g.Inner == nil {
|
|
return ErrPlanActionForbidden
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g QueryGuard) Execute(ctx context.Context, exec StepExec) (Receipt, error) {
|
|
if err := g.Authorize(ctx, exec); err != nil {
|
|
return Receipt{}, err
|
|
}
|
|
if g.Inner == nil {
|
|
return Receipt{}, ErrPlanActionForbidden
|
|
}
|
|
return g.Inner.Execute(ctx, exec)
|
|
}
|
|
|
|
func (g QueryGuard) LookupReceipt(ctx context.Context, idempotencyKey string) (Receipt, bool, error) {
|
|
if g.Inner == nil {
|
|
return Receipt{}, false, nil
|
|
}
|
|
return g.Inner.LookupReceipt(ctx, idempotencyKey)
|
|
}
|
|
|
|
func (g QueryGuard) ObserveFence(stepID string, token int64) {
|
|
if obs, ok := g.Inner.(FenceObserver); ok {
|
|
obs.ObserveFence(stepID, token)
|
|
}
|
|
}
|