Kyle: "Fold them into the plan." What changed and why is in docs/P6.md §14. In short:
- FetchFunc gets the *auth.Caller (the given api test could not compile, and a fetch with no
caller would bypass per-caller gates, the pool limit, the cache class and redaction);
- store reads never bump the lease (task 01's rule contradicted its own test); the API Touches;
- 1-2 workers round-robin over live jobs, so a batch holds at most 2 of the 8 global slots;
busy targets retry in the runner (bounded); batch PDFs use their own pool name; 210 s deadline;
- the api tests no longer touch the network or race the cancel; private names via fake DNS;
- failed/cancelled jobs are not reused; GET is a summary plus GET …/{idx} and ?results=1;
- the 256 KiB body cap moves into ServeHTTP; a fixed metrics label for /v1/batch/{id};
- guard.PreCheck (given verbatim) replaces guard.ParseURL at submit;
- 5 tasks became 8 (refactor, wiring, endpoints and serve/config split), each restating the
APIs it uses; every given test compiles against a panic-only skeleton and fails on it.
P6 starts after P5c: task 04 moves v1fetch, which P5b changes. Recheck the api given tests
against post-P5c master before the run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4.1 KiB
P6 task 04: extract v1fetch's pipeline into fetchTarget
Branch: p6 (git switch p6; git status --short must be empty, otherwise stop)
Commit subject: api: v1fetch's per-target pipeline becomes fetchTarget (no behaviour change)
This is the one task in P6 that edits working code. It is a move, not a rewrite: the reviewer will diff the old
v1fetchbody againstfetchTargetline by line.
Goal
v1fetch today inlines the whole per-target pipeline: fetch → recover → PDF render or clean →
question limit → digest → context → log line → envelope. The batch worker (task 05) needs the
same pipeline for one target. Move it into one method that both call:
// internal/api/target.go
func (s *Server) fetchTarget(ctx context.Context, c *auth.Caller, t batch.Target) (batch.Result, error)
v1fetch keeps its exact responses. Every existing api test must pass unchanged.
Files
- Copy:
_files/api/fetchtarget_test.go→internal/api/fetchtarget_test.go - Create:
internal/api/target.go(fetchTarget) - Modify:
internal/api/api.go(v1fetchshrinks;doFetchtakes acontext.Contextin place of the*http.Request, and its call sites passr.Context()),docs/implementer-log.md
What already exists
// internal/batch (task 02)
type Options struct{ Mode, Question, Pages, Grep, JQ string; Context *bool; Recover bool }
type Target struct{ URL string; Options Options }
type Result struct{ Content string; Meta map[string]any }
fetch.Error{Status int; Msg, Hint string; RetryAfter int} is the error type writeFetchErr
already turns into a response.
Rules
- What stays in
v1fetch:s.reqN.Add(1), the caller check, decodingv1Req, and the400 invalid urlfor a bad body or an empty URL. Then it builds abatch.Targetfrom the request (URL, andMode,Question,Pages,Grep,JQ,Context,RecoverintoOptions), callss.fetchTarget(r.Context(), c, t), and:- on an error,
s.writeFetchErr(w, c, req.URL, err); - on success,
writeJSON(w, 200, env)whereenvisres.Metaplus"content": res.Content.
- on an error,
- What moves into
fetchTarget, in the same order and unchanged: the question truncation to 500 runes, the mode default and check,wantCtx,doFetch, the recover path (recover.Try(ctx, …)), the PDF branch (render,pages/greperrors, the question-chunk limit), the clean/raw branch, the digest, the context search, thefetchlog line, and the envelope. - Errors that
v1fetchwrote directly withwriteErrbecome*fetch.Errorvalues with the same status, message and hint:400 invalid urlfor a bad mode,400 raw mode does not apply to PDFs.writeFetchErrthen writes them exactly as before. The only visible difference is itsfetch failedlog line, which is fine. - The envelope:
fetchTargetreturnsResult{Content: content, Meta: m}wheremholds every other key of today'soutmap (mode,untrusted,source,warnings,sizes,timings, andpdf,digest,contextwhen present). Never putcontentinMeta. doFetch(ctx, c, rawURL, jq, onlyMain):r.Context()becomesctx. That is the only change indoFetch.scrape(the other caller) passesr.Context().- No new behaviour, no new log fields, no renamed JSON keys. If something cannot move without changing behaviour, stop and report it.
Steps
- 1.
git switch p6; copy the given test. 2. See it fail (fetchTargetundefined). - 3.
doFetch(ctx, …)and its call sites. 4.target.go. 5. Shrinkv1fetch. - 6.
gofmt -w;go test -race -count=3 ./internal/api/→ok, every existing test included. 7.make gate. - 8. Row
p6/04-fetchtarget; commit.
git add internal/api docs/implementer-log.md
git commit -F .state/commit-msg.txt
Done when
- The two given tests and every existing api test pass under
-race -count=3, with no existing test file modified;make gateok; the given file is byte-identical.
Stop and report if
- An existing api test fails and fixing it would need a change to that test.