Repository navigation
fix: job event memory, artifact review gate, e2e port, dev-environment advisories - #326
Merged
Merged
Conversation
…ir bytes The live-reader headroom lets the event buffer grow past its retention limits so an attached SSE reader does not lose events it has not read yet. Two gaps made that headroom outlive its purpose or ignore its budget: - Eviction ran only inside _append_event. When the reader detached, nothing re-trimmed, so a finished job (which never publishes again) kept up to max_event_count * LIVE_READER_EVENT_HEADROOM events for as long as it stayed in the registry. - The protection condition checked only the event count, so under a live reader event_bytes was never compared with anything; at the defaults that is 2048 retained events of up to 1 MiB each. The eviction loop is now _trim(record). It runs after every append and in the finally of events() once the reader's cursor is removed, and the protection also requires event_bytes to stay within max_event_bytes * LIVE_READER_EVENT_HEADROOM. Plan item: BE-13 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…il closed tools/artifact_boundary_review.py had four defects that made its gate weaker than it read: - corpus_digest hashed a separate tuple of case names, so deleting or rewriting a case left the digest unchanged. It is now computed from the _CASES tuple that runs: each case's name and source in run order, plus the shared fixture helpers. - Any non-pass was reported as "blocked", so a boundary that accepted a hardlink read like a host limitation. The status is now "failed" when any case failed, "blocked" only when a case could not run, and "passed" only when every case ran and passed. An empty corpus is a failure. - --json changed only the separators; both modes printed JSON. Without it the tool now prints a readable summary that names each failed or blocked case; with it, compact sorted JSON as before. - Without --strict a total failure exited zero. Strict is now the default: 1 for failed, 2 for blocked. --strict is still accepted, so check.ps1, the pre-push hook and the workflow keep working unchanged. The twelve-case corpus itself is unchanged. Plan item: EX-26 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/check_dev_environment.py only compared installed versions with the declared specifiers. The setups that drift quietly even when every pin matches went unreported: system Python with no virtual environment (here, 3.14 with a large unrelated site-packages), an interpreter other than the 3.11 the CI locks target, and an import name of a declared dependency that resolves to code that dependency did not install. It now prints advisories for all three before the pin result. They never change the exit code. The lock target is read from the lock header rather than hard-coded. The shadowing check locates each declared distribution's top-level import names on the path the tests and the app use (repository root and backend/ in place of scripts/) and compares the file found with the distribution's RECORD. Lookup only locates modules; nothing is imported. On this machine it reports a real case: `import py` loads the separate `py` distribution, not the shim pytest installs. Plan item: QA-24 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and its own chat The real-API Playwright spec started scripts/e2e_backend.py on 8799 with reuseExistingServer outside CI, and the README screenshot server defaults to the same port. Anything left answering there was silently reused as "the real API", and its generation test asserted on the first element of the chat list, which depends on what earlier runs left behind. - frontend/e2e/e2e-backend.json holds the port (now 8798) and the marker. playwright.config.ts (through e2e/e2eBackend.ts) and e2e_backend.py both read it, so the port is written down once. - e2e_backend.py adds an X-Cortex-E2E-Backend header to its liveness route only. - Playwright's readiness probe only sees a status code, so real-api.spec.ts checks the marker before each test, both on the backend port and through the dev server's proxy, and refuses with a clear message when it is missing. - The generation test sends a prompt unique to the run, takes the thread id from the server's acceptance of that send, and asserts on that chat. Plan item: QA-15 Co-Authored-By: Claude Opus 5.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.
Problem
Four small verified defects, one commit each:
--jsononly changed separators;--strict, a total failure exited 0.reuseExistingServeroutside CI. Anything left listening there was silently tested as "the real API".scripts/check_dev_environment.pynever noticed three setups: system Python with no virtual environment, an interpreter other than the lock target, or a package that shadows a declared one.Root cause and change
_append_event, and a terminal job never appends again. The eviction loop is now_trim(record), called after every append and in thefinallyofevents()once the reader's cursor is gone. The live-reader protection now also requiresevent_bytes <= max_event_bytes * LIVE_READER_EVENT_HEADROOM._CASEStuple that runs: each case's name and source in run order, plus the shared fixture helpers.failedif any case failed,blockedonly if a case could not run, andpassedonly if every case ran and passed. An empty corpus fails.--strictis still accepted, soscripts/check.ps1, the pre-push hook andquality.ymlare unchanged.--jsonthe tool prints a readable summary naming each failed or blocked case.frontend/e2e/e2e-backend.jsonholds the port (now 8798) and a marker.playwright.config.ts(throughe2e/e2eBackend.ts) andscripts/e2e_backend.pyboth read it.X-Cortex-E2E-Backendheader to its liveness route only.real-api.spec.tschecks the marker before each test and refuses a server that lacks it.Compatibility and rollback
--strict. Every caller passes--strictand checks for non-zero.Checks
Run on Windows, Python 3.14 unless stated.
python -m pytest -q tests/test_job_registry_live_readers.py tests/test_artifact_boundary_review.py tests/test_check_dev_environment.py tests/test_e2e_backend.pygives 20 passed. Same files on Python 3.12 (py -3.12): 20 passed.origin/main(00b76b0) give 16 failed, 4 passed. The 4 that pass on both are guards (for example, a reader that is still attached keeps its unread events).python scripts/check_dev_environment.pyon this machine prints three advisories (no venv, 3.14 vs the 3.11 lock, a shadowingpypackage) and exits 0.scripts/check.ps1, quick tier (ruff, actionlint/zizmor, mypy, full backend pytest, artifact boundary review, contract check, tsc, eslint, vitest): all 11 checks passed in 381.2 s.real-api.spec.ts: not run locally; the CI "Browser tests" job runs it.Security, concurrency, data loss
Limits
Plan item: BE-13, EX-26, QA-15, QA-24
🤖 Generated with Claude Code