Keep Pass history immutable and replenish atomic with decisions
CI / ci (push) Successful in 2m56s
CI / ci (pull_request) Successful in 2m55s

Fixes the three review findings on 50399b6 (pull request #102):

- A batch now rereads every declined name inside its storing transaction,
  so a Pass committed after the batch read its input but before it stores
  can no longer let the passed dish into the deck (SQ-01).
- A decision that leaves the deck under five creates the replenish batch
  and its job in the decision's own transaction, so a decision is never
  recorded without the replenish it calls for; when they cannot be
  written, neither is the decision, and a retry decides again (SQ-02).
  StartBatch commits the batch with its job the same way, replacing the
  mutex and the fail-on-enqueue cleanup. jobs gains Kind.EnqueueTx and
  Kind.Wake for this.
- Migration 0012 gives each suggestion the name its dish was suggested
  under; declined names for batches and the household context's passed
  list read it instead of the dinner's mutable name, so renaming a passed
  dinner no longer erases it from dedupe (SQ-03).

Task: b4f2558c-227f-4627-852b-be5aa190242a

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 23:47:45 -04:00
co-authored by Claude Opus 5.5
parent 50399b6329
commit 4e2773703b
18 changed files with 482 additions and 125 deletions
+12 -10
View File
@@ -35,9 +35,9 @@ WHERE id = ? AND status IN ('pending', 'running');
-- name: FailOrphanedSuggestionBatches :execrows
-- Fails every batch still pending or running whose job is no longer queued
-- or running: the process stopped before enqueuing the job or before its
-- handler recorded a result. Run at boot, after the job runner fails the
-- jobs a previous process left running.
-- or running: the process stopped before its handler recorded a result.
-- Run at boot, after the job runner fails the jobs a previous process left
-- running.
UPDATE suggestion_batches
SET status = 'failed', updated_at = sqlc.arg(updated_at)
WHERE status IN ('pending', 'running')
@@ -49,10 +49,12 @@ WHERE status IN ('pending', 'running')
);
-- name: CreateSuggestion :exec
-- name is the name the dish is suggested under, kept whatever its dinner is
-- later renamed.
INSERT INTO suggestions (
id, dinner_id, batch_id, reason, cited_regular_ids, status, created_at, updated_at
id, dinner_id, batch_id, name, reason, cited_regular_ids, status, created_at, updated_at
) VALUES (
?, ?, ?, ?, ?, 'pending', ?, ?
?, ?, ?, ?, ?, ?, 'pending', ?, ?
);
-- name: GetSuggestion :one
@@ -79,12 +81,12 @@ JOIN dinners ON dinners.id = suggestions.dinner_id
WHERE suggestions.status = 'pending' AND dinners.status = 'suggested';
-- name: ListDeclinedSuggestionNames :many
-- The name of every declined suggestion's dinner, oldest decision first.
SELECT dinners.name
-- The name every declined suggestion was suggested under, oldest decision
-- first; renaming its dinner does not change it.
SELECT name
FROM suggestions
JOIN dinners ON dinners.id = suggestions.dinner_id
WHERE suggestions.status = 'declined'
ORDER BY suggestions.decided_at, suggestions.id;
WHERE status = 'declined'
ORDER BY decided_at, id;
-- name: DecideSuggestion :execrows
-- Records the household's decision on a pending suggestion; one already