From 41aa8fe39ecbd1b55449c480bc6bf827dae274a6 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 15 Jun 2026 10:20:03 -0400 Subject: [PATCH] docs(ingest): document the post-first migration contract at the native seam (#857) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Milestone #67 step 3. Spell out, at the IngestCore.run seam resolution, that post_record_key + write_post_record are the post-first contract a platform implements when migrating onto the native core ingester — the post-record owns the body/links, the per-media sidecar carries image identity only. The import side is already self-enforcing via uses_native_ingester → importer.post_first. Durable directive recorded as FC project rule #120. Co-Authored-By: Claude Opus 4.8 --- backend/app/services/ingest_core.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/backend/app/services/ingest_core.py b/backend/app/services/ingest_core.py index d8ee3b7..072a19c 100644 --- a/backend/app/services/ingest_core.py +++ b/backend/app/services/ingest_core.py @@ -141,9 +141,18 @@ class Ingester: # tick has no resumable backfill state. checkpoint = mode in ("backfill", "recovery", "recapture") ledger_key = self._ledger_key - # Optional seams (Patreon native ingester): capture pure-text posts that - # have NO downloadable media so the artist archive is complete. Absent on - # stub clients/downloaders (unit tests) → media-less posts skipped as before. + # POST-FIRST CONTRACT (milestone #67): these two optional seams make a + # platform "post-first" on the native core ingester — the post-record is + # the single authoritative writer of the post body/links/metadata, and the + # per-media sidecar carries image identity only (download_service flips + # importer.post_first via uses_native_ingester, so the import side follows + # automatically). A platform migrating off gallery-dl onto the native core + # adopts post-first by implementing BOTH: + # client.post_record_key(post) -> (ledger_key, post_id) | None (gate) + # downloader.write_post_record(post, artist_slug) -> PostRecordOutcome + # Absent on stub clients/downloaders (unit tests) and on not-yet-migrated + # platforms → media-less posts are skipped as before and the body still + # comes from the per-media sidecar (gallery-dl path). See [[post-first-ingest-contract]]. post_record_key = getattr(self.client, "post_record_key", None) write_post_record = getattr(self.downloader, "write_post_record", None) start = time.monotonic()