5.8 KiB
Mirrulations Mirror Backfill Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Replace the 7-day regulations.gov API crawl with a bulk pull from the Mirrulations public S3 mirror (AWS Open Data) — hours instead of days, zero API-key pressure (refs #662).
Architecture: s3://mirrulations (public, HTTPS-listable, no auth) mirrors every docket: raw-data/CMS/{D}/text-{D}/comments/{cid}.json is byte-compatible with the v4 /comments/{id} detail response ({"data": {...}}, no included block), and raw-data/CMS/{D}/binary-{D}/comments_attachments/{cid}_attachment_N.{ext} holds the attachment binaries. The mirror is a superset of our farm (15,368 vs 13,245 for CMS-2018-0076; 3,433 vs 2,728 for the live docket). A new backfill_from_mirror in bib/regulations_gov.py reuses _parse_comment + upsert_comment (creation of mirror-only comments) and the enrichment steps of backfill_details (abstract, org tag, attach_file, enriched:ok), with S3 I/O fanned out over a thread pool and ALL sqlite work on the main thread (same pattern as prisma/fetch.py). VMs are unnecessary: the constraint was the per-key API cap, which the mirror sidesteps entirely; the reg.gov API remains only for freshness gap-fill (#615).
Tech Stack: httpx (S3 REST list + GET), concurrent.futures.ThreadPoolExecutor, existing bib.store.Store / Source / Tag machinery; pytest + httpx.MockTransport.
Spec: Issue #662 plus this plan.
Global Constraints
- No git stash; explicit-path staging;
git status --shortpre-commit; ruff format+check first; commits in background (hook runs mapped tests). - No Claude co-author trailer.
- All
Store/sqlite calls on the main thread; worker threads do network + file I/O only. - Kill the running API backfill loop BEFORE launching the mirror run (two enrichers racing the same docket would double-attach).
- Local attachment naming must match the existing layout:
.state/comments/{D}/{cid}/attachment_N.{ext}(strip the{cid}_prefix from mirror basenames). - Mirror-only comments get the same tag set as farmed ones:
source:regulations-gov,doctype:comment,reg-docket:{D},rule:{cms_id}(derived from an existing item of the docket),year:{posted[:4]}, plusorg:when present.
Task 1: Mirror client + backfill_from_mirror
Files:
- Modify:
src/bib/regulations_gov.py - Modify:
src/cli/bib.py(add--mirrortobackfill-comments) - Test:
tests/bib/test_regulations_gov_mirror.py(new)
Interfaces:
-
Consumes:
_parse_comment(row) -> Comment,upsert_comment(store, comment, cms_id=, extra_tags=),Store.add_tag/attach_file/update,_slug,_docket_from_comment_id. -
Produces:
class Mirror—list_keys(prefix) -> list[str](paged, continuation-token),comment_ids(docket) -> list[str],attachment_keys(docket) -> dict[str, list[str]],get_json(key) -> dict,download(key, dest) -> Path | None. Basehttps://mirrulations.s3.amazonaws.com; constructor takes optionalhttpx.Clientfor testing.backfill_from_mirror(store, mirror, *, docket, limit=None, log_path=None, commit_every=200, scratch_root=Path(".state/comments"), workers=12) -> dict[str, int]with stats keys{"created", "enriched", "attached", "errors", "seen"}.- CLI:
stack bib backfill-comments --docket D --mirrorroutes to the mirror path (docket required with --mirror).
-
Step 1: Write the failing tests (
tests/bib/test_regulations_gov_mirror.py) — a MockTransport that serves: a paged list response (two pages viaNextContinuationToken), comment JSONs for one existing-stub cid and one mirror-only cid, one attachment binary (%PDF...). Assert: paging concatenates keys;backfill_from_mirroron a tmpStore(seed one stub item withsource:regulations-gov+reg-docket:tags and the comment URL) → stub gets abstract +enriched:ok, mirror-only cid gets created with the full tag set, attachment lands atscratch/{D}/{cid}/attachment_1.pdfand is registered viaattach_file, stats match. -
Step 2: Run to verify failure —
uv run pytest tests/bib/test_regulations_gov_mirror.py -q→ ImportError. -
Step 3: Implement
Mirror+backfill_from_mirrorper the interfaces; thread pool fetches(cid) -> (json_dict, [downloaded attachment paths])into scratch; main thread applies DB effects per completed future;rule:cms_id looked up once per docket from any existing item'srule:tag (empty string when the docket has none). Wire the CLI flag. -
Step 4: Tests green — the new file plus
uv run pytest tests/bib -q(background; large). -
Step 5: Live smoke —
uv run stack bib backfill-comments --docket CMS-2017-0092 --mirror --limit 25(smallest docket; verify enriched counts + attachment files). -
Step 6: Ruff, status check, commit —
feat(bib): backfill-comments --mirror — bulk enrichment from the Mirrulations S3 mirror (refs #662).
Task 2: Swap the running crawl over to the mirror
- Step 1: Kill the API loop (
pkill -f backfill-comments— anchored; also the wrapping bash pid) after noting its progress. - Step 2: Launch the mirror loop (same docket order + extract + sweep chain,
.envsourced), all ten dockets including CMS-2017-0092 and CMS-2026-2377 (mirror is fresher than our farm even there). - Step 3: Verify first-docket throughput; estimate completion (expect hours).
- Step 4: Comment on #662 (mirror route live, API loop retired to gap-fill duty); leave open until the run + extraction + sweep complete.
Task 3: Close out
- Update memory (
palliative_rfi_project.md+ the answer to "many VMs?": per-key limit, mirror route).