Add Stage E Phase 2 streaming dispatch loop - #445
Merged
Conversation
Builds the conveyor Phase 1's envelope primitive had no caller for: an event-driven card-create/evidence-change trigger plus a cron backstop sweep, micro-batched dispatch that calls the existing Stage C extraction and Stage D calculator entry points as-is (scoped via a new optional card_ids parameter), and a per-batch PilotRunLedger convention. Ships default-off behind settings.STAGE_E_STREAMING_ENABLED; BULK-mode commands are unaffected. Also fixes a pre-existing mypy type-arg gap in operating_envelope.py that was blocking this commit's pre-commit hook. Co-Authored-By: Claude Fable 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.
Description
Stage E Phase 2 (issue #153): the streaming dispatch loop that Phase 1's
envelope primitive (
cardpicker/operating_envelope.py, PR #440) had nocaller for. Design authority:
docs/proposals/stage-e-streaming.md(still HOLD as a brief; this is the owner-approved implementation task
for its §3-§5, same posture Phase 1 shipped under).
Ships default-OFF (
settings.STAGE_E_STREAMING_ENABLED = False) -every mechanism below is live code, wired unconditionally, but every
entry point checks the flag first and is a no-op while it's False.
Turning it on in production is the phase-3 shakedown's own polled owner
action, not something this PR does.
What shipped
post_savereceivers onCard(
created=Trueonly, "card-create") andImageEvidence(every save,"evidence-change"), queued via django-q2
async_task(
cardpicker/stage_e_signals.py, wired inapps.py'sready()),plus a cron backstop sweep (
manage.py stream_backstop_sweep) thatre-runs the same eligibility selectors against the full backlog.
_select_micro_batchbuilds a bounded card-idlist per dispatch (seed card first, filled from the Stage C backlog),
sized by
settings.STAGE_E_MICRO_BATCH_SIZE(default 25, aplaceholder inside the brief's own 10-100 sanity range - the real
number is a measured output of the phase-3 Bug-A shakedown, per
§10(c), not invented here).
dispatch samples live host load/RSS/fetch-failure-rate and refuses
to proceed if
operating_envelope.current_trip()is open or a freshsample breaches a bar. No artificial cards/hour cap.
calculator entry points (
run_join_key_calculator/run_fallback_calculator/run_slow_path_calculator) already callresolve_and_persist_printingper touched card internally - scopingthem to the micro-batch via a new optional
card_idsparameter(additive,
None= unchanged BULK-mode behaviour) scopes theirconsensus recompute to the same set, with no separate consensus step
in the dispatcher.
cardpicker/stage_e_dispatch.py)calls the existing Stage C (
compute_card_evidence/persist_evidence)and Stage D entry points AS-IS, sequentially per micro-batch. No
extraction/calculator/consensus logic touched.
PilotRunLedgerrow per micro-batch(
command="stage_e_streaming_dispatch"), counters incl.peak_rss_mb,stage_c_completed,stage_d_*_votes,trigger_reason,lockout_trip_id. Documented indocs/features/stage-e-operations.md'snew "Phase 2" section.
Two binding Tron-gate notes from Phase 1's review are honored: the
fetch-failure rolling window is sized to
FETCH_FAILURE_WINDOW(500)exactly (
_FetchOutcomeWindow), and the loop refuses to dispatchwhile
current_trip()is non-None, full stop - no self-resume anywherein this module.
BULK-mode commands (
run_image_evidence_cohort,local_calculate_verdicts,reparse_collector_evidence,consensus_recompute) are untouched -none of their call sites pass the new
card_idsparameter, so theirbehaviour is byte-identical to before this change.
Also fixes a pre-existing mypy type-arg gap in
operating_envelope.py(Phase 1) that was blocking this PR's own pre-commit hook - a type
annotation only, no behaviour change.
Checklist
pre-commitand installed the hooks withpre-commit installbefore creating any commits.cardpicker/tests/test_stage_e_dispatch.py, 20 tests) covering: default-off gate, envelope-trip halt + no-self-resume + resume-after-acknowledge, the 500-card fetch-failure window sizing (including a boundary check tied tocheck_envelope's own ratified math), micro-batch selection, an end-to-end event->batch->Stage C+Stage D->ledger test, a Google-lockout-mid-batch trip test, a kill-safety/resume-contract test (mid-batch crash -> truthful FAILED ledger -> idempotent re-invocation), and backstop-sweep idempotence.test_operating_envelope.py,test_local_calculate_verdicts.py,test_run_image_evidence_cohort.py,test_reparse_collector_evidence.py, andtest_image_evidence.pytogether (336 tests) against the pre-provisioned host venv (docs/troubleshooting.md's documented safe pattern - throwaway testcontainers Postgres/ES, never the live prod stack) - all green.black/isort/ruff/mypyclean on every changed file (via the actualpre-commithook on this commit).docs_lint.pyclean.test_vote_consensus.py/test_views.py/test_import_sources.pyhit confirmed testcontainers port contention from a concurrent session on this shared box (docs/troubleshooting.md's documented failure mode, not a regression) - not re-run before this PR; none of those files touch code this PR modifies (I never editvote_consensus.py/printing_consensus.py/PROTECTED CORE).docs/features/stage-e-operations.md- new "Phase 2" section (trigger, batching, observability, resume contract) plus a "Phase 3 (not yet built)" section replacing the old "What Phase 2/3 will add" stub.docs/proposals/stage-e-streaming.md- a "Phase 2 built" pointer paragraph alongside the existing Phase 1 one.docs/README.md- updated thestage-e-operations.mdindex entry to mention Phase 2.Closes #153 (Phase 2 of the brief - Phase 3, the live shakedown/actual
enablement, remains open and untouched by this PR).
🤖 Generated with Claude Code