console: an ended attempt context fails the create-time clear in every mode (#328, #329)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+33
−2
internal/console/apipolicy_test.go+29 −0internal/console/claim_app_enroll.go+4 −2
diff --git a/internal/console/apipolicy_test.go b/internal/console/apipolicy_test.go| index 4c31b5e68..5a901512e 100644 |
| --- a/internal/console/apipolicy_test.go |
| +++ b/internal/console/apipolicy_test.go |
| @@ -1566,3 +1566,32 @@ func TestPurgeAndRecreateStartsWithoutThePredecessorsPolicy(t *testing.T) { |
| t.Fatalf("the first write after the recreate = %+v, want only default small", r.Policy) |
| } |
| } |
| + |
| +// Review fix round 1: a directory deployment's attempt context is the |
| +// request's own; one that ends while the clear runs fails the clear even |
| +// when the store reports the delete done, and no row is inserted. |
| +func TestCreateClearCancelledInADirectoryDeploymentInsertsNoRow(t *testing.T) { |
| + t.Parallel() |
| + fx := newConsoleFixture(t) |
| + seedPredecessorPolicy(t, fx, "shop") |
| + ctx, cancel := context.WithCancel(context.Background()) |
| + defer cancel() |
| + f := installPolicyDeleteFault(t, fx, false) |
| + f.observe = func(context.Context, string) { cancel() } |
| + acct, err := fx.st.AccountBySqid(fx.sqid) |
| + if err != nil || acct == nil { |
| + t.Fatalf("account %+v %v", acct, err) |
| + } |
| + created, err := fx.srv.createClaimAppRow(ctx, acct, "shop", func(st *Store) (bool, error) { |
| + return st.CreateAppIn(OpsAccountID, "shop", "paul@keymail.dev", time.Now()) |
| + }) |
| + if created || !errors.Is(err, errAppPolicyClear) { |
| + t.Fatalf("createClaimAppRow = %v %v, want a failed clear", created, err) |
| + } |
| + if !slices.Contains(f.seen(), apppolicy.Key(fx.sqid, "shop")) { |
| + t.Fatalf("deletes %v, want the policy key's", f.seen()) |
| + } |
| + if has, err := fx.st.HasAppIn(OpsAccountID, "shop"); err != nil || has { |
| + t.Fatalf("HasAppIn = %v %v, want no row", has, err) |
| + } |
| +} |
diff --git a/internal/console/claim_app_enroll.go b/internal/console/claim_app_enroll.go| index 66cebc584..5245beada 100644 |
| --- a/internal/console/claim_app_enroll.go |
| +++ b/internal/console/claim_app_enroll.go |
| @@ -82,7 +82,7 @@ func (s *Server) createClaimAppRow(ctx context.Context, acct *Account, app strin |
| // clearForCreate is the create's recheck and clear, under the caller's |
| // policyLock: a row for the name now is a lost race (free false, nothing |
| // deleted); otherwise the clear runs as one attempt of hold, and a refused |
| -// attempt, or one whose holding ended during the clear, is a failed clear. |
| +// attempt, or one whose context ended during the clear, is a failed clear. |
| func (s *Server) clearForCreate(ctx context.Context, acct *Account, app string, hold policyHolding) (free bool, err error) { |
| if has, err := s.store.HasAppIn(acct.ID, app); err != nil || has { |
| return false, err |
| @@ -95,7 +95,9 @@ func (s *Server) clearForCreate(ctx context.Context, acct *Account, app string, |
| if err := s.clearAppPolicy(actx, acct, app); err != nil { |
| return false, err |
| } |
| - if !hold.sole && actx.Err() != nil { |
| + // In every mode: a sole holding's attempt context is the caller's own, |
| + // and a create whose context ended is not finished on a store's word. |
| + if actx.Err() != nil { |
| return false, fmt.Errorf("%w: the writer holding ended during the clear", errAppPolicyClear) |
| } |
| return true, nil |