Repository navigation
Batch 460 wave 2: upload cancel and teardown fixes, quota SQL, trusted-proxies docs (BACKLOG #3740, #3764) - #2645
Conversation
…y log line says what it trusts The refusal of a zoned [api].trusted_proxies host now adds, after its "List 'fe80::1' instead." advice, that a hop an inner proxy writes into X-Forwarded-For with its zone will not match that entry and becomes the client address. That fails closed. A test pins the uvicorn behaviour and the message. No zone matching is built (owner's delegated ruling). serve: the TLS-terminating-proxy log line says which entries gain a twin spelling and fits 100 columns; uvicorn_trusted_hosts joins the settings import and is called where its result is used, with the reason it cannot refuse there. test_api_tls reuses _kept from test_ambient_proxy_disclosure, reads each message at the end, and corrects the CodeQL comment. BACKLOG #3764 items 1, 7, 8, 9, 12, 13, 14 and 15.
…ates the zoned-hop gap D-1 said __main__.py feeds forwarded_allow_ips verbatim from [api].trusted_proxies. It is uvicorn_trusted_hosts' result: the entries as written plus each IPv4 or IPv4-mapped entry's other spelling. Corrected inline, with a dated amendment section and an index note. The trusted_proxies row of CONFIGURATION.md gains the zoned-hop caveat. Item 5 was already met: docs/SECURITY-LOOSENING.md says an IPv4-mapped entry counts as the IPv4 range it names. Items 6 (a Linux uvloop IPV6_V6ONLY measurement) and 11 (the CodeQL alert 209 dismissal) stay open. Alert 209 will likely need re-dismissal after merge. BACKLOG #3764 items 1, 4, 5 (confirmed met), 7, 8, 9, 10 (reported), 12, 13, 14 and 15. Proposed PR title: Trusted-proxy hygiene: the zoned-hop gap is stated beside its refusal, ADR 0151 D-1 says what uvicorn is handed Proposed banner: Items 1, 4, 7, 8, 9, 12, 13, 14 and 15 built; item 5 confirmed already met; item 10 reported (alert 209 would need re-dismissal after merge). Open: item 6 (a uvloop IPV6_V6ONLY measurement on a Linux host), item 11 (the CodeQL alert 209 dismissal, a repository act), and folding a zone off an XFF hop, a follow-up. Items 2 and 3 ride PRs 2638 and 2634.
… in one aggregate Store.release_upload_quota and UploadQuotaLedger.release_upload_quota take (uploader_id, reservation_id). On every backend the delete matches both, so a release cannot remove another uploader's row. SQL Server: the release no longer reads the row's uploader under READCOMMITTEDLOCK to name its applock. It takes the named uploader's applock unconditionally, opened by the same statement the reserve uses (_open_under_upload_quota_applock), so its lock order no longer rests on that hint. Round trips are unchanged: the applock still needs a table-touching opener to begin the implicit transaction. The reserve's in-flight COUNT and SUM, two subqueries over the same rows, are one derived-table aggregate on SQLite, PostgreSQL and SQL Server, with the same budget decision. BACKLOG #3740 items 10 and 12 (item 12: the SQL part only). Proposed PR title: batch 460 wave 2 (BACKLOG #3740, #3764) Proposed banner: Item 10 built: the upload-quota release takes the uploader on all three backends, and the SQL Server release takes that uploader's applock with no read to learn it; the transaction opener stays, so its round trips are unchanged. Item 12's SQL part built: the reserve's COUNT and SUM are one aggregate on every backend. The PostgreSQL and SQL Server store legs of tests/test_uploads_shard_quota_interleave.py were not run locally and must be read on CI.
…unded; the straggler drain reads its defaults when it runs BACKLOG #3740 items 4, 11 (the uploads.py text) and 12 (the drain_stragglers part). Item 4. UploadStore.save's cancel arm removed a landed pair under _quota_lock with no bound on the wait, so one hung unlink queued every upload in the process. _remove_cancelled_pair now waits at most _LEDGER_CANCEL_WAIT_SECONDS on the executor's own Future, then lets the lock go with the removal still running; until it lands the pair is still billed, which errs toward refusing. A late failure is logged at ERROR by file_id. _reserve_across_shards's awaited release (item 5) is untouched. The thread submission is shared with _to_thread_to_completion through _submit_to_thread. Test: a hung removal does not block a second upload's reserve; red on origin/main. Item 12. drain_stragglers reads _STRAGGLER_DRAIN_SECONDS and _STRAGGLER_CANCEL_GRACE_SECONDS at call time. Test red on origin/main. Item 11, uploads.py: the UploadQuotaError residual and the _reserve_may_hold_a_slot log lines say a release is started, not that the slot is paid back; one rewritten docstring line qualifies "engine shard".
… both lifespans' drains, the schema DDL's drop, and engine shard qualified BACKLOG #3740 items 3 (reason comment), 11 (the remaining text) and 13. Item 3: upload_file's outliving.run(_record_upload_create...) site says why a deadline that answers 503 keeps the stored file and its row, so a retry stores a second copy (kept by design, the owner's delegated ruling of 2026-10-29). Comment only. Item 11: the outliving = OutlivingOperations() comment names the upload routes and the embedded lifespan; CHANGELOG.md's #3180 entry says the schema DDL drops upload_quota, at open on SQLite and under auto, and by provision-schema under external; "shard" qualified in the lines PR 2362 rewrote (store comments, the interleave test). Item 13: api/outlive.py's module, drain and DRAIN_TIMEOUT_SECONDS text covered only DR and reload arms; api/approvals.py's Executor text named only the replay's row as committed with its effect, not the purge's.
…bits path, and states its cost once The caveat no longer prints the raw zone, which ipaddress takes almost unchecked; the entry is already quoted escaped. The host-bits refusal of a zoned entry (fe80::1%eth0/64) adds it too. The cost moves to one place, the trusted_proxies row of CONFIGURATION.md, which drops "fails closed": the inner proxy's address is what the audit, the login limiter and allowed_client_networks then see. It also records that fe80::1/128 matches a zoned hop on any zone, pinned by a test. ADR 0151's amendment no longer calls ::/80 and ::/0 outside the mapped block. BACKLOG #3764 items 1, 4, 5 (confirmed met), 7, 8, 9, 10 (reported), 12, 13, 14 and 15. Proposed PR title: Trusted-proxy hygiene: the zoned-hop gap is stated beside its refusal, ADR 0151 D-1 says what uvicorn is handed Proposed banner: Items 1, 4, 7, 8, 9, 12, 13, 14 and 15 built; item 5 confirmed already met; item 10 reported (alert 209 would need re-dismissal after merge). Open: item 6 (a uvloop IPV6_V6ONLY measurement on a Linux host), item 11 (the CodeQL alert 209 dismissal, a repository act), and folding a zone off an XFF hop, a follow-up. Items 2 and 3 ride PRs 2638 and 2634.
…unts Review round 1 on the release change. release_upload_quota takes uploader_id and reservation_id keyword-only, on the Store protocol, the UploadQuotaLedger protocol and all three backends, since its only caller holds the two ids in the opposite order and a swap of two strs would pass mypy. The offline SQL Server and PostgreSQL fakes now assert one bound value per placeholder. The SQL Server release docstring and the changelog state the no-row cost: four statements rather than two, and a possible wait on a sibling's reserve. BACKLOG #3740 items 10 and 12 (item 12: the SQL part only). Proposed PR title: batch 460 wave 2 (BACKLOG #3740, #3764) Proposed banner: Item 10 built: the upload-quota release takes the uploader (keyword-only) on all three backends, deletes only that uploader's row, and on SQL Server takes the uploader's applock with no read to learn it; a release with a row sends as many statements as before, and one with no row sends four rather than two. Item 12's SQL part built: the reserve's COUNT and SUM are one aggregate on every backend. The PostgreSQL and SQL Server legs of tests/test_uploads_shard_quota_interleave.py were not run locally and must be read on CI.
…failure Review round 2. The SQL Server release docstring no longer claims the release reads nothing (the opener reads the uploader's index, unlocked) or that a caller wait bounds the no-row case (command_timeout = 0 removes the applock timeout). The changelog counts round trips, and says the PostgreSQL and SQLite releases take no lock. The budget-edge contract test releases its rows in a finally, so a failing server leg leaves nothing in flight. BACKLOG #3740 items 10 and 12 (item 12: the SQL part only). Proposed PR title: batch 460 wave 2 (BACKLOG #3740, #3764) Proposed banner: Item 10 built: the upload-quota release takes the uploader (keyword-only) on all three backends and deletes only that uploader's row; on SQL Server it takes the uploader's applock with no read to learn it, so a release with a row makes as many round trips as before and one with no row makes four rather than two. Item 12's SQL part built: the reserve's COUNT and SUM are one aggregate on every backend. Open: the PostgreSQL release still takes no per-uploader lock, and no test races a release against an uncommitted reserve. The PostgreSQL and SQL Server legs of tests/test_uploads_shard_quota_interleave.py were not run locally and must be read on CI.
…/128, and point at one source CONFIGURATION.md said clients behind the gap are seen as the inner proxy; they are seen as the zoned hop's address, which the row now says. The row and the caveat no longer offer fe80::1/128 as the entry that matches: that is an open question, since the zoned-network refusal names trusting an address on every interface as its reason. A test still pins the /128 behaviour as a measurement. _ZONED_HOST says "no socket peer". ADR 0151's amendment and index note point at uvicorn_trusted_hosts instead of restating its twin rules. BACKLOG #3764 items 1, 4, 5 (confirmed met), 7, 8, 9, 10 (reported), 12, 13, 14 and 15. Proposed PR title: Trusted-proxy hygiene: the zoned-hop gap is stated beside its refusal, ADR 0151 D-1 says what uvicorn is handed Proposed banner: Items 1, 4, 7, 8, 9, 12, 13, 14 and 15 built; item 5 confirmed already met; item 10 reported (alert 209 would need re-dismissal after merge). Open: item 6 (a uvloop IPV6_V6ONLY measurement on a Linux host), item 11 (the CodeQL alert 209 dismissal, a repository act), whether to advise fe80::1/128 for a zoned hop, and folding a zone off an XFF hop, a follow-up. Items 2 and 3 ride PRs 2638 and 2634.
…e 0.5.2 note correction moves to a fragment, and text rewrapped BACKLOG #3740 items 4, 11 and 13, code-review round 1 repairs. - _remove_cancelled_pair waits _CANCELLED_REMOVAL_WAIT_SECONDS, a filesystem bound apart from the ledger's, so tests and tuning of _LEDGER_CANCEL_WAIT_SECONDS no longer move it. - Past the bound an ERROR names the file_id as a stored file with no upload.create row, since the process may stop before the removal answers; a late removal logs INFO, a failed one a second ERROR. - The test asserts that ERROR line, not a substring a ledger WARNING shares, and waits for the stragglers before the in-flight check. - The 0.5.2 "dropped at open" correction is a changelog.d fragment; CHANGELOG.md is back as released (changelog.d/README.md). - api/outlive.py lists the arms with "at least". - Paragraphs the earlier splices left long are rewrapped.
…e residuals and the outliving arms are stated plainly BACKLOG #3740 items 3 (reason comment), 4, 11, 12 (drain_stragglers defaults) and 13. Code-review round 2 repairs: - the late removal reads back, in its thread, whether the pair is gone, and a WARNING withdraws the ERROR, at a level the default log keeps; - _remove_cancelled_pair names its residuals: an abandoned removal holds an executor worker, uncapped, and a scan can refuse its Windows unlink; - save()'s docstring covers the abandoned removal; - api/outlive.py says the upload.create arm starts a removal that can be cut short; drain() and the outliving comment point, not restate; - the fragment says "no other upload in that process". Proposed PR title: Batch 460 wave 2: a cancelled upload's file removal no longer holds the quota lock unbounded, plus upload and outliving text fixes (BACKLOG #3740 items 3, 4, 11 to 13) Proposed banner: PARTIAL. Built: item 4 (the cancel-path removal waits at most _CANCELLED_REMOVAL_WAIT_SECONDS under _quota_lock, then goes on without it, with an ERROR by file_id; test red on the unfixed code); item 3 kept by design with the reason in the code at upload_file's outliving.run site, per the owner's delegated ruling of 2026-10-29; item 11 (UploadQuotaError residual, the reserve log lines, the outliving comment, a changelog.d correction of 0.5.2's "dropped at open", "engine shard" in the lines PR 2362 rewrote); item 12's drain_stragglers defaults (test red on the unfixed code); item 13 (api/outlive.py and the approvals.py Executor text). Item 8 was already fixed by the server-clock SQL (#3256). Not built here: item 12's discard reuse, since discard is what PR 2637 rewrites; item 12's COUNT/SUM and teardown parts (other wave 2 Builders); items 1, 2, 5 to 7, 9, 10, 14 to 18 per their own rows and PRs. Open: discard()'s removal after a failed upload.create row still waits unbounded under the lock, so a hung unlink there would still queue every upload; an abandoned removal would hold an uncapped default-executor worker; docs/PHI.md's uploads row does not yet list a removal abandoned at the bound.
…beat touches, and the step list is pinned The upload straggler drain now waits, inside its own wait share, for the outliving operations still running (OutlivingOperations.settle), so a removal a cancelled upload.create row's arm starts late is held and drained before the step returns. The drain no longer waits for a heartbeat touch: each pass cancels held touches, and a cancelled heartbeat cancels its own. The managed teardown's steps are built by _managed_teardown_steps, so a test pins their order against the real list, and the NSSM-window test counts a cut grace per bounded step. drain_stragglers_within's split is derived from its defaults; the long-operations split is the named _OUTLIVING_WAIT_SHARE. BACKLOG #3740 items 6, 14, 15, 16, 17, 18, and item 12's teardown part (recorded gone). Item 15 is also BACKLOG #3819 item 1. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within (derived) and named, not derived, for the long-operations split, by design; item 12's teardown part was already gone. Item 15 would leave 0.0 s of headroom on first deployment: 10 bounded steps make 10.0 + 3.0 + 1.0 = 14.0 s, plus the 1.0 s exit room, exactly the 15.0 s NSSM window; no constant moved.
…lls in its warning Review of the wave-2 rebase found the cancel pass over what the first wait left also cancelled a touch _cancel_touches had already cancelled, which would cut short a store's unwind of the first cancel. The pass now skips touches. The warning's count leaves touches out, and a WARNING names touches still unwinding when the drain returns. BACKLOG #3740 item 14. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within, and its long-operations split is named, not derived, which stays open for a ruling; item 12's teardown part was already gone. Item 15 would leave 0.0 s of NSSM headroom on first deployment: 14.0 s plus 1.0 s exit room equals the 15.0 s window.
…led heartbeat cancels its touch once Review round two of the wave-2 rebase: the touches stayed uncancelled for the whole settle wait, and a heartbeat's finally cancelled a touch that something else may already have cancelled. Both now go through _cancel_touches, which cancels each touch once. BACKLOG #3740 item 14. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within, and its long-operations split is named, not derived, which stays open for a ruling; item 12's teardown part was already gone. Item 15 would leave 0.0 s of NSSM headroom on first deployment: 14.0 s plus 1.0 s exit room equals the 15.0 s window.
|
QA line, W2a (BACKLOG #3740 items 3, 4, 11, 13, 12 part; head |
|
QA line, W2b (BACKLOG #3740 items 10, 12 SQL; head The Builder's tag line used Unicode arrows; this copy writes them as |
|
QA line, W2c (BACKLOG #3740 items 6, 12 teardown, 14 to 18; head Both rounds cover the rebase resolution. The last commit, |
|
QA line, W2d (BACKLOG #3764; head |
|
Lander: handing over PR 2645 (MEFORORG/MessageFoundry), the batch 460 wave-2 batch PR. It carries four items, and none of them changes a security control.
|
One conflict, in messagefoundry/api/app.py's imports: main's new refused_body import line is kept, followed by the batch's multi-line security_notify import, which adds SecurityEventNotifier. BACKLOG #3740 (batch 460 wave 2).
|
Lander (session d1c562bf), 2026-10-29. Held for three fixes and a conflict with main, at Fix before merge (each changes behaviour against main)
Rows (file after merge)
|
|
QA line for the merge of The fork transcripts are under |
|
Lander: the
Open findings where main meets the batch. All are for rows. None is from the merge itself.
|
…ve waits for them instead of the cancelled one spinning BACKLOG #3740 item 4, the Lander's hold on the wave-2 batch PR (2645), findings 2 and 3. 2. A removal abandoned at the bound kept a default-executor worker with no cap. Removals now run on _removal_executor, a pool of _CANCELLED_REMOVAL_WORKERS (2); a hung one past the cap queues there and still runs, and the pool every asyncio.to_thread shares stays free. Test: three hung removals with a one-worker pool run one at a time; red on 5adfe89 (three ran at once on the default executor). 3. The wait absorbed every CancelledError in a loop under _quota_lock, so an anyio scope, which cancels on every loop pass, spun it for the whole bound. The cancelled save now stops waiting at the next cancellation. The removal is registered in _pending_removals with its deadline, and the next save waits for it, holding the lock and not cancelled, before it reserves and scans, so the pair is still not counted. The ERROR at the bound is a timer. Test: under an anyio cancel with a hung removal, A ends well inside the bound and B waits; red on 5adfe89 (A ran out the bound). test_an_anyio_scope_cancel_still_removes_the_landed_file now waits for the removal, which can end just after the save does.
…unning, waits are per uploader, and a failed start is logged BACKLOG #3740 item 4, code-review round 1 repairs over 5adfe89..HEAD. - The bound's ERROR is a loop timer a closing loop would drop, so drain_stragglers names every removal still inside its bound at ERROR first; a removal that lands later logs the withdrawing WARNING. Test. - A pending removal leaves _pending_removals at its bound or at the drain, not only when it ends, so a removal that never returns holds no later save and no entry for the life of the store. - A save waits only for its own uploader's removals; the scan counts that uploader's files only. - save's arm logs a removal that could not start (submit can raise) and still propagates the cancellation. - Docstrings: the cap's cost (removals queue behind hung ones, bound counted from queueing), the suppress's one caller, the WARNING's text. - Tests: the cap test shuts its pool in its finally and drops a check that passed unfixed too; the spin test's bound is 5 s; the two other anyio tests in test_uploads.py wait for the removal.
… names only what its wait leaves, and the withdrawal is logged from the thread BACKLOG #3740 item 4, code-review round 2 repairs over 5adfe89..HEAD. - Every save waits for pending removals again, not only the same uploader's: the scan opens every sidecar, which on Windows refuses the unlink. The deadline filter is back, so an entry whose timer never ran cannot hold a save. - The drain waits for pending removals inside its own wait and names at ERROR only those still running at its end, so a healthy unlink is not reported as an audit gap. - The withdrawing WARNING is logged in the removal's thread, so it still lands after the loop has closed. - A removal that cannot start is caught as RuntimeError and its ERROR says the file is stored with no upload.create row. Test. - The cap test also checks the real pool's cap. The changelog fragment and the docstrings describe the next-save wait.
…very long operation The Lander's hold on the wave-2 batch: the settle before the upload straggler drain waited on every running long operation, so a DR change or reload still rolling back at shutdown held the step for its whole wait share, slack the later audit-writing steps would otherwise get. OutlivingOperations.settle takes a label, and the step passes the upload route's own, now the constant _UPLOAD_CREATE_OPERATION. BACKLOG #3740 item 6. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within, and its long-operations split is named, not derived, which stays open for a ruling; item 12's teardown part was already gone. Item 15 would leave 0.0 s of NSSM headroom on first deployment: 14.0 s plus 1.0 s exit room equals the 15.0 s window.
…arrowing costs Review of the settle narrowing: a default that waits on every operation made the retired behaviour the easy path, and the comment named only the slack it saves, not that a DR arm in its hook reap loses this wait. Docstring and changelog lines rewrapped at 100 columns. BACKLOG #3740 item 6. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within, and its long-operations split is named, not derived, which stays open for a ruling; item 12's teardown part was already gone. Item 15 would leave 0.0 s of NSSM headroom on first deployment: 14.0 s plus 1.0 s exit room equals the 15.0 s window.
… revision Review round two of the settle narrowing: the step comment, the new test's docstring and the changelog compared the narrowed settle with the wait-on-everything form, which never reached main. They now state the design, and the test docstring says "at least" where the constant does. Prose only. BACKLOG #3740 item 6. Proposed PR title: Shutdown drains: the upload drain settles long operations and skips heartbeat touches; the teardown order and NSSM window are pinned against the real steps (BACKLOG #3740 items 6, 14-18; #3819 item 1) Proposed banner: PARTIAL. Items 6, 14, 15, 16 and 18 are built; item 17 is built for drain_stragglers_within, and its long-operations split is named, not derived, which stays open for a ruling; item 12's teardown part was already gone. Item 15 would leave 0.0 s of NSSM headroom on first deployment: 14.0 s plus 1.0 s exit room equals the 15.0 s window.
|
QA lines for the PR 2645 hold fixes. Posted verbatim by the batch 460 Manager. W2a, fixes 2 and 3 ( W2c, fix 1 ( No review round covers |
|
Lander: all three hold fixes have landed. The new head is Fix 1: settle now waits only on upload rows (W2c:
Fix 2: abandoned removals are capped (W2a, on the way to
Fix 3: no busy-spin (W2a). The cancelled save absorbs the cancel once, so under anyio it returns at once. The removal is registered in
Checks. Ruff clean. mypy clean on the linux, win32 and tests legs. 72 changelog fragments, all well-formed. pytest: W2a's upload and teardown set, 354 passed; W2c's teardown, outlive, reload, DR, partition and upload set, 326 passed. Hosted legs: New open findings, for rows:
|
One conflict, the `trusted_proxies` row of docs/CONFIGURATION.md. The resolved row is main's row, with its exposure-rule sentence (vault BACKLOG #2251), plus the batch's two insertions: "socket" in "would match no socket peer", and the zoned-hop caveat (BACKLOG #3764). A word diff of the row against each side shows only the other side's insertions. BACKLOG #3740 (batch 460 wave 2).
|
Lander (session d1c562bf), 2026-10-30. Fixes 1-3 covered at
Still needed: the conflict with main. It is now in docs/CONFIGURATION.md, after 2638, 2641 and 2644 merged. Merge origin/main and reply. The Lander checks the resolution and then enqueues. |
|
QA line for the second sync of |
|
Lander: the second conflict on PR 2645 is resolved. The new head is The conflict. Only
The resolution. The resolved row is the batch's row with main's sentence where main placed it. A word diff of the result shows only the batch's two additions against main, and only main's sentence against the batch. The reviewer re-ran that comparison and found nothing dropped. The automatic merges in Checks.
Open findings in code this merge brings in from main. These are for rows:
|
|
Lander (session d1c562bf), 2026-10-30. Resync checked at |
|
Lander (session d1c562bf), 2026-10-30. Enqueuing at |
Batch 460 wave 2 is four Builder branches on
batch/b460-wave2: three items for BACKLOG #3740 and one for #3764. None changes a security control. The wave's control changes ship in their own PRs: 2634, 2636, 2637, 2638 and 2640. Every item below is reversible.Combined-tree checks (head of
batch/b460-wave2)ruff check .,ruff format --check .mypy: linux engine, win32 engine, linux testsledger_check.py --ciThe full suite was not run. The PostgreSQL and SQL Server store legs,
windows-service-smoke, the Windows pytest legs and CodeQL run hosted only, and are named per item below.W2a: BACKLOG #3740 items 3 (reason in the code), 4, 11, 13 and part of 12
Source branch
b460w2-3740-uploads-text, headf7cd6839774b4b45d7f5ad1bc67e83da31e61db5.Merge danger
Reversible: yes.
Blast radius:
UploadStore.save's cancel arm inmessagefoundry/uploads.py, through the new_remove_cancelled_pair,_submit_to_threadand_CANCELLED_REMOVAL_WAIT_SECONDS.drain_stragglersdefaults.api/app.py,api/outlive.py,api/approvals.pyanduploads.py.store/base.py,store/postgres.py,store/sqlserver.py,store/store.pyanduploads.py. The schema hash is unchanged.Evidence
test_a_hung_removal_of_a_cancelled_pair_does_not_hold_the_quota_lock.uploads.py("B waited on the lock for A's hung unlink").test_the_drain_reads_its_default_waits_when_it_runs.What changed
d20261029T205249596-4de5a7. The reason now sits atupload_file'soutliving.run(_record_upload_create...)site. Each stored copy has its ownupload.createrow, is owned by its uploader and counts against that uploader's quota. Discarding on a deadline would reopen #3178.file_id. If the removal later lands, a WARNING withdraws that ERROR.outlivingcomment is corrected. The 0.5.2 "dropped at open" correction is a new fragment,changelog.d/3740-schema-drop-note.fixed.md, becausechangelog.d/README.mdforbids editingCHANGELOG.md.api/outlive.pyandapi/approvals.pyare corrected.drain_stragglersnow reads its waits at call time. "discard could reuse_run_to_completion" was not done, because PR 2637 rewritesdiscard.Open, for a new row.
UploadStore.discard()goes through_discard_under_lock, which still holds_quota_lockwith no bound if an unlink hangs after a failedupload.createrow. Thedocs/PHI.mduploads row also does not list a removal abandoned at the 5 s bound. PR 2637 edits that function and that row, so this is a follow-up.Other open findings:
W2b: BACKLOG #3740 items 10 and the SQL part of 12
Source branch
b460w2-3740-store-sql, head2efc4deb75e174c624b02aa58e7cb2cf795b80d9.Merge danger
Reversible: yes.
Blast radius:
release_upload_quota(*, uploader_id, reservation_id)changes on the Store protocol, theUploadQuotaLedgerprotocol and all three backends. It is keyword-only.READCOMMITTEDLOCK. It takes the named uploader's applock, through a new shared opener,_open_under_upload_quota_applock.Evidence
tests/test_upload_quota_store_sql.pyfailed onorigin/main. The aggregate counted 2 reads, and the release signature and scoping failed.tests/test_uploads_shard_quota_interleave.pygains a wrong-uploader release case andtest_the_reserve_budget_admits_an_exact_fit_and_refuses_one_past_it, on all three backends.What changed
_PG_EPOCH_NOWand_MSSQL_EPOCH_NOW.Hosted legs to read: the
[postgres]and[sqlserver]cases oftests/test_uploads_shard_quota_interleave.py, the only live checks of the new SQL.Open, for a new row. The PostgreSQL release takes no per-uploader advisory lock, so on PostgreSQL a release can still miss an uncommitted reserve. This predates the branch.
Other open findings:
uploads.py.touch_upload_quotamatches by reservation id only.W2c: BACKLOG #3740 items 6, 12 (teardown), 14 to 18, and #3819 item 1
Source branch
b460w2-3740-teardown-r, head98cdc0e5e7f38451af61c2ff440e976eed62a7eb. It was rebased by its Builder onto this batch branch, after a conflict with W2a inmessagefoundry/uploads.py. The Builder resolved the conflict, and the resolution had its own two review rounds. The superseded branchb460w2-3740-teardown(a404092ad4) is not in this PR.Merge danger
Reversible: yes.
Blast radius:
api/app.py:_app_drain_steps, a new_managed_teardown_steps, and_OUTLIVING_WAIT_SHARE.api/outlive.py: a newOutlivingOperations.settle.uploads.py:drain_stragglers,drain_stragglers_within, held touches (_touches,_cancel_touches,_running) and the heartbeat'sfinally.Evidence
test_a_removal_a_cancelled_create_row_starts_late_is_drained_before_the_teardown_endsis red on the unfixed code.test_the_shutdown_drain_does_not_wait_for_a_heartbeat_touch(four cases) andtest_the_drain_cancels_a_held_touch_onceare red on the old code.test_the_managed_steps_are_built_in_their_pinned_orderandtest_the_managed_lifespan_runs_its_real_steps_in_the_pinned_orderpin the order.What changed
settle(timeout)now waits without cancelling, within the drain's wait and never its grace. A removal can still start late if its operation outlasts that wait. The docstring says so._OUTLIVING_WAIT_SHARE: deriving it would cut the wait from 0.5 s to about 0.21 s and cancel work that would otherwise finish. Its derivation half stays OPEN on the row.Review coverage: the last commit,
98cdc0e5e7, which cancels touches before settle, came after the two-round cap. No review round covers it.Hosted legs to read:
windows-service-smokeand the Windows pytest legs. The new tests are timing-based, with 1 to 2 s bounds.Open findings:
cancelling() > 0is read as "already cancelled by us".settlewaits on every long operation._stragglers.W2d: BACKLOG #3764 items 1 (docs, by ruling), 4, 5 (confirmed met), 7 to 10, 12 to 15
Source branch
b460w2-3764-hygiene, headeec940cdb3ba249a2f07ebdff90d8fb74d800f3d.Merge danger
Reversible: yes.
Blast radius:
config/settings.py(_ZONED_HOP_CAVEAT).trusted_proxiesrow ofdocs/CONFIGURATION.md.__main__.py, the serve info log line, theuvicorn_trusted_hostsimport and its call site.tests/test_api_tls.pyandtests/test_ambient_proxy_disclosure.py, and a changelog fragment.Evidence
('fe80::1%eth0', 0), a zoneless control, and the/128measurement. A parametrized refusal test covers four entries, including newline injection, plus a control. Both are red on the unfixed code.What changed
d20261029T205256184-f14632). A zoned hop written by an inner proxy will not match the zoneless entry, and becomes the client address. The refusal advice and the docs now say so, and name what that costs: the audit, the per-IP login limiter andallowed_client_networksall see the zoned address. There is no code match./128(decisiond20261029T223607035-82fdc4). The refusal does not advise<host>/128. That would match a zoned hop on any zone. The behaviour is pinned by a test only.run_kwargs, with the real reason in the comment: the settings load already refuses a bad entry._keptand reads after redaction.Item 10 / 11, for the Lander or the owner. The edit touches the format string of the call that CodeQL alert 209 flags. The alert will likely need re-dismissal after merge, under ADR 0034's amendment rule 3. That is item 11, a repository act.
Not built: item 6, which needs a Linux host with uvloop.
Open findings:
docs/adr/0150around line 122 still saysforwarded_allow_ips = [api].trusted_proxies._Keepcopy remains intests/test_header_floor_wire.py.Ledger asks for the Lander
discard()path, plus the PHI.md abandoned-removal entry;Landing order: none. Expect textual overlaps only, with PRs 2637 (
uploads.py,api/app.py), 2641 (__main__.py,docs/CONFIGURATION.md) and 2640 (store files).