Repository navigation
math poller: queue admission caps, a stable request key, routing refused without a queue that answers, the login named by its secret (P-084) - #2984
Open
colinmegill wants to merge 9 commits into
Open
colinmegill wants to merge 9 commits into
colinmegill wants to merge 9 commits into
Conversation
…; the daemon's idle poll is 60 s while LISTEN is up The poller's math_rebuild admission takes its lane, attempt budget and class from one read-only table (math_rebuild -> lane 1, 3 attempts, class large, cost-reduction plan P-082); no caller can choose another lane or budget, and a test pins the table and what reaches pd_enqueue. The values are the ones the producer already sent. polis-jobs: POLIS_JOBS_IDLE_POLL_SECONDS (default 60, 1..3600) is the fallback claim poll while the LISTEN connection is up; without it the worker polls every POLIS_JOBS_POLL_SECONDS as before. The test harnesses set it to 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…sed without a queue that answers, the queue login named by its secret Cost-reduction plan P-084, day one, all behind MATH_CAPACITY_ROUTING (off): - The request key is the digest of the intent (env, scope, labels, input watermark, and the job the poller already knows), with one actor per env (math-poller:<env>): asking again for the same unresolved demand reuses one binding row instead of adding one per ask. A finished job in an answer is followed by a re-ask keyed after that job, so the scope guard decides (a fresh job once released, the same one while it holds). - No new job while 20 jobs of class large are queued or waiting (queue_full=1 on the capacity line; the conversation stays routed); at most 2 new jobs per conversation per day (kept on the record and in the state file). - With routing on, a missing queue DSN, a missing source commit, or a queue that does not answer (at start, before the first tick, and on any readiness tick) refuses routing: the small poller computes those conversations itself, as with routing off, and the line says queue_unreachable=1 until a depth read succeeds. - MATH_CAPACITY_QUEUE_LOGIN_SECRET names the Secrets Manager secret that holds the queue login's password; a DSN that carries a password is refused. Forwarded by compose; the server's line parser learns the two new keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…eleased the guard; the poison walks lift the per-day cap A binding made while the guard still held a finished job (exit unproven) answered the ask that followed the release with the same finished job, so no fresh job was admitted. The re-ask's key now says whether this ask released the guard (after-release:<job> or after:<job>). The Postgres walks that admit more than two jobs for one conversation in a day lift the module constant, as the unit walk does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
# Conflicts: # delphi/polismath/poller/capacity.py # delphi/tests/poller/fixtures/ops-poller-lines.txt
…e_full and queue_unreachable) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
The small poller's capacity line (and the readiness line's capacity object) now carries "rev" (1 here). A line without it predates revisioning and is revision 1. Python decode_counts and the server's TypeScript decodeCounts read revisions 1..own+8: an older line carries exactly its own keys (newer ones decode as null); a newer line must still carry every key the decoder knows, and the keys its revision declared are checked as counts and dropped, never relayed. Within a decoder's own revisions the shape stays closed. The Python depth decoder reads both 000024's polis-queue/3 reply and 000026's polis-queue/4 (with oldest_eligible_at), so a poller keeps its depth when 000026 is applied under it. delphi/tests/poller/fixtures/capacity-revisions.txt holds a line per revision (pre-revision, 1, 2, 3 and a forward 4); the Python test pins this revision's line to the real emitter and both decoders read every line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Re-sync by merge with ops/capacity-line-revisions (which is edge plus the revision field). This branch's emitter writes rev 2: revision 2 added queue_full and queue_unreachable (CAPACITY_REV_KEYS in capacity.py and readinessLine.ts). Its decoders read revisions 1 (with or without rev) and 3, and the fixture's revision-2 line is pinned to this emitter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
…lve revision 2 Production (stable) logs nine counts; edge added large_leased, large_poisoned and large_parked. Both shapes were logged without a rev, so both are kept readable: an unrevisioned line is revision 1 or 2, told apart by its keys. This emitter writes rev 2 explicitly. The fixture now holds both unrevisioned shapes, revisions 2..4 and a forward 5. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Production's nine-count line is revision 1 and edge's twelve revision 2 (both unrevisioned, told apart by their keys); queue_full and queue_unreachable are revision 3, which this emitter writes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
colinmegill
added a commit
that referenced
this pull request
Oct 7, 2026
…sion 4 Revision 4 added large_dead, oldest_queued_age_ms, queue_bytes and sweep_age_ms (CAPACITY_REV_KEYS in capacity.py and readinessLine.ts). The depth decoder keeps this branch's /3 + /4 reader, which edge now has too, and its usage reader. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
Member
Author
|
Correction merged in (9f7a2e0, 1edea3c; merge, no rebase): the capacity line carries |
Delphi Coverage Report
|
This was referenced Oct 7, 2026
This branch has not been deployed
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.
Observable changes, all only with
MATH_CAPACITY_ROUTING=1(default 0; nothing changes with routing off):MATH_CAPACITY_QUEUE_DSN, a missing source commit, or a queue whose depth read fails (at start, before the first tick, and on any readiness tick) refuses routing: the small poller computes those conversations itself, exactly as with routing off, and the capacity line saysqueue_unreachable=1until a depth read succeeds. Before this, routing went on and routed conversations were silently not enqueued.math_poller.capacity/1, and the readiness line'scapacityobject):queue_fullandqueue_unreachable, 0 or 1. The server's line parser accepts them. The CDK filters of cdk: one worker group per queue class; demand and alarms read from the queue; manifest remnants removed #2974 already read both.largeare queued or waiting to retry in the env (queue_full=1; the conversation stays routed, no compute, no park); at most 2 new jobs per conversation per day (kept on the record and in the state file).math-poller:<env>, so asking again for the same unresolved demand reuses onepolis_queue_requestsrow instead of adding one per ask (8,640 a day for six unchanged conversations, per the operating-surface map). A finished job in an answer is followed by one re-ask keyed after that job, so the scope guard decides as before (a fresh job once released, the same one while it holds).MATH_CAPACITY_QUEUE_LOGIN_SECRETnames the Secrets Manager secret holding the login's password (JSON withpassword, the shape the jobs daemon reads); a DSN that carries a password is refused. Compose forwards the name.No schema change. Nothing is enabled.
What it is
Cost-reduction plan P-084 (admission), PRs 1 and 3 of its list, and the secret-name rule. Code:
delphi/polismath/poller/capacity_queue.py(request_key,actor_scope,QUEUE_CAP_LARGE,SCOPE_DAILY_CAP,dsn_with_secret_password, the bounded follow-the-finished-job loop),capacity.py(queue_full,queue_unreachable, the record'sadmitted_ms,CapacityRouter.routing= configured and not refused),service.py(the start check and the per-tick reachability),server/src/ops/readinessLine.ts,docker-compose.yml, docs.Where the plan did not survive contact with the code
sha256(env, scope, staged_label, target_label, watermark). On the merged SQL a binding wins over the guard for 24 h: with that key alone, a cancelled or dead job's binding would answer every later ask of the same watermark with the finished job, and the cancel and dead paths of the composed chain could never admit a fresh job. The key also carries the job the poller already holds for the conversation (empty at first), and a re-ask after a finished job is keyedafter:<job>. Rows stay bounded per job (two or three), not per ask.test_capacity_queue.py.secretsmanager:GetSecretValueon the login secret (CDK grants it today only to the worker class roles), and the container needs metadata access. Recorded for the provisioning step; Colin provisions the login.Gates (mm2)
jobs_integration: pass.npm ci,contract:check,lint: pass. Full jest the CI way: 1707 passed, 1 failed,conversation-stats.test.ts"should accept until parameter", which fails the same way on this box for every branch tried (edge's hosted run is green; recorded as a box finding).TestAdmission(one key per intent, the cap asks nothing and the line says so, two a day survive the state file, an unreachable queue refuses routing until it answers, routing waits for the first answer, a missing commit stays refused, the login named by its secret only) and the Postgrestest_one_binding_per_intent_however_often_it_is_asked(five asks, one row; five more with the known job, two; three newer-watermark asks, no row; actormath-poller:<env>).test_gateandsign_lint: pass.Stacked by merge on #2982 (scheduling constants); its commit shows here until it lands.
🤖 Generated with Claude Code
https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s