Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5 (Feature 12 - Regular Dinners and Onboarding).
Done-condition
Creating a Regular enqueues a grounding job that runs the Feature 10 pipeline and sets grounding_status grounded (recipe, photo, method, minutes, difficulty attached) or failed with a reason; creation by recipe_url grounds that URL directly. GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 candidates {candidate_id, title, site_name, photo_url, minutes}; POST /api/v1/dinners/{id}/ground accepts {candidate_id} | {recipe_url} | {manual: {ingredients[], steps[], minutes, method}} and sets grounded or manual accordingly. Tests with fake search/fetch/LLM cover successful background grounding, failure -> failed, fixing via candidate, via pasted URL and via manual entry, and that POST /dinners returns before grounding completes. Go quality floor passes.
Evidence
New async grounding job on dinner create (POST returns before grounding), direct GroundURL for recipe_url creates, recipe-candidates (up to 3, ranked) and the ground endpoint (candidate/URL/manual). New migration 0009; sqlc regenerated; the store's pinned migration list updated.
gofmt/vet/build/go test -race pass; make sqlc-check no drift.
Tests cover successful background grounding, failure -> failed, fix by candidate/URL/manual, a manual fix kept while a job is running, candidate capping/ranking, and validation/404/422.
Self-review fixes: 422 responses no longer echo internal/provider error text (fixed message + test); the background job logs only the reason code. Known small gap: if enqueuing the job fails after the dinner is saved, the dinner is marked failed but the API returns 500 without the dinner id.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5 (Feature 12 - Regular Dinners and Onboarding).
## Done-condition
Creating a Regular enqueues a grounding job that runs the Feature 10 pipeline and sets grounding_status grounded (recipe, photo, method, minutes, difficulty attached) or failed with a reason; creation by recipe_url grounds that URL directly. GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 candidates {candidate_id, title, site_name, photo_url, minutes}; POST /api/v1/dinners/{id}/ground accepts {candidate_id} | {recipe_url} | {manual: {ingredients[], steps[], minutes, method}} and sets grounded or manual accordingly. Tests with fake search/fetch/LLM cover successful background grounding, failure -> failed, fixing via candidate, via pasted URL and via manual entry, and that POST /dinners returns before grounding completes. Go quality floor passes.
## Evidence
- New async grounding job on dinner create (POST returns before grounding), direct GroundURL for recipe_url creates, recipe-candidates (up to 3, ranked) and the ground endpoint (candidate/URL/manual). New migration 0009; sqlc regenerated; the store's pinned migration list updated.
- gofmt/vet/build/go test -race pass; make sqlc-check no drift.
- Tests cover successful background grounding, failure -> failed, fix by candidate/URL/manual, a manual fix kept while a job is running, candidate capping/ranking, and validation/404/422.
Self-review fixes: 422 responses no longer echo internal/provider error text (fixed message + test); the background job logs only the reason code. Known small gap: if enqueuing the job fails after the dinner is saved, the dinner is marked failed but the API returns 500 without the dinner id.
Creating a Regular now enqueues a ground_dinner job and returns at once
with its grounding_job_id. The job grounds the dinner's recipe_url with
GroundURL, else its name with Ground, and sets grounding_status grounded
(recipe, photo, method, minutes, difficulty) or failed with a
grounding_error reason code (migration 0009). It writes only a dinner
still pending, so a fix made meanwhile wins.
GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked
candidates {candidate_id, title, site_name, photo_url, minutes} without
downloading photos or running the model. POST /api/v1/dinners/{id}/ground
takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and
sets grounded or manual.
PhotoCache takes an ImageFetcher interface and recipestest provides fakes
so other packages can test grounding with fake search, fetch and model.
main wires the Grounder with normalize_recipe over the AI settings.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Creating a Regular now enqueues a ground_dinner job and returns at once
with its grounding_job_id. The job grounds the dinner's recipe_url with
GroundURL, else its name with Ground, and sets grounding_status grounded
(recipe, photo, method, minutes, difficulty) or failed with a
grounding_error reason code (migration 0009). It writes only a dinner
still pending, so a fix made meanwhile wins.
GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked
candidates {candidate_id, title, site_name, photo_url, minutes} without
downloading photos or running the model. POST /api/v1/dinners/{id}/ground
takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and
sets grounded or manual.
PhotoCache takes an ImageFetcher interface and recipestest provides fakes
so other packages can test grounding with fake search, fetch and model.
main wires the Grounder with normalize_recipe over the AI settings.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
At boot, after the job runner fails the jobs a previous process left
running, FailInterruptedGroundings sets every dinner still pending with
no ground_dinner job queued or running to failed (interrupted). A crash
mid-job, or between saving a dinner and enqueuing its job, no longer
leaves the dinner pending forever.
A search failure on recipe-candidates or ground now logs only the reason
code search_failed, never the wrapped error, which may quote the
provider; the response stays redacted.
When the grounding job cannot be enqueued, POST /dinners answers 201
with the saved dinner failed (grounding_error) and grounding_job_id
null, instead of a 500 that hid the dinner it had created.
Grounding a candidate or link by hand runs under a 90-second deadline
(fetch, photo, normalize_recipe); past it the request answers 504
ground_timeout and the dinner is unchanged.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5 (Feature 12 - Regular Dinners and Onboarding).
Done-condition
Creating a Regular enqueues a grounding job that runs the Feature 10 pipeline and sets grounding_status grounded (recipe, photo, method, minutes, difficulty attached) or failed with a reason; creation by recipe_url grounds that URL directly. GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 candidates {candidate_id, title, site_name, photo_url, minutes}; POST /api/v1/dinners/{id}/ground accepts {candidate_id} | {recipe_url} | {manual: {ingredients[], steps[], minutes, method}} and sets grounded or manual accordingly. Tests with fake search/fetch/LLM cover successful background grounding, failure -> failed, fixing via candidate, via pasted URL and via manual entry, and that POST /dinners returns before grounding completes. Go quality floor passes.
Evidence
Self-review fixes: 422 responses no longer echo internal/provider error text (fixed message + test); the background job logs only the reason code. Known small gap: if enqueuing the job fails after the dinner is saved, the dinner is marked failed but the API returns 500 without the dinner id.
Creating a Regular now enqueues a ground_dinner job and returns at once with its grounding_job_id. The job grounds the dinner's recipe_url with GroundURL, else its name with Ground, and sets grounding_status grounded (recipe, photo, method, minutes, difficulty) or failed with a grounding_error reason code (migration 0009). It writes only a dinner still pending, so a fix made meanwhile wins. GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked candidates {candidate_id, title, site_name, photo_url, minutes} without downloading photos or running the model. POST /api/v1/dinners/{id}/ground takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and sets grounded or manual. PhotoCache takes an ImageFetcher interface and recipestest provides fakes so other packages can test grounding with fake search, fetch and model. main wires the Grounder with normalize_recipe over the AI settings. Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Creating a Regular now enqueues a ground_dinner job and returns at once with its grounding_job_id. The job grounds the dinner's recipe_url with GroundURL, else its name with Ground, and sets grounding_status grounded (recipe, photo, method, minutes, difficulty) or failed with a grounding_error reason code (migration 0009). It writes only a dinner still pending, so a fix made meanwhile wins. GET /api/v1/dinners/{id}/recipe-candidates returns up to 3 ranked candidates {candidate_id, title, site_name, photo_url, minutes} without downloading photos or running the model. POST /api/v1/dinners/{id}/ground takes exactly one of {candidate_id}, {recipe_url} or {manual: {...}} and sets grounded or manual. PhotoCache takes an ImageFetcher interface and recipestest provides fakes so other packages can test grounding with fake search, fetch and model. main wires the Grounder with normalize_recipe over the AI settings. Maestro task: 0cbaa5bf-8be4-4cbd-aab2-891e7f1bc2b5 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Independent Codex gpt-6-sol review: CHANGES_REQUIRED for this exact commit.
Independent Codex gpt-6-sol review: APPROVED for this exact commit.