Skip to content

DRAFT — polis-jobs: parked jobs are rediscovered from the database after a restart (needs 000026, P-086) - #2983

Draft
colinmegill wants to merge 15 commits into
edgefrom
queue/parked-rediscovery
Draft

colinmegill wants to merge 15 commits into
edgefrom
queue/parked-rediscovery

Conversation

@colinmegill

Copy link
Copy Markdown
Member

Observable changes: on a database with migration 000026, a polis-jobs worker re-reads its class's parked jobs on every reaper tick. A parked attempt whose exit is unproven and that this worker's journal does not know (the box that ran it is gone) now prints polis_jobs.alarm/1 with "reason":"exit_unproven","rediscovered":true on every tick until an operator records the proof; before, a restarted worker never saw such a row and printed nothing. On a database without 000026 it prints polis_jobs parked rediscovery off: … once. Nothing infers proof from elapsed time; no job state changes. Draft: it stands on the 000026 schema ruling (#2978). Not a schema change itself; nothing runs a daemon.

What it is

Cost-reduction plan P-086, "restart-safe recovery of parked and exit-proof state" (part of its PR 3). The reaper's watch set (reaper.rs) was built only from transitions the process saw, so it was empty after a restart (finding in the operating-surface map, reproduced there). Now, after each reap pass, the reaper pages through pq_class_parked(env, class, after_job, 100) (000026): for each parked job with unproven attempts, an attempt the journal knows goes into the existing watch (the journal recovery proves its exit when it can; the existing alarm after lease + one tick still applies); an attempt it does not know raises the alarm line above, each tick. The way out is the operator's recorded proof (pq_end_attempt … 'confirm_exit' with the evidence, after reading the instance's termination), after which the job is claimable as before.

Not in this PR (rest of P-086 PR 3/4): the durable inventory keys on the readiness line, disk free, the confirm-exit subcommand, drain on SIGTERM.

Gates (mm2)

  • queue-rs: cargo fmt --check, clippy -D warnings both feature sets, unit 41 + 7 + 4, jobs_integration 26 passed against postgres:17, including the new a_parked_job_is_rediscovered_after_a_restart_and_alarms_until_proven (worker A killed with its child and journal; B parks the row and stops; C, which never saw the transition, alarms exit_unproven every tick, invents no proof, and runs the job once the proof is recorded, after which the alarm stops) and the /3 case (one "rediscovery off" line, the worker keeps working).
  • Vote convention test_gate and sign_lint: pass.
  • Composed chain proof on the chain through 000026: every step held.

Stacked on #2981 (the built-in sweep), itself on #2978; their commits show here until they land.

🤖 Generated with Claude Code

https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s

colinmegill and others added 15 commits October 6, 2026 15:23
server/postgres/bin/apply-migration.sh refused 000024 ("has no apply
policy here"), so an existing database had no checked path from
polis-queue/2 to /3. The wrapper now accepts 000024:

- seal: down/000024-files.sha256
- rights: superuser or SET membership in polis_queue_owner (as 000023)
- chain: 000023 installed and polis_queue_install reads polis-queue/2
  (a second run is refused here, since the install then reads /3)
- rows: every queue and job table is empty (000024 alters the queue
  tables' CHECK constraints, ACCESS EXCLUSIVE until COMMIT)
- lock line: names the four queue tables; no lock on conversations
- post-check: polis_queue_install reads polis-queue/3

The usage text now prints the whole header (it was cut at line 60).

test_000024_down.sh gains check (w): refused without 000023 and with a
queued job (catalog unchanged both times); --preflight-only passes and
changes nothing; the apply exits 0 with post-check /3 and the same
schema dump as a plain psql apply; a second run is refused at preflight.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
… schema change)

pq_sweep deletes one bounded page of expired queue history per call
(P-083's bounds), recorded in a new polis_queue_sweeps ledger;
pq_class_parked lists a class's parked jobs with their unproven attempts
(P-086 restart-safe rediscovery); pq_queue_usage reports the queue's bytes
and the last finished sweep; pq_class_depth gains oldest_eligible_at and
answers polis-queue/4. Three indexes serve the sweep's scans. The contract
version stays polis-queue/3.

Sealed down script and its test (forward, backward byte-identical,
refusals, the chain unwinding through 000024 and 000023). The poller's
depth decoder accepts the /4 reply; the server and queue-rs tests learn
the new functions and the jobs_v4 template.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…rows are reported, not refused

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…needs migration 000026)

The daemon runs retention itself, page by page through pq_sweep, only when
its claim pass has nothing to start (cost-reduction plan P-083: the sweep
never goes ahead of a job). The SQL decides cadence (once per 24 h per
env), the single sweeper and every bound; each finished sweep is one
polis_jobs.sweep/1 line with its counts. POLIS_JOBS_SWEEP_MAX_PAGES (100)
and POLIS_JOBS_SWEEP_CHECK_SECONDS (900) bound it. On a database without
000026 the sweep says so once and turns itself off.

Stacked on the 000026 draft: a schema-dependent change, draft too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…eaper tick (needs migration 000026)

Cost-reduction plan P-086, restart-safe recovery: the reaper re-reads the
class's parked jobs (pq_class_parked) instead of watching only the rows it
saw park, so a restarted daemon still sees them. An attempt this daemon's
journal knows is watched as before; one it does not know (its box is gone)
prints polis_jobs.alarm/1 reason=exit_unproven on every tick until an
operator records the proof. No proof is inferred from elapsed time. Without
000026 the rediscovery says so once and stays off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
# Conflicts:
#	server/postgres/bin/apply-migration.sh
…eft 'latest' to the job ids' order)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
… (a grep that exits early made pipefail flaky)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

Delphi Coverage Report

File Stmts Miss Cover
init.py 2 0 100%
benchmarks/bench_pca.py 128 107 16%
benchmarks/bench_repness.py 81 65 20%
benchmarks/bench_update_votes.py 38 28 26%
benchmarks/benchmark_utils.py 34 0 100%
components/init.py 1 0 100%
components/config.py 165 70 58%
conversation/init.py 2 0 100%
conversation/conversation.py 1291 264 80%
conversation/group_vote_counts.py 38 2 95%
conversation/manager.py 131 42 68%
database/init.py 1 0 100%
database/dynamodb.py 395 178 55%
database/postgres.py 414 95 77%
delphi_storage/init.py 1 0 100%
delphi_storage/codec.py 295 23 92%
delphi_storage/golden_corpus.py 60 9 85%
empty_output.py 29 2 93%
engine_adapter.py 317 265 16%
job_child/init.py 325 49 85%
job_child/census.py 101 13 87%
pca_kmeans_rep/init.py 5 0 100%
pca_kmeans_rep/clusters.py 257 38 85%
pca_kmeans_rep/corr.py 98 17 83%
pca_kmeans_rep/group_k_smoother.py 28 0 100%
pca_kmeans_rep/legacy_kmeans.py 197 8 96%
pca_kmeans_rep/pca.py 162 19 88%
pca_kmeans_rep/repness.py 226 5 98%
poller/init.py 4 0 100%
poller/admission.py 318 15 95%
poller/backfill.py 936 32 97%
poller/capacity.py 485 27 94%
poller/capacity_queue.py 255 13 95%
poller/coordinator_bridge.py 317 317 0%
poller/math_writer.py 113 3 97%
poller/promotion.py 112 4 96%
poller/readiness.py 368 13 96%
poller/rebuild_child.py 133 6 95%
poller/service.py 665 52 92%
poller/worker_pool.py 140 0 100%
queue/executor.py 223 111 50%
regression/init.py 4 0 100%
regression/clojure_comparer.py 188 20 89%
regression/comparer.py 891 640 28%
regression/datasets.py 135 27 80%
regression/recorder.py 36 27 25%
regression/utils.py 130 85 35%
replay/init.py 2 0 100%
replay/attribution_capture.py 49 0 100%
replay/axis_continuity.py 347 24 93%
replay/certify.py 999 59 94%
replay/crosslang.py 128 2 98%
replay/decision_ties.py 55 8 85%
replay/diagnostics.py 50 7 86%
replay/driver.py 73 1 99%
replay/dsn_admission.py 36 1 97%
replay/equiv_query.py 10 0 100%
replay/event_ingress.py 95 1 99%
replay/fixture_bundle.py 815 62 92%
replay/fixture_config.py 266 11 96%
replay/fixture_extract.py 285 12 96%
replay/fixture_generate.py 103 1 99%
replay/fixture_samples.py 157 14 91%
replay/fixture_selection.py 119 0 100%
replay/fixture_survey.py 147 11 93%
replay/legacy_pca.py 29 17 41%
replay/polarity.py 319 25 92%
replay/poller_equiv.py 776 99 87%
replay/prodclone.py 193 2 99%
replay/real_data.py 146 10 93%
replay/schedule.py 205 10 95%
replay/served_math.py 159 4 97%
replay/shard_bench.py 189 65 66%
replay/stagecompare.py 890 161 82%
replay/stages.py 256 40 84%
replay/stepcompare.py 86 1 99%
replay/store.py 120 11 91%
replay/tie_capture.py 107 19 82%
replay/types.py 71 6 92%
run_math_pipeline.py 284 93 67%
tools/engine_rebuild.py 230 11 95%
types.py 22 0 100%
umap_narrative/501_calculate_comment_extremity.py 113 38 66%
umap_narrative/502_calculate_priorities.py 135 135 0%
umap_narrative/700_datamapplot_for_layer.py 502 502 0%
umap_narrative/701_static_datamapplot_for_layer.py 310 310 0%
umap_narrative/702_consensus_divisive_datamapplot.py 434 275 37%
umap_narrative/801_narrative_report_batch.py 837 620 26%
umap_narrative/803_check_batch_status.py 267 148 45%
umap_narrative/llm_factory_constructor/init.py 2 2 0%
umap_narrative/llm_factory_constructor/model_provider.py 234 234 0%
umap_narrative/polismath_commentgraph/init.py 1 0 100%
umap_narrative/polismath_commentgraph/cli.py 270 270 0%
umap_narrative/polismath_commentgraph/core/init.py 3 3 0%
umap_narrative/polismath_commentgraph/core/clustering.py 108 108 0%
umap_narrative/polismath_commentgraph/core/embedding.py 104 104 0%
umap_narrative/polismath_commentgraph/lambda_handler.py 219 219 0%
umap_narrative/polismath_commentgraph/schemas/init.py 2 0 100%
umap_narrative/polismath_commentgraph/schemas/dynamo_models.py 160 9 94%
umap_narrative/polismath_commentgraph/tests/conftest.py 17 17 0%
umap_narrative/polismath_commentgraph/tests/test_clustering.py 74 74 0%
umap_narrative/polismath_commentgraph/tests/test_embedding.py 55 55 0%
umap_narrative/polismath_commentgraph/tests/test_storage.py 87 87 0%
umap_narrative/polismath_commentgraph/utils/init.py 3 0 100%
umap_narrative/polismath_commentgraph/utils/converter.py 283 237 16%
umap_narrative/polismath_commentgraph/utils/group_data.py 331 289 13%
umap_narrative/polismath_commentgraph/utils/storage.py 580 466 20%
umap_narrative/reset_conversation.py 159 50 69%
umap_narrative/run_pipeline.py 379 229 40%
umap_narrative/topic_naming.py 148 148 0%
utils/cli_flags.py 15 0 100%
utils/clj_hash.py 44 0 100%
utils/env_flags.py 13 0 100%
utils/general.py 63 39 38%
utils/output_profile.py 66 2 97%
utils/serialization.py 10 5 50%
utils/vote_convention.py 159 5 97%
Total 24280 8159 66%

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant