4.4 KiB
4.4 KiB
P5a task 07: the PDF document cache, pages, grep, and the question limit
Branch: p5a (git switch p5a; git status --short must be empty, otherwise stop)
Commit subject: api: PDF cache with revalidation; pages and grep; refuse questions on very long documents
Goal
Task 06 serves PDFs but fetches and extracts every time. After this task, a PDF already
extracted is answered from the cache: fresh for 24 h, then revalidated with a conditional GET.
An agent can ask for pages or grep. A question on a document too long for the digest is
refused with a pointer to those two.
Files
- Copy: nothing (the given
internal/api/pdf_test.gocame in task 06) - Modify:
internal/api/pdf.go(all new logic),internal/api/api.go(only the call in rule 1 and the two request fields' use in rule 3),docs/implementer-log.md
What already exists
- Task 06's
extractPDF,pdfError,renderPDF, theServerfields (PDFTTL,PDFMaxQuestionChunks), thev1Reqfields (Pages,Grep), andDefaultPDFTTL/DefaultPDFMaxQuestionChunks. internal/dcache:A niltype PDFEntry struct{ ContentHash, ETag, LastModified string; FetchedAt time.Time; Meta []byte; Pages []string } func PDFKey(url, callerClass string) string func (d *DB) PutPDF(key string, e PDFEntry) func (d *DB) GetPDF(key string) (PDFEntry, bool) func (d *DB) TouchPDF(key string, fetchedAt time.Time)*DBis a no-op.internal/fetch:func (f *Fetcher) FetchIf(ctx, rawURL, etag, lastModified string) (*Resource, error). A 304 returnsStatus 304, an empty body and a nil error.internal/digest:Split(text string, maxTok int) []stringandChunkTokens.
Rules
- Cache first, in
doFetch, beforeacquireands.Fetch.Fetch: calls.pdfFromCache(ctx, c, rawURL) (res *fetch.Resource, doc *pdftext.Doc, ok bool, err error). The key isdcache.PDFKey(rawURL, s.callerClass(c)); the cache iss.cache()(may be nil).- Miss:
ok == false; go on as today. - Fresh (
time.Since(e.FetchedAt) < ttl, wherettliss.PDFTTLorDefaultPDFTTL): build the doc from the entry (unmarshalMetaintodoc.Meta;doc.Text = e.Pages), and a syntheticres:URLandFinalURL= rawURL,Status200,ContentType "application/pdf",FetchedAt=e.FetchedAt,Hash=e.ContentHash, no body. No network call and no child. - Stale:
s.Fetch.FetchIf(ctx, rawURL, e.ETag, e.LastModified).- 304 →
TouchPDF(key, now), then answer as fresh. - 200 → return that response with
ok == false, sodoFetchgoes on without a second GET: the new document is extracted and stored. - An error → return it.
- 304 →
- The source in the
/v1/fetchenvelope says"cached": truefor an answer from the cache (fresh or after a 304).
- Miss:
- Store after extraction: after
extractPDFsucceeds,PutPDFunder the URL key with:ContentHash: res.SHA256();ETagandLast-Modifiedfrom the response headers;FetchedAt: now;Meta: the JSON ofdoc.Meta;Pages: doc.Text.
pagesandgrep(/v1/fetch):- Pass
req.Pagesandreq.Grepintopdfdoc.OptionsinrenderPDF. pdfdoc.ErrBadPagesorpdfdoc.ErrBadGrep(useerrors.Is) → 400, with the error's own text as the message.pagesorgrepon a non-PDF →400 pages and grep apply to PDFs only.
- Pass
- Question limit: in mode
digestwith a doc, computen := len(digest.Split(pdfdoc.Full(doc), digest.ChunkTokens)). Ifnexceedss.PDFMaxQuestionChunks(or its default), answer422 document too long for question (<n> chunks); use grep or pagesbefore any digest call.
Steps
- 1.
git switch p5a. 2. See the three tests fail:go test -count=1 -run 'TestPDFPagesAndGrep|TestPDFCache|TestPDFQuestionTooLong' ./internal/api/. - 3.
pdfFromCacheand the store (rules 1–2), 4. pages and grep, 5. the question limit. - 6.
gofmt -w;go test -race -count=3 ./internal/api/→ok(all ten PDF tests and every existing test). - 7.
make gate→gate: ok. 8. Rowp5a/07-api-cache-pages; commit.
git add internal/api docs/implementer-log.md
git commit -F .state/commit-msg.txt
Done when
go test -race -count=3 ./internal/api/is ok;make gateok; the given file is still byte-identical.