Fail interrupted groundings at boot and bound hand grounding
CI / ci (push) Successful in 2m25s
CI / ci (pull_request) Successful in 2m20s

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>
This commit is contained in:
2026-10-06 16:30:53 -04:00
co-authored by Claude Opus 5.5
parent d6d82e8368
commit f4bed5d82d
13 changed files with 357 additions and 37 deletions
+10 -2
View File
@@ -62,12 +62,20 @@ func (s *Search) Queries() []string {
}
// Pages is a recipes.PageFetcher serving Bodies by URL; any other URL is a
// 404.
// 404. Gate, when set, makes every fetch wait for it to close.
type Pages struct {
Bodies map[string][]byte
Gate chan struct{}
}
func (p *Pages) Fetch(_ context.Context, rawURL string) (recipes.Page, error) {
func (p *Pages) Fetch(ctx context.Context, rawURL string) (recipes.Page, error) {
if p.Gate != nil {
select {
case <-p.Gate:
case <-ctx.Done():
return recipes.Page{}, ctx.Err()
}
}
body, ok := p.Bodies[rawURL]
if !ok {
return recipes.Page{}, &recipes.StatusError{URL: rawURL, Status: 404}