THE MONOLITH: one command that runs the whole pipeline end to end (#618) - #660
Merged
Conversation
…ster-vote propagation Neither piece is changed in behaviour; both are lifted from a caller-private scope (a BaseCommand method / a closure over six pieces of run_pilot state) to a module-level callable so a second engine can reach them without a copy. - stream_full_catalog._run_stage_zero_freshness -> module-level run_stage_zero_freshness, taking write/warn callables instead of closing over self.stdout/self.style. It now also RETURNS the bulk-file vintage (remote updated_at, cache path, cache mtime age, whether it refreshed, the import stats) so a caller can date its own conclusions; the existing method is a wrapper that discards it and prints exactly what it always printed. - local_identify_printing_tags.run_pilot's propagate_cluster_vote closure -> module-level build_propagated_cluster_votes, which returns the rows rather than writing them. The closure now calls it and keeps its own batching and written-id ledgers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
manage.py run_pipeline. A bare invocation is a complete, from-scratch, whole-catalogue
run that WRITES. Every flag either narrows the cohort, disables a stage, or withholds
the write; none is a precondition.
Stage 0 stream_full_catalog.run_stage_zero_freshness, and the bulk-file vintage it
returns is recorded on the run's ledger row so the run can be dated
Stage E operating_envelope.current_trip / check_envelope preflight
Stage C run_image_evidence_cohort via call_command (pooled engine, run-scoped resume,
md5 evidence transfer, RSS guard - none of it re-derived here)
Stage D stage_e_dispatch._run_stage_d, called explicitly rather than reached by the
post_save echo: join-key -> fallback -> illustration -> slow-path, then the
border / frame-style / bleed-edge chips
Stage C+ local_clustering.compute_two_threshold_clusters +
local_identify_printing_tags.build_propagated_cluster_votes - the pilot
capability no engine had
Stage E verify_zero_resolutions fidelity gate
end channel_report, reported and never folded into the exit status
_run_stage_d/_run_attribute_chip_casters/_run_illustration_calculator gain two
parameters so one Stage D serves both engines: batch_ids=None is bulk mode, and
dry_run is threaded through. Both defaults preserve existing behaviour exactly.
Write polarity is inverted relative to every organ called: all six Stage D
calculators/casters default to dry_run=True, so the monolith passes dry_run
explicitly at every seam. Inheriting those defaults would compute a whole pass and
persist nothing while every log line reported success.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
docs/identification-pipeline.md gains a 'How to run all of it' section at the front of the stage walkthrough - the sequence, that the command holds no pipeline logic of its own, and the two load-bearing defaults (writes by default, redoes everything from scratch). Stage 0's placement is noted where the reference set is introduced. docs/features/stage-e-operations.md gains the operational section: the write-polarity organ table (which organ, its own gate, what run_pipeline passes), how enforce_dry_run_precondition interacts with a write-by-default command, what a dry run actually does, Stage 0's returned bulk-file vintage, why Stage D is called explicitly rather than reached by the post_save echo, the two deliberate differences between the monolith's cluster propagation and the pilot's, exit codes, the ledger run_id suffix and why the DATA keeps the clean name, and the recorded list of ways a fresh run still inherits from an earlier one. No dated report. docs_lint.py --strict clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
The fidelity-gate and channel_report checks both passed with the stage unwired: 'violations == 0' is what you get whether the gate found nothing or was never called, and the CHANNEL REPORT banner is printed by run_pipeline itself either way. Now assert a string only the stage itself can emit - 'FIDELITY GATE: clear over N cards' and channel_report's own roster family title. Both mutations now go red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
…ests test_run_pipeline.py passed alone and halted every test at the envelope preflight inside the full suite: stage_e_dispatch._window is a process-global rolling window that _sample_envelope_signals reads, and four fetch failures recorded by test_stage_e_dispatch.py earlier in the same pytest process tripped the fetch_failure_rate bar at 4/4. Same autouse reset fixture, same reasoning, as that file already carries. Full cardpicker/tests/: 3541 passed, 8 skipped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN
This was referenced Jul 30, 2026
WilfordGrimley
added a commit
that referenced
this pull request
Jul 30, 2026
…in test_valid_url (#663) master went RED at f947346 (PR #660, the monolith) on test_valid_url[scryfall]/[scryfall_with_www] - 2 failed, 3542 passed. The failure is NOT in the monolith and touches no pipeline code: it is the third instance of a failure mode this test already documents twice. WHAT HAPPENED. `Scryfall.retrieve_card_list` fetches `https://api.scryfall.com/decks/<id>/export/text` for real. CI received an HTML document instead of the text export - `<!DOCTYPE html>` appears verbatim in the `base.py:64` "Invalid response" log line - so `query_import_site` raised `InvalidURLException`. WHY THIS IS NOT A SCRYFALL OUTAGE, AND WHY A RETRY IS NOT THE FIX. The endpoint is healthy: a direct curl from outside CI returns HTTP 200, `text/plain`, and the expected body. The immediately preceding master commit (1d433dd) went green on the identical test 111 minutes earlier, and nothing between the two touches integrations. What CI got was an edge/bot-challenge interstitial served to the GitHub Actions runner's address space - a property of WHERE the request originates, not of the code or of Scryfall's contract. The next runner sits in the same address space, so a re-run is a coin flip, not a remedy. THE FIX IS THE ONE THIS FILE ALREADY ESTABLISHED. tappedout.net (2026-07-20) and manastack.com (2026-07-22) each hit this same class of live-network false-red and each was mocked in place rather than skipped, so the site's own `retrieve_card_list` parsing stays covered and only the transport is faked. Scryfall now gets the same treatment, and it was the last un-mocked flaky caller left in this parametrize. TWO THINGS THAT ARE EASY TO GET WRONG HERE, both handled and both commented in place: - THE HOST IS `api.scryfall.com`, NOT `Scryfall.get_host_names()`. The two existing mocks build their patterns from `get_host_names()` because those sites fetch from the host they are matched on. Scryfall does not: it matches the `scryfall.com` deck page a user pastes but requests `netloc="api.scryfall.com"`. A pattern derived from `get_host_names()` would match nothing and leave the test exactly as red. - THE MOCK IS SCOPED TO `/decks/`. `api.scryfall.com` also serves this suite's bulk-data, DFC and meld calls; a host-wide mock would gut `test_get_double_faced_card_pairs`/`test_get_meld_pairs`. `real_http=True` still carries every other Scryfall request to the real API unchanged. The mocked body is the real response captured byte for byte (od -c verified: CRLF line endings, no trailing newline), so `retrieve_card_list`'s own `"// Sideboard"` strip runs on genuine input and the recorded snapshot is unchanged. VERIFICATION (mutation red -> restore -> green, local, python 3.10): - baseline, before this change: test_valid_url[scryfall] passes locally, because this box's IP is not the one being challenged - which is itself the evidence that the failure is origin-dependent rather than code-dependent. - mutate the mocked body ("3 Past in Flames" -> "3 MUTANT"): 2 failed (scryfall, scryfall_with_www) on the snapshot comparison. This proves the mock is genuinely serving the request - had it fallen through to the real network, altering the mock body could not have changed the outcome - and that the snapshot assertion is live rather than vacuous. - restore: 13 passed, 2 skipped, 13 snapshots passed. - black --check: unchanged. Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
WilfordGrimley
added a commit
that referenced
this pull request
Jul 30, 2026
…ess + census leak, mid-pass envelope re-sampling (#665) FIX 1 - THE MD5 GROUP BEHAVES AS ONE UNIT THROUGH THE MONOLITH Owner: "the md5 dedupe should only fetch each identical image once across sources and then apply votes to the entire group as the fetched card passes through the monolith." The fetch half already existed (`evidence_transfer`, keyed on `Card.md5_checksum`). The vote half existed only on the phash distance-0 key, so the set that got a fetch saved and the set that got a vote propagated were DIFFERENT SETS - byte-identical files always share a phash, but files sharing a phash are not necessarily byte-identical. CHECKED BEFORE BUILDING, as instructed: propagation is NOT redundant. `evidence_transfer` gives every md5 sibling its own ImageEvidence row with byte-identical extractor values, so it is reasonable to ask whether each member already reaches the same conclusion independently. It does not, structurally: a Stage D printing deduction is not a function of the evidence row alone. `_resolve_candidates_for_card` keys the candidate list on `Card.name`, and md5-identical uploads from different sources routinely carry different names. Members also differ on per-card eligibility. `test_the_unfetched_twin_has_no_verdict_of_its_own_without_propagation` falsifies the "N independent deductions already agree" hypothesis on the fixture rather than arguing it. Stage C+ now runs TWO tiers through ONE propagation engine (`_propagate_over_groups`), which takes the grouping as a parameter and knows nothing about how it was keyed: md5 exact identity -> shares a PRINTING vote. New. Runs first. phash d0 unchanged from PR #660. Runs second, filling only what md5 did not. Two defects in the PR #660 propagation are fixed on the way: - SOURCE VOTES WERE READ FROM REPRESENTATIVES ONLY. Stage D has no reason to reach a group's lowest pk first, so whenever it reached any other member, nothing propagated. Source votes are now read across every member, and one source per (group, identity) is chosen deterministically so two vote-holders in one group cannot generate duplicate rows inside a single write batch. - REPRESENTATIVES WERE NEVER PROPAGATION TARGETS. Same root cause, opposite direction. PROPAGATION NEVER OVERRIDES A MEMBER'S OWN INELIGIBILITY (owner constraint). A member already resolved, already confirmed to a `canonical_card`, not a CARD, or carrying a resolved custom-art/non-english tag is skipped. custom-art is the catalogue DECLARING the image is not a faithful depiction of a printing; a checksum must not overturn that. THE PHASH TIER IS LEFT IN PLACE, flagged rather than accreted. Issue #661 holds what phash grouping is FOR - the owner's direction is that it should eventually share an ILLUSTRATION (same artwork, possibly a different printing), not a printing verdict. Removing it now would itself be a behaviour change, and it is currently the only propagation reaching cards with no md5 at all (md5 is NULL for every LOCAL_FILE source by design). The `groups` parameter is the seam that tier plugs into later. FIX 2a - `run_name_frequency_elimination` NEVER LOOKED AT THE IMAGE Owner: "just because a card was printed exactly once doesn't mean that the image in our catalogue is an accurate depiction of that card, it may have a different border or another issue." Owner leaned toward adding the conjunct rather than dropping the tier; the conjunct is what shipped, and the reasoning for keeping the tier is in its own docstring. Everything the 1:1 gate checked was a COUNT. Counting establishes that IF the card depicts one of the name's printings THEN it is the uncovered one; nothing established the antecedent, and the only filters that spoke to it were the DECLARED custom-art/non-english tags - so an untagged altered border sailed through. "It is only a vote" is weaker than it sounds: #593 established a machine vote is what the question feed renders as the suggestion to confirm, and the human's click returns as a full-weight USER vote. The missing conjunct now requires the card's ALREADY-STORED evidence to be consistent with the candidate printing. NOT a new implementation: `_apply_agreement_checks`' border/frame check was lifted to `local_identify_printing_tags.printing_attribute_disagreement` and both callers now share it. That direction is forced - `local_calculate_verdicts` imports `local_identify_printing_tags`, never the reverse. Sharing also inherits PR #656's `artist_ocr` gate for free, which is the half a second copy would most likely have got wrong. NO STORED EVIDENCE MEANS ABSTAIN. This module's "missing data is not evidence" rule protects a match from being VETOED by silence; here silence is being asked to ESTABLISH something, so it points the other way. Counted separately from mismatches so the cost is legible. FIX 2b - THE CENSUS LEAK (a fresh wrong positive, not a stale vote) `_eligible_base_queryset(NAME_FREQUENCY_ANONYMOUS_ID)` was called with no `run_id`, making its "exclude cards already carrying this calculator's vote" LIFETIME. The gate is a COUNT over exactly that population, so the calculator was taking a census over a pool it permanently shrinks itself: run 1 votes on a card, a second upload of that name arrives, and run 2 sees one unresolved card where there are really two - and votes. Nothing about the second card changed; only the size of the population the gate counts. `compute_covered_printing_pks()` stays catalogue-wide and unscoped, deliberately: "covered" is a fact about the world, not about this calculator's progress. `run_pilot`'s `select_candidates` and `count_below_resolution_floor` are LEFT UNSCOPED - neither gates on a count over the returned population, so neither has this defect. Stated in `_eligible_base_queryset`'s docstring so the asymmetry is visible from the function rather than only from its callers. FIX 3 - THE MONOLITH RE-SAMPLES THE ENVELOPE MID-PASS Owner: "host resampling is likely required (for steps that aren't fetch) as the same monolith will run for small datasets and large ones so needs to fit the available compute appropriately." PR #660 checked the envelope ONCE, before Stage C. `_EnvelopeSentry` now re-samples at every stage seam: after Stage C, between each of Stage D's calculators/casters (via a new OPTIONAL `envelope_check` callback on `stage_e_dispatch._run_stage_d`, defaulting to None so the conveyor is byte-identical), and before each Stage C+ tier. Sample counts land on the ledger. HALT SEMANTICS PRESERVED. A breach still persists an EnvelopeTrip, still exits 3, still needs `resolve_envelope_trip` - no self-resume, and NOT converted to a throttle (that is rate pressure's channel, beneath Stage C, PR #644). A mid-pass halt message differs from the preflight's: rows already written STAY written, and it names the `--run-id` to resume with. Interval-gated at 60s so the check cannot become its own load. The number is derived, not tuned: the host-load bar reads the ONE-MINUTE load average, so sampling faster re-reads a number that has not finished moving. RESIDUAL, reported not hidden: the seams are BETWEEN calculators, not inside them. Closing that gap means threading a callback into each of seven calculators' own batch loops - a real refactor of shared code, deliberately not done here. DELIBERATE DUPLICATION, WITH A TRIPWIRE. `_members_eligible_for_a_propagated_vote` expresses four catalogue-level facts `_eligible_base_queryset` also expresses. It does not call that function (which bundles workload rules wrong for a propagation target) and that function could not be refactored to expose them (its own docstring records that tests and `stream_backstop_sweep` assert against its COMPILED SQL). `TestPropagationEligibilityMatchesTheBaseQueryset` fails if the two ever disagree. VERIFICATION - mutation red, restore green (7 mutants, all red; 263 tests green restored): M1 md5 tier returns no groups 3 failed M2 source votes read from representatives only 2 failed M3 propagation ignores member ineligibility 1 failed M4 envelope re-sampling reverted to preflight 2 failed M5 visual conjunct never disagrees 1 failed M6 no-evidence no longer abstains 1 failed M7 propagation eligibility drops the tag excludes 1 failed (the tripwire) Suites: test_run_pipeline, test_local_identify_printing_tags, test_local_calculate_verdicts, test_stage_e_dispatch - 520+ tests, all green. No model changes, so no migration. Docs: living pages only, no dated report - docs/identification-pipeline.md (Stage C+ md5 section), docs/features/printing-tags.md (both name-frequency fixes), docs/features/stage-e-operations.md (mid-pass re-sampling). Claude-Session: https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this is
One command that runs the whole identification pipeline end to end. Owner brief: "1 click". Every stage already existed and every stage was already run separately; what did not exist was anything that ran them together, in order, under one
run_id.is a complete, working, from-scratch, whole-catalogue run that writes. No flag is required.
This is assembly, not invention.
run_pipeline.pycontains no pipeline logic: it is imports, sequencing,run_idthreading and error handling. Every stage is reached by calling the module that already owned it.Wiring, per stage
stream_full_catalog.run_stage_zero_freshness→printing_metadata_importoperating_envelope.current_trip/check_envelope,stage_e_dispatch._sample_envelope_signalscurrent_tripfirst, no self-resumerun_image_evidence_cohortviacall_commandstage_e_dispatch._run_stage_d,batch_ids=Nonelocal_clustering.compute_two_threshold_clusters+local_identify_printing_tags.build_propagated_cluster_voteslocal_identify_printing_tags.verify_zero_resolutionslocal_calculate_verdictsruns between calculatorschannel_reportviacall_commandTwo thin extractions, no copies
Both lift an existing body to a module-level callable and have the original call it. Behaviour is unchanged.
stream_full_catalog.Command._run_stage_zero_freshness→ module-levelrun_stage_zero_freshness(require_fresh, is_resume, write, warn). It now also returns the bulk-file vintage (remoteupdated_at, cache path, cache mtime age, whether it refreshed, import stats), which the monolith records on its ledger row so a run's conclusions can be dated.stream_full_catalog's method is a wrapper that discards it and prints exactly what it always printed.run_pilot'spropagate_cluster_voteclosure → module-levelbuild_propagated_cluster_votes(...), returning rows instead of writing them. It closed over six pieces ofrun_pilot-local state and was reachable from nothing; the closure now calls it and keeps its own batching and written-id ledgers.Three parameters added to existing functions
stage_e_dispatch._run_stage_d/_run_attribute_chip_casters/_run_illustration_calculator:batch_ids: Optional[list[int]]—Noneis bulk mode, which every calculator underneath already supported. One Stage D now serves both engines.dry_run: bool = False— threaded through, was hard-codedFalse.Both defaults preserve existing behaviour byte-for-byte for the conveyor.
Write polarity — the organ table
Owner ruling: "the eventual intention for the monolith is that default is to write and flags are what prevents it. (opposite)". This is the inverse of every organ called. Inheriting their defaults is the dangerous failure: a pass that computes everything, logs every card, reports success and persists nothing is indistinguishable from a working run except by row counts.
run_pipelinepassesimport_scryfall_printing_metadata--dry-run(no preview mode)run_image_evidence_cohort--dry-run(already write-first)--dry-run; nothing overriddenrun_join_key_calculatordry_run=Truedefaultdry_run=Falserun_fallback_calculatordry_run=Truedefaultdry_run=Falserun_illustration_calculatordry_run=Truedefaultdry_run=Falserun_slow_path_calculatordry_run=Truedefaultdry_run=Falserun_layout_class_cast(border)dry_run=Truedefaultdry_run=Falserun_attribute_chip_cast(frame + bleed)dry_run=Truedefaultdry_run=Falsenot dry_runenforce_dry_run_precondition(the forced dry-run, #362) is deliberately not added: a write-by-default command has no--writeto gate, and requiring a prior dry-run would put a flag back in front of the working run. Verified it only arms inside Stage C's--card-ids-filepath —write_mode=(not dry_run) and bool(card_ids_file_for_scope)— so a bare run never trips it.--skip-dryrun-checkis forwarded for the file path. Pinned bytest_a_bare_run_never_trips_the_forced_dry_run_precondition.--dry-runis a real pass that withholds the write, not a plan: every stage executes, every calculator reports what it would cast, andchannel_reportstill runs. Exits 0.Organs not wired (not in the brief's sequence):
local_lands_identify,local_detect_ai_art,local_residual_classify,local_name_frequency_elimination(see finding 1),run_pilot's OCR/phash voting (standing owner deferral).Verification
End-to-end test:
cardpicker/tests/test_run_pipeline.py, 22 tests, drives the whole command over a 3-card fixture cohort. Only the network is stubbed (_fetch_one_card,_compute_one_card, and Stage 0's real download). Every Stage D calculator, all three chip casters, clustering, propagation, the gate andchannel_reportrun for real against the test DB.Mutation table — every stage unwired at the source, in turn:
channel_reportnot calleddry_run=Truedefault--dry-runleaks a write through Stage CM7 and M8 initially survived and the assertions were strengthened:
violations == 0is what you get whether the gate found nothing or was never called, and theCHANNEL REPORTbanner is printed byrun_pipelineitself either way. Both now assert a string only the stage can emit.docs_lint.py --strict: clean. Fullcardpicker/tests/suite: 3541 passed, 8 skipped. CI: 13/13 green.One cross-test defect found and fixed on the way:
test_run_pipeline.pypassed alone and halted every test at the envelope preflight inside the full suite.stage_e_dispatch._windowis a process-global rolling fetch-outcome window that_sample_envelope_signalsreads, and four fetch failures recorded bytest_stage_e_dispatch.pyearlier in the same pytest process trippedfetch_failure_rateat 4/4. Same autouse reset fixture that file already carries.Findings — recorded, not fixed
Per the brief: this command's first run is how we discover what is broken, and changing what it measures while building it means the first reading cannot be trusted.
run_name_frequency_eliminationis a census predicate that leaks across runs. Its "exactly one unresolved eligible card for this name" test counts over_eligible_base_querysetcalled with norun_id(local_identify_printing_tags.py:2013), so the pool is depleted by its own prior votes. A name that gains a second card after run 1 can have that card voted on in run 2 where an empty-vote catalogue would abstain — a fresh wrong positive assertion, not a carried-forward one. It has never run in production, so the fix window is open. Not wired into the monolith.CardPrintingTagis archived on supersession.models.vote_archive_modelmaps that one model;CardTagVote(all three chip families) andCardIllustrationVoteare deleted outright. So the printing grain stays diffable across generations via--generation-diff; the tag and illustration grains do not.CardIllustrationVoteis additionally the only production caller ofpurge_stale_machine_votesthat bypassesvote_write.purge_and_write_votes, so it would never recordsuperseded_by_run_ideven if registered.ArchivedCardPrintingTaghas no deletion path at all except FK cascade. Nothing can sweep this run's rows.run_idscoping and no value-comparison split, so a card whose evidence is later re-extracted can never have its chip re-read under a new run. Monotonic suppression, not a false fire. Fresh surface — both were only wired into an engine in Wire the attribute-chip casters into an engine; strip the vote from the uncalled wrapper #654._split_new_printing_tag_votescompares VALUES, so an identical recomputed verdict is skipped and the original row survives with its originalrun_id. Correct and desirable, but it meansfilter(run_id=R)returns what R changed, not what R concluded.PilotRunLedger.run_idis UNIQUE, so the monolith and its delegated Stage C cannot share a row. The data keeps the cleanrun_idand this command's summary row takes a-pipelinesuffix — becausechannel_reportscopes a channel's counts by therun_idon the rows, and a Stage C whose evidence carried a differentrun_idfrom the votes would read as a silent channel.--writeopt-in,--dry-runopt-out, and both plusenforce_dry_run_precondition). There is no single convention to inherit, which is what makes this surface error-prone even after this PR pins it. Noted, not fixed.bleed_diff_mmneeds no backfill.image_evidence.compute_card_evidencealready callscompute_bleed_diff_mmunconditionally (image_evidence.py:976). It was 97.9% NULL only because the field was added without an extractor version bump, so no existing row was re-extracted. A from-scratch run fills it.post_saveecho.suppress_evidence_change_echois acontextvarsflag set by the dispatch loop; bulk-mode writes are deliberately unflagged, and the pooled engine's writes happen in worker processes/threads anyway. Since Stage D is invoked explicitly, the echo is redundant work rather than a correctness problem, and it is inert unless a django-q cluster is running withSTAGE_E_STREAMING_ENABLED.docs/features/stage-e-operations.mdis still not in.github/wiki-publish-map.json(issue 11 docs/features pages are absent from the wiki publish map, including stage-e-operations.md #657). Not fixed here, per the brief. Nothing enforces the absence —check_wiki_publish_maponly validates listed entries.What is thin
harvest_fetch_limiter/harvest_rate_coordinator) and are unchanged, but the monolith does not re-sample host load mid-pass the way the conveyor does per micro-batch.Deviations
Cardby storedcontent_phash(the whole catalogue), not over a selection pool.run_pilotclusters over its eligibility-narrowed pool, which makes membership a function of what earlier runs voted on — a genuine 3-card cluster can present as a 2-card one, and dropping the lowest-pk member changes which card becomes representative. Clustering over the catalogue is what makes the answer independent of run history.stage_e_dispatch._run_stage_dgained two parameters rather than the monolith re-listing its six calls. Re-listing would have been a second copy of the load-bearing dependency order.Docs
docs/identification-pipeline.mdanddocs/features/stage-e-operations.md— the living pages, edited in place. No dated report.Live state
Nothing deployed, nothing run against production. Branch
feat/monolith-one-command; no migrations (checkedorigin/master's migration tree immediately before committing — none needed).🤖 Generated with Claude Code
https://claude.ai/code/session_013NhYmT1PxCcyemA16dFDxN