fix(api): flush the summary-access coalescer at lifespan shutdown (BACKLOG #1640) - #1241
Open
wshallwshall wants to merge 2 commits into
Open
wshallwshall wants to merge 2 commits into
wshallwshall wants to merge 2 commits into
Conversation
added 2 commits
September 16, 2026 17:15
…decision The UserPromptSubmit hook reported how full the session's context window was and warned the seat "before it runs out of room", claiming a compaction "drops the seat, the goal and the brief, and nothing re-declares them for you". OWNER DECISION 2026-09-16: it must emit nothing, ever. The desktop app compacts automatically when the context overflows, so the warning advertises a failure mode that does not occur, and its central claim about losing the seat and the brief is false. It cost real attention as well as being wrong. A session receiving it on every prompt starts narrating its own context percentage back to the owner, curtails work it should have finished, and treats a normal condition as an emergency. That is why it is disabled rather than reworded. The script is left in place as a no-op carrying the reason, so a reader who finds the wiring does not conclude the file was lost and restore it. The wiring in .claude/settings.json is untouched here on purpose: a missing script would make the hook fail rather than stay quiet. All 65 worktree copies were neutered at the same time, so no live session sees it.
…CKLOG #1640) The M-5 control writes one `summary_access` audit row per (actor, scope, hour) window, so routine console polling does not produce a row per request while a bulk harvest shows a large count. A window is emitted when a LATER access rolls it into a new hour -- so the window open at shutdown was emitted by nothing. `_SummaryAuditCoalescer.flush` existed and documented itself as the engine-shutdown path. NOTHING CALLED IT. Every clean restart therefore dropped the open hour's PHI-summary access audit, and the case where that matters most is the one the control exists for: an operator restarting shortly after a bulk census fetch. Not deployed (section 0), so this is written in the conditional: a first deployment restarted within the hour of a bulk fetch WOULD hold no row for it. WHERE, AND WHY THE ORDER IS LOAD-BEARING The flush runs in the lifespan `finally`, BEFORE `engine.stop()`, because that ends in `store.close()` and the emit needs the store. It is wrapped, on the reaper's precedent: a store error during teardown must not skip `engine.stop()`. aiosqlite's connection worker is non-daemon, so skipping it leaves the process unable to exit -- a lost audit row would become a hung service. CHECKS ruff check pass ruff format --check pass mypy (strict) pass, on the module and the new test new tests 3 passed existing regression 13 passed (test_api.py -k "summary or audit or lifespan") THE CONTROL, run rather than asserted. With the fix stashed out, the new test fails with "the open window was dropped at shutdown: []". With it restored, 3 pass. The suite also carries that control as a test of its own: `test_the_assertion_can_fail` neutralises the flush and requires the row to disappear, so the main assertion cannot pass for some unrelated reason. `test_a_flush_failure_does_not_abort_the_teardown` pins the wrapper: its assertion is narrowly that the lifespan context EXITS when the flush raises. NOT RUN: the full suite, and every leg needing an extra this interpreter lacks (`vault`). The venv used is the primary checkout's; `messagefoundry.__file__` was confirmed to resolve to THIS worktree and to contain the change before any result was trusted. LEDGER: #1640 is not edited here. Banner text for the Lander -- SHIPPED: the coalescer is flushed in the lifespan `finally` before `engine.stop()`, guarded so a flush failure cannot abort the teardown. Both limbs of "what closing looks like" are covered: the flush, and a lifespan-to-shutdown test asserting the row lands.
Collaborator
Author
|
Note for review: this same commit also currently sits on Whichever merges first makes the other's copy redundant; there is no conflict. |
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.
The M-5 control writes one
summary_accessaudit row per(actor, scope, hour)window, so routine console polling does not produce a row per request while a bulk harvest shows a large count. A window is emitted when a later access rolls it into a new hour -- so the window open at shutdown was emitted by nothing._SummaryAuditCoalescer.flushexisted and documented itself as the engine-shutdown path. Nothing called it. Every clean restart dropped the open hour's PHI-summary access audit, and the case where that matters most is the one the control exists for: an operator restarting shortly after a bulk census fetch.Not deployed (section 0), so this is in the conditional: a first deployment restarted within the hour of a bulk fetch would hold no row for it.
Where, and why the order is load-bearing
The flush runs in the lifespan
finally, beforeengine.stop(), because that ends instore.close()and the emit needs the store.It is wrapped, on the reaper's precedent: a store error during teardown must not skip
engine.stop(). aiosqlite's connection worker is non-daemon, so skipping it leaves the process unable to exit -- a lost audit row would become a hung service.The control, run rather than asserted
With the fix stashed out, the new test fails with
the open window was dropped at shutdown: []. With it restored, 3 pass.The suite carries that control as a test of its own:
test_the_assertion_can_failneutralises the flush and requires the row to disappear, so the main assertion cannot pass for some unrelated reason.test_a_flush_failure_does_not_abort_the_teardownpins the wrapper, and its assertion is narrowly that the lifespan context exits when the flush raises.The durability assertion reads the audit through a second store opened on the same file after shutdown -- reading through the app's own store would prove nothing, since surviving
store.close()is the property under test.Checks
ruff checkruff format --checkmypy(strict)test_api.py -k "summary or audit or lifespan")Not run: the full suite, and every leg needing an extra this interpreter lacks (
vault). Those must be read on the runner.The venv used is the primary checkout's.
messagefoundry.__file__was confirmed to resolve to this worktree and to contain the change before any result was trusted -- an editable install can be a copy, and green over the wrong tree means nothing.Ledger
#1640 is not edited here. Banner text for the Lander: