Washington Post source + catalog/store/archive lifecycle model - #3
Conversation
Claude Code keeps git worktrees and scratch/session state under .claude/; it must never be tracked in the repo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
Adversarial audit of the provider/catalog/database/archive seams. Four root causes (RC1, the split-ownership sources row, is central), what to preserve, a tiered course-correction, and a forward plan for user-defined sources so we don't paint ourselves into a corner. Diagnosis only; no structural changes made. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
A poll where every probe returned a non-2xx/304/404 status (e.g. a total 5xx outage) was reported as a healthy no-op, because only transport errors incremented the failure gate. Count unexpected statuses too, so a sustained outage surfaces in the health record instead of looking like a run of clean polls. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
Broadsheet's first non-freedomforum provider. WaPo publishes its print front page as per-page PDFs on an open CloudFront CDN, keyed by full date with a zone code (SU/RE) that rotates day to day. The provider probes both zones across a UTC day window, treats 403 (S3 AccessDenied) as absent, and takes the edition date from the URL folder. The 'today's paper' HTML page is Akamai bot-protected, so the provider talks only to the CDN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
Repoints The Washington Post from the broken freedomforum entry (DC_WP 404s on freedomforum's CDN) to the new washingtonpost provider. Also audited all freedomforum papers against two signals -- freedomforum's live sitemap inventory and the PDF CDN -- and removed the 80 gone from both (delisted, or no recent image + no PDF) plus one malformed entry (bra^pe-jdc). Kept the 11 that still have an active image feed but a dropped PDF. Catalog 606 -> 525. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
Two intertwined changes that meet at loadSources: - Reconcile the store to the catalog on every boot (store.SeedSources): upsert catalog-owned wiring while preserving the user's enabled toggle, and prune papers the catalog dropped. A catalog fix (repoint, rename, removal) now reaches existing installs, not just fresh ones. - Make the archive independent and self-describing: a paper dropped from the catalog keeps its history browsable and renderable (knownSource is archive-aware) until it ages out on retention. Each source dir carries a .meta.json sidecar stamped by the reconciler and read by the archive browser, so the archive is portable -- drop a <id>/ dir into any install and it shows up named. Labels are stamped before the seed prune so a paper dropped this release keeps its name. Sits on the RC1/RC2 turf flagged in docs/architecture-review.md: correct and preserve-listed, but the eventual source_state refactor will restructure this area. (Includes the architecture.md prose for both this model and the WaPo provider.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
📝 WalkthroughWalkthroughThe PR adds a Washington Post CDN provider, catalog-to-store reconciliation, archive display-name sidecars, archived-source addressability, and related tests and documentation. It also updates archive UI name fallback behavior and ignores Claude Code state. ChangesSource acquisition and archive flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Engine
participant Reconciler
participant WashingtonPost
participant CloudFrontCDN
participant Archive
Engine->>Reconciler: Reconcile configured source
Reconciler->>WashingtonPost: Poll candidate dates and zones
WashingtonPost->>CloudFrontCDN: Send conditional PDF requests
CloudFrontCDN-->>WashingtonPost: Return edition or probe status
WashingtonPost-->>Reconciler: Return editions and ETags
Reconciler->>Archive: Store editions and display name
Engine-->>Archive: Resolve archived source name
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR adds The Washington Post as a new provider/source, removes a large set of dead Freedom Forum catalog entries, and introduces a catalog↔store↔archive lifecycle model so catalog updates reconcile into existing installs while archived history remains browsable even after a paper is dropped from the catalog.
Changes:
- Add a
washingtonpostprovider with conditional polling against WaPo’s CloudFront CDN and registry/catalog wiring. - Reconcile the SQLite
sourcestable to the embedded catalog on every boot (upsert wiring + prune dropped papers while preserving userenabled). - Make the archive self-describing via per-source
.meta.jsonlabels and expose archive names to the UI for dropped papers.
Reviewed changes
Copilot reviewed 18 out of 19 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/broadsheet/rotation_test.go | Updates test fixture helper usage after uniformPNG signature change. |
| pkg/broadsheet/broadsheet.go | Builds archive earlier, stamps archive labels on startup, expands addressability to archived-only ids, and exposes ArchiveName. |
| pkg/broadsheet/broadsheet_test.go | Adds tests for archived history outliving catalog membership and startup label backfill/ordering. |
| internal/store/store.go | Changes SeedSources to a full reconcile (upsert wiring + prune dropped sources + dependent state). |
| internal/store/store_test.go | Adds coverage for reconcile semantics (preserve user enabled, prune dropped papers, empty seed no-op). |
| internal/registry/registry.go | Registers and decodes the new washingtonpost provider type. |
| internal/registry/registry_test.go | Adds decode tests for Washington Post configs (nil/empty/override/malformed). |
| internal/reconcile/reconcile.go | Stamps archive label on successful archive write. |
| internal/reconcile/reconcile_test.go | Ensures archive label stamping is exercised in reconcile tests. |
| internal/provider/washingtonpost/washingtonpost.go | Implements WaPo provider polling and PDF sniffing with conditional GET + multi-zone probing. |
| internal/provider/washingtonpost/washingtonpost_test.go | Adds thorough unit tests for WaPo polling behavior (cold/warm/403/5xx/non-PDF). |
| internal/provider/freedomforum/freedomforum.go | Treats unexpected upstream HTTP statuses as failed probes (counts toward failure gating). |
| internal/catalog/catalog.json | Repoints dc-wp to washingtonpost and removes dead/malformed Freedom Forum entries. |
| internal/archive/archive.go | Adds .meta.json metadata sidecar with SetName/Name and reclaims emptied source dirs during prune. |
| internal/archive/archive_test.go | Tests archive label behavior and source-dir reclamation on prune. |
| docs/architecture.md | Documents reconcile semantics and archive self-description/portability; describes WaPo provider. |
| docs/architecture-review.md | Adds the recorded adversarial architecture boundary review (diagnosis only). |
| cmd/broadsheet-server/ui.go | Uses ArchiveName fallback to label dropped papers in the archive UI. |
| .gitignore | Ignores .claude/ tooling directory. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Reclaim a source directory once no editions remain — e.g. a paper the | ||
| // catalog dropped, whose editions have all aged out. RemoveAll also clears | ||
| // the display-name label and any write litter; an active source that Puts | ||
| // again just re-creates the directory (and re-writes its label). | ||
| if len(s.list(d.Name())) == 0 { | ||
| _ = os.RemoveAll(filepath.Join(s.Root, d.Name())) | ||
| } |
| // stampArchiveLabels writes each source's display name into its archive metadata | ||
| // so history stays labeled — and the archive stays self-describing and portable — | ||
| // after a paper leaves the catalog and its store row is pruned. It only touches | ||
| // ids that already have an archive directory (SetName is a no-op for a blank name | ||
| // and rewrites only when the name changed), so it never creates directories and | ||
| // never clobbers a transplanted archive that already carries its own label. |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
internal/store/store_test.go (1)
103-150: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winExercise
crop_overridespruning too.The test claims dependent state is removed, but only verifies versions and fetch events. Add a crop override for
cand assert it is deleted so every new cleanup statement is covered.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/store/store_test.go` around lines 103 - 150, Extend TestSeedSources_PrunesDroppedPapers by creating a crop override for source "c" before reseeding the reduced catalog, then query the relevant crop-override state afterward and assert no override remains for "c". Keep the existing versions and health-history assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture.md`:
- Around line 182-200: Update the Washington Post provider documentation to
state that missing CDN objects typically return 403, but CloudFront may also
return 404. Replace the absolute “not a 404” wording in the “Adding a provider”
section while preserving the surrounding explanation of provider-specific
handling.
In `@pkg/broadsheet/broadsheet.go`:
- Line 319: Update stampArchiveLabels to propagate failures from SetName and
ListSources instead of discarding them, and handle that error in SeedSources by
aborting startup before source reconciliation or deletion runs. Preserve normal
archive-label backfilling and pruning only after all label operations complete
successfully.
---
Nitpick comments:
In `@internal/store/store_test.go`:
- Around line 103-150: Extend TestSeedSources_PrunesDroppedPapers by creating a
crop override for source "c" before reseeding the reduced catalog, then query
the relevant crop-override state afterward and assert no override remains for
"c". Keep the existing versions and health-history assertions unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3eda96c8-a322-42bc-9ab2-5235c907c406
📒 Files selected for processing (19)
.gitignorecmd/broadsheet-server/ui.godocs/architecture-review.mddocs/architecture.mdinternal/archive/archive.gointernal/archive/archive_test.gointernal/catalog/catalog.jsoninternal/provider/freedomforum/freedomforum.gointernal/provider/washingtonpost/washingtonpost.gointernal/provider/washingtonpost/washingtonpost_test.gointernal/reconcile/reconcile.gointernal/reconcile/reconcile_test.gointernal/registry/registry.gointernal/registry/registry_test.gointernal/store/store.gointernal/store/store_test.gopkg/broadsheet/broadsheet.gopkg/broadsheet/broadsheet_test.gopkg/broadsheet/rotation_test.go
- store/broadsheet: propagate stampArchiveLabels + ListSources errors and SKIP the catalog prune when a label write fails, so a dropped paper never loses its archived name to a failed stamp (CodeRabbit, major). SeedSources gains a prune flag. - archive.Prune: guard directory reclamation behind an error-checked ReadDir; list() returns nil on any read error, which could mistake a transient failure for empty and RemoveAll a populated dir (Copilot). - docs: soften the WaPo 403-vs-404 wording; the provider handles both (CodeRabbit). - broadsheet: correct the stampArchiveLabels doc -- a transplanted label is only left untouched for ids absent from the names map (Copilot). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM
…/wapo-and-lifecycle # Conflicts: # pkg/broadsheet/broadsheet_test.go
What
Adds The Washington Post as a source (Broadsheet's first non-freedomforum provider), cleans up dead freedomforum papers, and introduces the catalog↔store↔archive lifecycle model so catalog changes reach existing installs and a dropped paper's history stays intact. Also records an adversarial architecture boundary review (diagnosis only).
The branch is a deliberately-ordered stack — the bottom commits are solid and independent; the lifecycle-model commit is kept isolated because the review flags that area for a future refactor (see below).
Commits
chore: ignore the .claude tooling directorydocs: record the architecture boundary reviewfix(freedomforum): count an unexpected upstream status as a failed probefeat(provider): add Washington Post sourcechore(catalog): repoint dc-wp to washingtonpost; drop 81 dead papersfeat: catalog↔store↔archive lifecycle modelfix(review): address Copilot + CodeRabbit review findingsHighlights
Washington Post provider. WaPo publishes its print front page as per-page PDFs on an open CloudFront CDN, keyed by full date with a zone code (
SU/RE) that rotates day to day. The provider probes both zones across a UTC day window, treats403(S3AccessDenied) as absent, and takes the edition date from the URL folder. The "today's paper" HTML page is Akamai bot-protected, so the provider talks only to the CDN.dc-wpis repointed from the broken freedomforum entry (DC_WP404s on freedomforum's CDN).Dead-paper cleanup. Audited all freedomforum papers against two signals — freedomforum's live sitemap inventory and the PDF CDN — and removed the 80 gone from both, plus one malformed entry (
bra^pe-jdc). Kept the 11 that still have an active image feed but a dropped PDF. Catalog 606 → 525.Lifecycle model. Two intertwined changes that meet at
loadSources. First, reconcile the store to the catalog on every boot (store.SeedSources): upsert catalog-owned wiring while preserving the user'senabledtoggle, and prune papers the catalog dropped — so a catalog fix (repoint, rename, removal) reaches existing installs, not just fresh ones. Second, make the archive independent and self-describing: a paper dropped from the catalog keeps its history browsable and renderable (knownSourceis archive-aware) until it ages out on retention, and each source dir carries a.meta.jsonsidecar (stamped by the reconciler, read by the archive browser) so the archive is portable — drop a<id>/dir into any install and it shows up named. Labels are stamped before the prune so a paper dropped this release keeps its name; if a label write fails, the prune is skipped that boot so no name is lost.Architecture review
docs/architecture-review.mdrecords an adversarial audit of the provider/catalog/database/archive seams: four root causes (RC1, the split-ownershipsourcesrow, is central), what to preserve, a tiered course-correction, and a forward plan for user-defined sources. Diagnosis only — no structural changes here. The lifecycle commit is correct and preserve-listed, but the eventualsource_staterefactor will restructure that area; it's isolated so that rework rebases cleanly.Testing
go build ./...,go vet ./...,go test ./..., andgolangci-lint run ./...are all clean at the tip. Every commit compiles (bisectable history). The WaPo provider was verified live end-to-end (fetched and rendered a real front page); the store-reconcile and archive-portability paths are covered by new unit tests plus an end-to-end upgrade-migration check. Copilot and CodeRabbit review findings have been addressed.🤖 Generated with Claude Code
https://claude.ai/code/session_01Wd6XKSHR9dJpr8hAZjAkcM