Skip to content

fix(canary): B-01 queue-status coherence — backend-consistent Side B + confirm-re-read (#1450)#1495

Merged
vybe merged 4 commits into
devfrom
AndriiPasternak31/issue-1450
Jul 7, 2026
Merged

fix(canary): B-01 queue-status coherence — backend-consistent Side B + confirm-re-read (#1450)#1495
vybe merged 4 commits into
devfrom
AndriiPasternak31/issue-1450

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Fixes #1450

Summary

Canary B-01 (queue-status coherence) collected its two compared values from two reads that were neither temporally atomic nor backend-consistent. Latent on SQLite (canary default-OFF, both reads hit the same file), but fatal under the Postgres direction. This is the production-side residue #1446 explicitly deferred (that P1 fixed only the test-harness sys.modules leak).

The two gaps closed

(a) Temporal non-atomicity — the accessor count and the id-list read happened at different instants; a concurrent enqueue/backlog-drain landing between them produced a transient mismatch → spurious critical + green→red Slack alert.

(b) Backend divergence (#300/#1093) — Side B (queued_exec_ids) was collected via raw sqlite3 at DB_PATH, while Side A (db.get_queued_count) honors get_engine()/DATABASE_URL. On Postgres these are two different databases, so B-01 compared Postgres truth against a stale/absent SQLite file.

Fix

  • New Side B queued_ids_via_engine (snapshot.py::_collect_queued_ids_via_engine) reads queued ids through the same get_engine() seam as the accessor — SELECT id/literal 'queued' vs the accessor's COUNT(*)/TaskExecutionStatus.QUEUED enum, so it shares a database but not a code path (stays a genuine coherence check, not a tautology — AC Feature/vector log retention #3). queued_exec_ids stays raw-sqlite for B-02/E-02.
  • One confirm-re-read in collect_snapshot(): on a first-read mismatch, re-read both sides once. A transient race self-resolves (green); a persistent drift survives and still fires. An unconfirmable confirm degrades to a skip, never a false critical (AC Feature/gemini runtime support #2).
  • Engine-read failure → B-01 skip (queued_ids_via_engine=None), recorded in sources_unavailable — never falls back to the raw set, which would re-arm gap (b) on Postgres.
  • No cache / second representation of the count added (AC fix: add missing logging_config.py to backend Dockerfile #4 — that's the drift B-01 guards).

Collector is now half-migrated (B-01's queued id-set is engine-backed; running-side/known-agents/orphan reads stay raw-sqlite — invisible on SQLite, tracked for the collector-wide migration follow-up).

Tests

tests/test_canary_invariants.py (all 97 pass):

  • test_b01_backend_consistency_on_diverged_backend — new canary_db_split fixture models a Postgres-style diverged backend (raw file ≠ engine file) without a live PG; proves B-01 stays green when the two backends diverge (AC Setup improvements #5).
  • test_b01_tolerates_transient_race — confirm-re-read absorbs a between-reads race.
  • test_b01_confirm_does_not_mask_persistent_drift — confirm does not hide a real regression.
  • test_b01_skips_on_engine_read_failure / test_skips_when_engine_ids_none — engine-read failure degrades to skip.
  • Adds _STUBBED_MODULE_NAMES + autouse _restore_sys_modules — file is now lint-clean under tests/lint_sys_modules.py (the bug: canary B-01 queue-status coherence violation fires under full-suite load — snapshot race or real projection gap #1446 discipline).

Docs

  • architecture.md — B-01 row updated (both sides via get_engine(), confirm-re-read, engine-fail skip, half-migrated note).
  • feature-flows.md — Recent Updates row.

Validation

🤖 Generated with Claude Code

AndriiPasternak31 and others added 3 commits July 6, 2026 22:38
Two pre-existing breakages landed via #1472 (367a5e1, merged today) in
root-level tests/test_canary_invariants.py — a file the CI unit job
(tests/unit/ only) does not run, so they merged undetected:

1. Duplicate `CREATE TABLE agent_schedules` in the `canary_db` fixture DDL
   — the E-06 work added a second definition next to the pre-existing one,
   so `executescript` raised "table agent_schedules already exists" and
   every fixture-dependent canary test errored. Removed the older, unused
   block (its `message`/`owner_id` columns have no consumer; `_add_schedule`
   and the E-06/L-03 collectors use only the kept block's columns).
2. `test_run_invariants_all` expected registry set omitted "E-06" (added to
   the invariants registry by the same PR). Added E-06 to the expected set
   and asserted it is green on a clean platform.

Restores a green baseline (92 passed) so the #1450 B-01 work can be verified.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…+ confirm-re-read (#1450)

B-01 compared two reads that were neither temporally atomic nor
backend-consistent (both latent while the canary is default-OFF and SQLite
makes the two reads hit one file):

(a) Temporal non-atomicity — a concurrent enqueue/backlog-drain landing
    between the two reads produced a transient count mismatch → spurious
    critical + green→red Slack alert.
(b) Backend divergence (#300/#1093) — Side A (`db.get_queued_count`) honors
    `get_engine()`/DATABASE_URL; Side B read raw sqlite3 at DB_PATH. On
    Postgres those are two different databases, so B-01 compared Postgres
    truth to a stale/absent SQLite file (fatal under the Postgres direction +
    SQLite EOL, #1278).

Production-side residue of #1446 (PR #1452), which fixed only the test-harness
`sys.modules` leak and deferred these two gaps.

Fix (localized to B-01):
- New `_collect_queued_ids_via_engine` reads B-01's Side B through the SAME
  `get_engine()` seam as the accessor, on a dedicated `queued_ids_via_engine`
  snapshot field. Independent code path (SELECT id/literal 'queued' vs
  COUNT(*)/the QUEUED enum) — shares a database, not a code path, so a
  cache/status-filter regression still surfaces (non-tautology, AC #3). No
  cache / second count of the queue (AC #4).
- The collector performs one confirm-re-read on a mismatch: a transient race
  self-heals; a persistent drift survives and fires (AC #2). An engine-read or
  unconfirmable confirm degrades to a B-01 skip — it never compares an engine
  count against the raw-sqlite id-set (the blocker the reviews surfaced).
- `queued_exec_ids` (raw sqlite) stays untouched for B-02/E-02, so blast
  radius is B-01-only and the sibling #1077 merge stays clean.

The running-side / known-agents reads remain raw-sqlite (half-migrated
collector); the collector-wide migration + a dark-canary tripwire are a filed
follow-up.

Tests (test_canary_invariants.py): retarget the synthetic B-01 tests at
`queued_ids_via_engine`; add the AC #5 regression net — a diverged-backend
proxy (raw ≠ engine temp files) that false-fires pre-fix and is green
post-fix, a transient-race confirm-absorb + persistent-drift-still-fires pair,
and an engine-read-failure→skip test. New split fixtures (`canary_db_split` /
`reload_canary_split`) model the diverged backend without a live PG. Convert
the file to the sanctioned `_STUBBED_MODULE_NAMES` + `_restore_sys_modules`
escape hatch so its import-time stubs no longer leak (the #1446 mechanism) and
the reimport `del`s are lint-clean.

Docs: architecture.md Canary B-01 row updated for the engine-backed read path
+ confirm-re-read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sync-feature-flows: canary-internal change, no dedicated flow doc
(architecture.md is canonical); append one Recent Updates row.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	docs/memory/feature-flows.md

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated: Fixes #1450 ✅, focused 5-file diff. Side-B migration to the get_engine() seam is correct and the non-tautology property is preserved (SELECT id + literal 'queued' vs COUNT(*) + enum). Confirm-re-read races degrade to skip, never a spurious critical — right call. Resolved the feature-flows.md index conflict against dev (kept both rows) and pushed the merge; ran tests/test_canary_invariants.py locally on the merged branch — 97 passed.

@vybe
vybe enabled auto-merge (squash) July 7, 2026 08:35
@vybe
vybe merged commit 994576a into dev Jul 7, 2026
18 checks passed
@vybe
vybe deleted the AndriiPasternak31/issue-1450 branch July 7, 2026 08:45
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.

2 participants