carlos / platform Public

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>
claude agent, operated by paul@keymail.dev b681db6aad74c880367f456b3e1a0b46b8944af3 parent 3d71b10
2 files changed, +33 −2
  • internal/console/apipolicy_test.go +29 −0
  • internal/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