Keep one draft in flight per week
CI / ci (push) Successful in 3m26s
CI / ci (pull_request) Successful in 3m10s

A confirmed draft asked for while the week's draft job was asked for
without confirm no longer enqueues a second job. With two job workers the
two drafts ran at once, and whichever stored second failed plan_changed,
so the household's confirmed draft could fail. StartDraft now refuses it
with ErrDraftRunning, 409 draft_running, and enqueues nothing; the
household asks again once the running draft finishes. Every other draft
joins the week's queued or running job, so a week has at most one.

The plan tests run the job runner with two workers, as cmd/server does,
and a confirmed draft during a running unconfirmed one is refused while
only the running job drafts.

Maestro task ea48e650-d510-49cc-8485-0f07174ee731.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 06:46:13 -04:00
co-authored by Claude Opus 5.5
parent 7e6e49f6b1
commit 45fcd71d27
8 changed files with 133 additions and 117 deletions
+3 -2
View File
@@ -345,8 +345,9 @@ automatic** (D-021).
crock-pot dinner with known minutes, else null. `POST crock-pot dinner with known minutes, else null. `POST
/api/v1/plans/{weekStart}/draft` takes `{confirm?}` and answers 202 /api/v1/plans/{weekStart}/draft` takes `{confirm?}` and answers 202
`{jobId}` for a `draft_week` job, or for the one already queued or `{jobId}` for a `draft_week` job, or for the one already queued or
running for the week — a confirmed one when the draft is confirmed, so a running for the week, so a week has at most one draft in flight. A
confirmed draft never joins an unconfirmed job. The job runs the `draft_week` skill on every Regular confirmed draft while the week's job was asked for without confirm is
409 `draft_running` and enqueues nothing. The job runs the `draft_week` skill on every Regular
and every Quest log dinner no other plan for the current or a later week and every Quest log dinner no other plan for the current or a later week
has, with the household context and last week's dinners, and stores the has, with the household context and last week's dinners, and stores the
week: a new plan is `draft`; a redraft keeps each day's week: a new plan is `draft`; a redraft keeps each day's
+17 -12
View File
@@ -34,30 +34,35 @@ func (q *Queries) CreateWeekPlan(ctx context.Context, arg CreateWeekPlanParams)
return err return err
} }
const getActiveDraftJobID = `-- name: GetActiveDraftJobID :one const getActiveDraftJob = `-- name: GetActiveDraftJob :one
SELECT id FROM jobs SELECT id, CAST(json_extract(payload, '$.confirm') = 1 AS INTEGER) AS confirmed
FROM jobs
WHERE kind = ?1 WHERE kind = ?1
AND status IN ('queued', 'running') AND status IN ('queued', 'running')
AND json_extract(payload, '$.week_start') = ?2 AND json_extract(payload, '$.week_start') = ?2
AND (CAST(?3 AS INTEGER) = 0 OR json_extract(payload, '$.confirm') = 1)
ORDER BY id ORDER BY id
LIMIT 1 LIMIT 1
` `
type GetActiveDraftJobIDParams struct { type GetActiveDraftJobParams struct {
Kind string Kind string
WeekStart string WeekStart string
}
type GetActiveDraftJobRow struct {
ID string
Confirmed int64 Confirmed int64
} }
// The id of the job of kind queued or running for the week starting // The job of kind queued or running for the week starting week_start,
// week_start, one whose payload has confirm when confirmed is 1. Returns // with whether its payload has confirm. StartDraft enqueues a draft only
// sql.ErrNoRows when there is none. // when there is none, so there is at most one. Returns sql.ErrNoRows when
func (q *Queries) GetActiveDraftJobID(ctx context.Context, arg GetActiveDraftJobIDParams) (string, error) { // there is none.
row := q.db.QueryRowContext(ctx, getActiveDraftJobID, arg.Kind, arg.WeekStart, arg.Confirmed) func (q *Queries) GetActiveDraftJob(ctx context.Context, arg GetActiveDraftJobParams) (GetActiveDraftJobRow, error) {
var id string row := q.db.QueryRowContext(ctx, getActiveDraftJob, arg.Kind, arg.WeekStart)
err := row.Scan(&id) var i GetActiveDraftJobRow
return id, err err := row.Scan(&i.ID, &i.Confirmed)
return i, err
} }
const getWeekPlan = `-- name: GetWeekPlan :one const getWeekPlan = `-- name: GetWeekPlan :one
+5 -4
View File
@@ -68,10 +68,11 @@ type Querier interface {
FinishSuggestionBatch(ctx context.Context, arg FinishSuggestionBatchParams) (int64, error) FinishSuggestionBatch(ctx context.Context, arg FinishSuggestionBatchParams) (int64, error)
// Returns sql.ErrNoRows until the row exists. // Returns sql.ErrNoRows until the row exists.
GetAISettings(ctx context.Context) (AiSetting, error) GetAISettings(ctx context.Context) (AiSetting, error)
// The id of the job of kind queued or running for the week starting // The job of kind queued or running for the week starting week_start,
// week_start, one whose payload has confirm when confirmed is 1. Returns // with whether its payload has confirm. StartDraft enqueues a draft only
// sql.ErrNoRows when there is none. // when there is none, so there is at most one. Returns sql.ErrNoRows when
GetActiveDraftJobID(ctx context.Context, arg GetActiveDraftJobIDParams) (string, error) // there is none.
GetActiveDraftJob(ctx context.Context, arg GetActiveDraftJobParams) (GetActiveDraftJobRow, error)
// The batch still pending or running, oldest first. Returns sql.ErrNoRows // The batch still pending or running, oldest first. Returns sql.ErrNoRows
// when there is none. // when there is none.
GetActiveSuggestionBatch(ctx context.Context) (SuggestionBatch, error) GetActiveSuggestionBatch(ctx context.Context) (SuggestionBatch, error)
+19 -15
View File
@@ -71,6 +71,10 @@ var ErrDinnerNotFound = errors.New("dinner not found")
// without confirm. // without confirm.
var ErrConfirmRequired = errors.New("the plan was pushed to Calendar; drafting over it needs confirm") var ErrConfirmRequired = errors.New("the plan was pushed to Calendar; drafting over it needs confirm")
// ErrDraftRunning is a confirmed draft asked for while a draft of the week
// asked for without confirm is queued or running.
var ErrDraftRunning = errors.New("a draft of the week without confirm is already running; ask again when it finishes")
// InvalidWeekError is a week start that is not a Monday date. // InvalidWeekError is a week start that is not a Monday date.
type InvalidWeekError struct { type InvalidWeekError struct {
Value string Value string
@@ -275,10 +279,11 @@ type Draft struct {
// StartDraft enqueues the KindDraftWeek job drafting the week starting // StartDraft enqueues the KindDraftWeek job drafting the week starting
// weekStart, unless one is already queued or running for it, which it // weekStart, unless one is already queued or running for it, which it
// returns instead with Created false. A confirmed draft joins only a // returns instead with Created false, so a week has at most one draft in
// confirmed job, so a job asked for without confirm cannot fail it with // flight. A confirmed draft joins only a confirmed job: while the week's
// ReasonConfirmRequired. Drafting over a synced or changed plan needs // job was asked for without confirm, it fails with ErrDraftRunning and
// confirm, else it fails with ErrConfirmRequired and enqueues nothing. // enqueues nothing. Drafting over a synced or changed plan needs confirm,
// else it fails with ErrConfirmRequired and enqueues nothing.
func (s *Service) StartDraft(ctx context.Context, weekStart store.LocalDate, confirm bool) (Draft, error) { func (s *Service) StartDraft(ctx context.Context, weekStart store.LocalDate, confirm bool) (Draft, error) {
if _, err := household.WeekDays(weekStart); err != nil { if _, err := household.WeekDays(weekStart); err != nil {
return Draft{}, &InvalidWeekError{Value: weekStart.String()} return Draft{}, &InvalidWeekError{Value: weekStart.String()}
@@ -294,19 +299,18 @@ func (s *Service) StartDraft(ctx context.Context, weekStart store.LocalDate, con
case cur.Status != StatusDraft && !confirm: case cur.Status != StatusDraft && !confirm:
return ErrConfirmRequired return ErrConfirmRequired
} }
confirmed := int64(0) active, err := q.GetActiveDraftJob(ctx, db.GetActiveDraftJobParams{Kind: KindDraftWeek, WeekStart: weekStart.String()})
if confirm { switch {
confirmed = 1 case errors.Is(err, sql.ErrNoRows):
} case err != nil:
id, err := q.GetActiveDraftJobID(ctx, db.GetActiveDraftJobIDParams{Kind: KindDraftWeek, WeekStart: weekStart.String(), Confirmed: confirmed}) return fmt.Errorf("plan: read draft job of %s: %w", weekStart, err)
if err == nil { case confirm && active.Confirmed != 1:
d = Draft{JobID: id} return ErrDraftRunning
default:
d = Draft{JobID: active.ID}
return nil return nil
} }
if !errors.Is(err, sql.ErrNoRows) { id, err := s.draftJob.EnqueueTx(ctx, tx, DraftJob{WeekStart: weekStart.String(), Confirm: confirm})
return fmt.Errorf("plan: read draft job of %s: %w", weekStart, err)
}
id, err = s.draftJob.EnqueueTx(ctx, tx, DraftJob{WeekStart: weekStart.String(), Confirm: confirm})
if err != nil { if err != nil {
return fmt.Errorf("plan: enqueue draft of %s: %w", weekStart, err) return fmt.Errorf("plan: enqueue draft of %s: %w", weekStart, err)
} }
+64 -66
View File
@@ -53,9 +53,9 @@ type env struct {
// newEnv returns a plan service over a migrated store in zone // newEnv returns a plan service over a migrated store in zone
// America/New_York with dinner time 17:30, the Regulars below and Quest // America/New_York with dinner time 17:30, the Regulars below and Quest
// log dinners Pad Thai and Shawarma Bowls, and an unstarted job runner. It // log dinners Pad Thai and Shawarma Bowls, and an unstarted job runner
// drafts through the real harness and draft_week with the scripted // with two workers. It drafts through the real harness and draft_week with
// e.model. // the scripted e.model.
func newEnv(t *testing.T) *env { func newEnv(t *testing.T) *env {
t.Helper() t.Helper()
ctx := t.Context() ctx := t.Context()
@@ -106,7 +106,8 @@ func newEnv(t *testing.T) *env {
return skills.DraftWeek(ctx, cfg, scope, in) return skills.DraftWeek(ctx, cfg, scope, in)
} }
logger := slog.New(slog.NewTextHandler(io.Discard, nil)) logger := slog.New(slog.NewTextHandler(io.Discard, nil))
e.runner, err = jobs.NewRunner(st, logger, 1) // Two workers, as cmd/server runs, so drafts of a week could overlap.
e.runner, err = jobs.NewRunner(st, logger, 2)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -518,100 +519,97 @@ func TestStartDraftIsSingleFlight(t *testing.T) {
} }
} }
func TestSimultaneousStartDraftsShareOneJobPerConfirm(t *testing.T) { func TestSimultaneousStartDraftsShareOneJob(t *testing.T) {
e := newEnv(t) for _, confirmFirst := range []bool{false, true} {
first, err := e.svc.StartDraft(t.Context(), week, false) t.Run(fmt.Sprintf("first confirm %v", confirmFirst), func(t *testing.T) {
if err != nil { e := newEnv(t)
t.Fatal(err) first, err := e.svc.StartDraft(t.Context(), week, confirmFirst)
} if err != nil {
// Twenty simultaneous drafts, half of them confirmed: the unconfirmed t.Fatal(err)
// ones join the queued job, and the confirmed ones share one confirmed
// job of their own.
const n = 20
drafts := make([]plan.Draft, n)
errs := make([]error, n)
var wg sync.WaitGroup
for i := range n {
wg.Go(func() {
drafts[i], errs[i] = e.svc.StartDraft(context.Background(), week, i%2 == 1)
})
}
wg.Wait()
confirmedJob := ""
created := 0
for i, d := range drafts {
if errs[i] != nil {
t.Fatalf("draft %d: %v", i, errs[i])
}
if d.Created {
created++
}
if i%2 == 0 {
if d.JobID != first.JobID || d.Created {
t.Errorf("unconfirmed draft %d = %+v, want it to join %s", i, d, first.JobID)
} }
continue // Twenty simultaneous drafts, half of them confirmed: every
} // one joins the queued job, except a confirmed one while the
if confirmedJob == "" { // job is unconfirmed, which is refused.
confirmedJob = d.JobID const n = 20
} drafts := make([]plan.Draft, n)
if d.JobID != confirmedJob || d.JobID == first.JobID { errs := make([]error, n)
t.Errorf("confirmed draft %d = %+v, want the one confirmed job %s, not %s", i, d, confirmedJob, first.JobID) var wg sync.WaitGroup
} for i := range n {
} wg.Go(func() {
if created != 1 { drafts[i], errs[i] = e.svc.StartDraft(context.Background(), week, i%2 == 1)
t.Errorf("%d drafts enqueued a job, want 1", created) })
} }
var payload string wg.Wait()
if err := e.st.Reader.QueryRowContext(t.Context(), `SELECT payload FROM jobs WHERE id = ?`, confirmedJob).Scan(&payload); err != nil { for i, d := range drafts {
t.Fatal(err) if i%2 == 1 && !confirmFirst {
} if !errors.Is(errs[i], plan.ErrDraftRunning) {
if !strings.Contains(payload, `"confirm":true`) { t.Errorf("confirmed draft %d = %+v, %v, want ErrDraftRunning", i, d, errs[i])
t.Errorf("confirmed job payload = %s, want confirm", payload) }
continue
}
if errs[i] != nil || d.JobID != first.JobID || d.Created {
t.Errorf("draft %d = %+v, %v, want it to join %s", i, d, errs[i], first.JobID)
}
}
var queued int
if err := e.st.Reader.QueryRowContext(t.Context(), `SELECT COUNT(*) FROM jobs WHERE kind = ?`, plan.KindDraftWeek).Scan(&queued); err != nil {
t.Fatal(err)
}
if queued != 1 {
t.Errorf("%d draft jobs, want 1", queued)
}
})
} }
} }
func TestConfirmedDraftDoesNotJoinUnconfirmedJob(t *testing.T) { func TestConfirmedDraftWhileUnconfirmedDraftRuns(t *testing.T) {
e := newEnv(t) e := newEnv(t)
e.start(t) e.start(t)
e.reply("first", fullWeek...) e.reply("first", fullWeek...)
e.mustDraft(t, false) e.mustDraft(t, false)
// An unconfirmed redraft is running when the plan is synced; the // An unconfirmed redraft is running when the plan is synced; the
// household then confirms a redraft, which must not join the job that // household then confirms a redraft. With a second worker idle, a
// will fail confirm_required. // second job would draft alongside the first, so the confirmed draft
// is refused and only the first job runs draft_week.
reversed := slices.Clone(fullWeek) reversed := slices.Clone(fullWeek)
slices.Reverse(reversed) slices.Reverse(reversed)
started, release := make(chan struct{}), make(chan struct{}) started, release := make(chan struct{}), make(chan struct{})
r := e.weekReply("unconfirmed", fullWeek...) r := e.weekReply("unconfirmed", fullWeek...)
r.Started, r.Release = started, release r.Started, r.Release = started, release
e.model.QueueGenerate(r) e.model.QueueGenerate(r)
e.reply("confirmed", reversed...) e.reply("concurrent", reversed...)
unconfirmed, err := e.svc.StartDraft(t.Context(), week, false) unconfirmed, err := e.svc.StartDraft(t.Context(), week, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
<-started <-started
e.setStatus(t, plan.StatusSynced) e.setStatus(t, plan.StatusSynced)
confirmed, err := e.svc.StartDraft(t.Context(), week, true) if d, err := e.svc.StartDraft(t.Context(), week, true); !errors.Is(err, plan.ErrDraftRunning) {
if err != nil { t.Fatalf("confirmed draft = %+v, %v, want ErrDraftRunning", d, err)
t.Fatal(err)
}
if !confirmed.Created || confirmed.JobID == unconfirmed.JobID {
t.Fatalf("confirmed draft = %+v, want its own job, not %s", confirmed, unconfirmed.JobID)
} }
close(release) close(release)
if job := e.waitJob(t, unconfirmed.JobID); job.Status != jobs.StatusFailed || *job.Error != plan.ReasonConfirmRequired { if job := e.waitJob(t, unconfirmed.JobID); job.Status != jobs.StatusFailed || *job.Error != plan.ReasonConfirmRequired {
t.Errorf("unconfirmed job %s %v, want failed %s", job.Status, job.Error, plan.ReasonConfirmRequired) t.Errorf("unconfirmed job %s %v, want failed %s", job.Status, job.Error, plan.ReasonConfirmRequired)
} }
if job := e.waitJob(t, confirmed.JobID); job.Status != jobs.StatusSucceeded { var queued int
t.Fatalf("confirmed job %s %v, want succeeded", job.Status, job.Error) if err := e.st.Reader.QueryRowContext(t.Context(), `SELECT COUNT(*) FROM jobs WHERE kind = ?`, plan.KindDraftWeek).Scan(&queued); err != nil {
t.Fatal(err)
}
if queued != 2 {
t.Errorf("%d draft jobs, want the first and the unconfirmed redraft", queued)
} }
p, err := e.svc.Get(t.Context(), week) p, err := e.svc.Get(t.Context(), week)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if p.Status != plan.StatusChanged || *p.AINote != "confirmed" || !slices.Equal(dayNames(p), reversed) { if p.Status != plan.StatusSynced || *p.AINote != "first" || !slices.Equal(dayNames(p), fullWeek) {
t.Errorf("plan = %s %q %v, want the synced first draft", p.Status, *p.AINote, dayNames(p))
}
// Asked again once the unconfirmed job has finished, the confirmed
// draft replaces the plan.
if p := e.mustDraft(t, true); p.Status != plan.StatusChanged || *p.AINote != "concurrent" || !slices.Equal(dayNames(p), reversed) {
t.Errorf("plan = %s %q %v, want the confirmed draft, changed", p.Status, *p.AINote, dayNames(p)) t.Errorf("plan = %s %q %v, want the confirmed draft, changed", p.Status, *p.AINote, dayNames(p))
} }
} }
+8 -4
View File
@@ -44,8 +44,9 @@ func getPlan(logger *slog.Logger, svc *plan.Service) http.HandlerFunc {
// postPlanDraft enqueues the draft of the week and answers 202 with the // postPlanDraft enqueues the draft of the week and answers 202 with the
// jobId that GET /jobs/{id} reports on, without waiting for the job. When // jobId that GET /jobs/{id} reports on, without waiting for the job. When
// a draft of the week is already queued or running, confirmed if this one // a draft of the week is already queued or running, it answers 202 with
// is, it answers 202 with that job instead. // that job instead, or 409 when this draft is confirmed and that one is
// not.
func postPlanDraft(logger *slog.Logger, svc *plan.Service) http.HandlerFunc { func postPlanDraft(logger *slog.Logger, svc *plan.Service) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
week, ok := weekStartOrFail(w, r, logger) week, ok := weekStartOrFail(w, r, logger)
@@ -105,8 +106,9 @@ func weekStartOrFail(w http.ResponseWriter, r *http.Request, logger *slog.Logger
// writePlanError maps a plan.Service error to its API error: a week with // writePlanError maps a plan.Service error to its API error: a week with
// no plan or an unknown dinner is 404, a date outside the week is 400, and // no plan or an unknown dinner is 404, a date outside the week is 400, and
// drafting over a synced plan without confirm, or planning a dinner that // drafting over a synced plan without confirm, a confirmed draft while an
// is neither a Regular nor in the Quest log, is 409. // unconfirmed one runs, or planning a dinner that is neither a Regular nor
// in the Quest log, is 409.
func writePlanError(w http.ResponseWriter, r *http.Request, logger *slog.Logger, action string, err error) { func writePlanError(w http.ResponseWriter, r *http.Request, logger *slog.Logger, action string, err error) {
var day *plan.InvalidDayError var day *plan.InvalidDayError
var week *plan.InvalidWeekError var week *plan.InvalidWeekError
@@ -120,6 +122,8 @@ func writePlanError(w http.ResponseWriter, r *http.Request, logger *slog.Logger,
writeError(w, r, logger, http.StatusConflict, "dinner_not_plannable", notPlannable.Error()) writeError(w, r, logger, http.StatusConflict, "dinner_not_plannable", notPlannable.Error())
case errors.Is(err, plan.ErrConfirmRequired): case errors.Is(err, plan.ErrConfirmRequired):
writeError(w, r, logger, http.StatusConflict, "confirm_required", err.Error()) writeError(w, r, logger, http.StatusConflict, "confirm_required", err.Error())
case errors.Is(err, plan.ErrDraftRunning):
writeError(w, r, logger, http.StatusConflict, "draft_running", err.Error())
case errors.Is(err, plan.ErrNotFound): case errors.Is(err, plan.ErrNotFound):
writeError(w, r, logger, http.StatusNotFound, "not_found", "plan not found") writeError(w, r, logger, http.StatusNotFound, "not_found", "plan not found")
case errors.Is(err, plan.ErrDinnerNotFound): case errors.Is(err, plan.ErrDinnerNotFound):
+10 -8
View File
@@ -184,16 +184,18 @@ func TestPostPlanDraft(t *testing.T) {
t.Errorf("POST draft of a new week = %d %v, want 202 with its own job", rec.Code, body) t.Errorf("POST draft of a new week = %d %v, want 202 with its own job", rec.Code, body)
} }
// A confirmed draft does not join the unconfirmed one, but a second // A confirmed draft does not join the unconfirmed one and enqueues
// confirmed draft joins the first. // nothing.
rec, body = callDinners(t, e.h, http.MethodPost, plansPath+"2026-10-12/draft", `{"confirm":true}`) rec, body = callDinners(t, e.h, http.MethodPost, plansPath+"2026-10-12/draft", `{"confirm":true}`)
confirmedID, _ := body["jobId"].(string) if rec.Code != http.StatusConflict || errorCode(body) != "draft_running" {
if rec.Code != http.StatusAccepted || confirmedID == "" || confirmedID == jobID { t.Errorf("confirmed POST draft = %d %v, want 409 draft_running", rec.Code, body)
t.Errorf("confirmed POST draft = %d %v, want 202 with its own job", rec.Code, body)
} }
rec, body = callDinners(t, e.h, http.MethodPost, plansPath+"2026-10-12/draft", `{"confirm":true}`) var queued int
if rec.Code != http.StatusAccepted || body["jobId"] != confirmedID { if err := e.st.Reader.QueryRowContext(t.Context(), `SELECT COUNT(*) FROM jobs WHERE kind = ?`, plan.KindDraftWeek).Scan(&queued); err != nil {
t.Errorf("second confirmed POST draft = %d %v, want 202 with job %s", rec.Code, body, confirmedID) t.Fatal(err)
}
if queued != 2 {
t.Errorf("%d draft jobs, want one per week", queued)
} }
rec, body = callDinners(t, e.h, http.MethodPost, plansPath+"2026-10-12/draft", `{"confirm":"yes"}`) rec, body = callDinners(t, e.h, http.MethodPost, plansPath+"2026-10-12/draft", `{"confirm":"yes"}`)
+7 -6
View File
@@ -72,14 +72,15 @@ JOIN week_plans ON week_plans.id = plan_days.plan_id
WHERE week_plans.week_start = ? AND plan_days.dinner_id IS NOT NULL WHERE week_plans.week_start = ? AND plan_days.dinner_id IS NOT NULL
ORDER BY plan_days.date; ORDER BY plan_days.date;
-- name: GetActiveDraftJobID :one -- name: GetActiveDraftJob :one
-- The id of the job of kind queued or running for the week starting -- The job of kind queued or running for the week starting week_start,
-- week_start, one whose payload has confirm when confirmed is 1. Returns -- with whether its payload has confirm. StartDraft enqueues a draft only
-- sql.ErrNoRows when there is none. -- when there is none, so there is at most one. Returns sql.ErrNoRows when
SELECT id FROM jobs -- there is none.
SELECT id, CAST(json_extract(payload, '$.confirm') = 1 AS INTEGER) AS confirmed
FROM jobs
WHERE kind = sqlc.arg(kind) WHERE kind = sqlc.arg(kind)
AND status IN ('queued', 'running') AND status IN ('queued', 'running')
AND json_extract(payload, '$.week_start') = sqlc.arg(week_start) AND json_extract(payload, '$.week_start') = sqlc.arg(week_start)
AND (CAST(sqlc.arg(confirmed) AS INTEGER) = 0 OR json_extract(payload, '$.confirm') = 1)
ORDER BY id ORDER BY id
LIMIT 1; LIMIT 1;