feat(types): pyright zero-diagnostic gate — 568-diagnostic ratchet burned to zero, warnings_strict flips to Met (#197, final) - #240
Merged
Conversation
… return type PgStoreHost (declaration-only, TYPE_CHECKING-guarded) makes the implicit mixin->store contract explicit per DIP §5.1: every self._execute / self._conn / self.batch_pool call in the eight persistence mixins now checks against the real signature. _MaterializedCursor moves to pg_store_host.MaterializedCursor and _execute's declared return type stops lying (it never returned a psycopg.Cursor): rows are DictRow, so row["column"] access type-checks throughout the mixins. The honest fetchone() -> DictRow | None surfaced ten unchecked INSERT..RETURNING sites (previously typed as never-None); they now use MaterializedCursor.one(), which raises with the real cause when the database breaks the row-guaranteeing contract instead of failing later on a bare None. Also removes the five dead per-mixin _normalize_memory_row shims (PgMemoryStore's own definition always wins the MRO; the shims were unreachable) per §9. Pyright (standard, 1.1.410): 568-baseline measured 418 on main in the CI-equivalent env; this commit brings it to 229. Refs #197 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…backend conn contract, compat executemany parity, lastrowid honesty - StoreConnection (db_types.py): the 16 shared query modules (pg_store_wiki_*, pg_store_memory_*, near_dup, lesson_promotion) were annotated psycopg-only while also serving PsycopgCompatConnection on SQLite; the union names the real contract, so both halves type-check at every call site. - Latent bug: _CompatExecutingCursor had no executemany, so upsert_page_sources (wiki page provenance) raised AttributeError on the SQLite backend — same silent-degradation class as #206. Added with SQL translation. - Latent bug: pipeline_installer accepted a rust_install result whose action claimed success but carried no cargo path, flowing None into the build argv; now refused as missing_toolchain. - lastrowid honesty: sqlite3's lastrowid is int|None; the compat layer claimed int. Insert paths now raise ProgrammingError on the broken contract instead of masking it (one pre-existing type: ignore removed). - MCPClient declares _extra_allowed_commands (was runtime attr injection + getattr fallback — §7.2); ap_bridge/mcp_client_pool assignments now type-check. - psycopg LiteralString query typing: batch/staging sink SQL pinned as LiteralString; allowlist-gated dynamic SQL (dedup/concepts/drafts) re-asserts literalness via cast at the S608-documented sites; get_all_ddl re-asserts it once after comment-stripping. - wiki_stats raises on an impossible empty aggregate instead of returning None as dict; _entry_row narrows its tuple union via match (len()-vs-constant cannot narrow); dead hasattr-fallback branches removed where the compat cursor already guarantees dict rows; msvcrt/fcntl split now guarded by sys.platform so the checker resolves it per-platform. pyright (standard, 1.1.410): 229 -> 125; mcp_server/infrastructure/ = 0. Refs #197 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…onest report types, capability contracts Store parity (the largest latent-bug class this family surfaced — every site below raised AttributeError on the SQLite backend, swallowed by broad stage boundaries into silent degradation, the FlashRank shape): - SqliteMemoryStore gains acquire_interactive/acquire_batch (yielding the store's single WAL connection — the exact shape PgMemoryStore's POOL_DISABLED path yields), _execute (translating passthrough), search_newer_neighbors (numpy cosine parity of the pgvector query), update_forgetting_pressure_accum, get_memories_by_tag (json_each containment), iter_memories_for_decay (single-chunk parity of the PG kill-switch path), find_co_accessed_pairs (same memory_entities self-join). Callers: anchor, get_rules, backfill, codebase_analyze, consolidation (sleep/forgetting/homeostatic/memify/cascade), ingest_findings, remember_helpers. - PG-only capabilities became explicit runtime-checkable protocols with named degraded modes: procedural skills (recall_skills), fallback re-embed worklist (embedding_upgrade). write_gate/pg_recall keep hasattr guards (their observability tests pin MagicMock seams, which 3.12+ protocol isinstance — getattr_static — correctly rejects). Honest annotations replacing checked-off lies (each switched type checking back on for every caller): - EmbeddingEngine.encode returns bytes, not list[float] (compression); fractal scoring takes bytes end-to-end (recall_hierarchical); compute_centroid accepts the None-bearing lists it already handles; emergence/interference reports carry labels, not float-only dicts; update_style_ema pins its dict contract ((None, None) returned None). - MemoryStore construction in hook processes goes through get_shared_store (the factory the module itself mandates); the TYPE_CHECKING union alias is no longer called. - psycopg connect sites parameterize the class (Connection[DictRow].connect) so dict rows type through; hook tests retargeted to the consumer's actual call path (#238 seam rule). - safe_handler accepts Awaitable-returning callables (what HandlerFn is); max(d, key=d.get) rewritten with lambdas (the None-default overload poisons the key type); frontmatter object-reads narrowed at each seam (wiki_migrate/schema_loader/sync/identity/draft_compiler); the synthesized fake-Node hack in ast_extractors replaced by a child-list dispatch helper (§7.2). Latent bugs fixed beyond the store parity: - sparse_dictionary.encode_session: a direction-less feature now fails loudly with its index/label instead of a bare TypeError inside omp (positional alignment forbids skipping). - get_causal_chain: an absent start entity produced reason=None; now always a named reason. - forgetting: a row without created_at is stated to have no newer neighbors (was an implicit SQL NULL-comparison no-op). pyright (standard, 1.1.410): 125 -> 0 on mcp_server/. Refs #197 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…x shifts Same writers, no new ones: the pg_store MaterializedCursor move and the sqlite_store parity-method insertions shifted seven pinned line numbers (pg_store 720->694/762->736/860->834, sqlite_store 537->543/567->573/ 631->637, pipeline_impact_bump 177->182). Verified each target is the identical pre-existing writer. Refs #197 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- test_sqlite_parity_197: the full SQLite parity surface (acquire_*,
_execute translation + untranslatable-SQL degraded mode,
get_memories_by_tag containment/recency/stale/substring,
iter_memories_for_decay chunk parity, find_co_accessed_pairs
ordering/dedup, search_newer_neighbors newer-only/self-exclusion/
similarity order, update_forgetting_pressure_accum persistence),
compat executemany translation, the lastrowid-None ProgrammingError,
and MaterializedCursor.one (row + guaranteed-row violation).
- update_style_ema(None, None) returns {} (was None against a dict
signature).
- encode_session refuses a direction-less Feature loudly, naming it
(was a bare TypeError inside omp).
- pipeline installer: a rust_install result claiming success without a
cargo path is refused as missing_toolchain (was None in the argv).
- forgetting: a memory without created_at yields no newer neighbors and
never queries the store with a None bound.
Refs #197
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uild; flip warnings_strict to Met (#197) The 568-diagnostic backlog is zero, so the ratchet's only purpose (tolerating a backlog without letting it grow) is gone: - typecheck-baseline.json and scripts/check_pyright_ratchet.py deleted; the CI typecheck job runs `pyright mcp_server/` and fails on ANY diagnostic via pyright's own exit code. The type-check env adds the [otel] extra so the exporter imports resolve (their absence was 4 spurious reportMissingImports). - .bestpractices.json: warnings_strict -> Met, citing configuration, not promise: the explicit ruff select list (E4/E7/E9/F/S110/BLE001/PLR2004/E501/PLC0415/S608), pyright standard mode, and the measured zero-diagnostic count (2026-07-28). coding_standards_enforced updated off the retired ratchet. - docs/ASSURANCE-CASE.md: the warnings-strictness caveat is replaced by the factual posture (zero at standard; strict remains an annotation-coverage project, 10,231 errors measured). - CONTRIBUTING.md, docs/provenance/pyright-remediation-plan.md: ratchet references closed out; the plan is marked historical. - One narrowing fix (context_assembly/coverage.py): bind the candidate embedding before branching — variable-index subscripts do not narrow across statements, and the un-narrowed Optional reached np.dot. - Mutation run on the new sqlite parity queries (scoped, scripts/mutation_check.sh): 0 surviving non-equivalent mutants — the one real survivor (default limit 20->21) is now pinned by test_default_limit_caps_at_twenty; the 21 documented equivalents are SQL keyword/identifier case mutants (SQLite is case-insensitive there) and the deliberately-unused chunk_size signature-parity default. - Advertised test count synced 6275 -> 6297 everywhere the doc-claim gate checks; CHANGELOG entry added. Closes the acceptance criteria of #197. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… criterion 1) Three stated exclusion classes in [tool.ruff.lint], each with measured counts (mcp_server/, 2026-07-28): rejected style-police families (D/ANN/COM/TD/I — the checking they promise is owned by review, pyright, the formatter, issue governance, and PLC0415 respectively), rewrite-suggestion families (UP/C4/SIM/PIE/RSE/PERF/RUF — transformations of working code outside this program's defect classes), and the untriaged judgement families (B/TRY/RET/ARG/PTH/N/ERA/FBT/DTZ/S-rest) now tracked as the program's continuation in #239 — a family stays out deliberately or lands with every finding triaged, never by accident. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ability TestSearchNewerNeighbors exercises the vector path, which the PG-matrix CI jobs (and any Python built without extension support) cannot load — they got the documented empty degraded result and failed. Reuses the _needs_vec skip idiom from test_semantic_fallback_169.py; the dedicated SQLite job (extension present) still executes the class for real. Co-Authored-By: Claude Fable 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.
Final PR of the #197 maximal-strictness program, following #230 (S110), #232 (BLE001), #234 (PLR2004/E501) and #238 (PLC0415/S608): the 568-diagnostic pyright ratchet backlog is burned to ZERO at
typeCheckingMode: "standard"(pyright 1.1.410), the ratchet is retired — CI now fails on ANY pyright diagnostic — and.bestpractices.jsonflipswarnings_strictto Met, citing configuration rather than promise.What this PR does
The burn-down was fixes, not annotations-to-match
Re-measured on this branch's base in the CI-equivalent env (
.[dev,postgresql,sqlite,codebase,otel]+ flashrank, pyright pinned 1.1.410): 418 errors (the committed 568 floors dated from a less-resolved env). Every one was traced to its cause; no rule was disabled, no floor raised, and the only per-site suppression in the tree is the unpublished optionalcortex_beam_abstainimport whoseexcept ImportErrorarm is the documented degraded mode.The classes, each a real contract defect the wrong types had been hiding (the memory rule this program keeps proving: a wrong annotation switches type-checking off for every caller):
PgMemoryStoremixins calledself._execute/self._conn/self.batch_poolwith no declaration.pg_store_host.PgStoreHost(declaration-only, TYPE_CHECKING-guarded, zero MRO change) makes the contract explicit per DIP §5.1; five dead per-mixin_normalize_memory_rowshims removed._execute's return-type lie (~60 downstream). Declared-> psycopg.Cursor, actually returned the materialized dict-row surrogate.MaterializedCursor(moved to the host module, rows typedDictRow) makesrow["column"]check everywhere — and the honestfetchone() -> DictRow | Nonesurfaced ten uncheckedINSERT..RETURNINGsites, nowMaterializedCursor.one()which raises with the real cause when the database breaks the row-guaranteeing contract.pg_store_wiki_*,pg_store_memory_*,near_dup,lesson_promotion) served both psycopg connections andPsycopgCompatConnectionwhile typed for one.db_types.StoreConnectionnames the real union; both halves now check at every call site.AttributeErroron the SQLite backend, swallowed by broad stage boundaries into silent degradation (the FlashRank shape):acquire_interactive/acquire_batch,_execute,search_newer_neighbors,update_forgetting_pressure_accum,get_memories_by_tag,iter_memories_for_decay,find_co_accessed_pairs. All implemented with PG-parity semantics (numpy cosine for the pgvector query,json_eachcontainment for@>, the vec-table client-side join idiom). PG-only capabilities (procedural skills, fallback re-embed worklist) becameruntime_checkableprotocols with named degraded modes.connect()returnsSelf, soRowbinds at the class:psycopg.Connection[DictRow].connect(...)at every connect site (pg_store, hooks, migrate); pools parameterizedConnectionPool[Connection[DictRow]].LiteralStringquery discipline (~15). psycopg 3.3 typesexecute(query)againstLiteralString; one cast chokepoint in_execute_on_connplus per-site casts at the S608-documented allowlist-gated builders, each naming its mechanism.encodereturns bytes notlist[float]; emergence/interference reports carry labels),object-typed frontmatter narrowed at each seam,max(d, key=d.get)rewritten (the None-default overload poisons the key type),hasattr-vs-protocol chosen per seam (MagicMock-pinned observability tests keephasattr; real-store capability checks use protocols — 3.12+runtime_checkableresolves members viagetattr_static), the synthesized fake-Node hack inast_extractorsreplaced by a child-list dispatch helper (§7.2),MemoryStore(...)construction in hook processes routed throughget_shared_store(the factory the module itself mandates).Latent bugs fixed in-PR (each with a regression test)
executemany→ every SQLiteupsert_page_sources(wiki page provenance) crashed; added with SQL translation.lastrowidhonesty: sqlite3'slastrowidisint | None; the compat layer claimedintand one pre-existingtype: ignoremasked it. Insert paths now raiseProgrammingErroron a broken row-id contract.Noneinto the build argv; refused asmissing_toolchain.update_style_ema(None, None)returnedNoneagainst adictsignature.encode_sessiondied with a bareTypeErrordeep inompon a direction-less feature; now refuses loudly naming the feature (positional alignment forbids skipping).get_causal_chaincould returnreason=None; forgetting sent aNonetimestamp into the store (now: no timestamp → no newer neighbors, stated).Gate rewiring, docs, flip
typecheck-baseline.json+scripts/check_pyright_ratchet.pydeleted; the CI typecheck job ispyright mcp_server/failing on its own exit code; the env adds[otel]so the exporter imports resolve.pyproject.tomldocuments every excluded ruff family with a measured count and a stated reason (criterion 1): rejected style-police families (D/ANN/COM/TD/I), rewrite-suggestion families (UP/C4/SIM/PIE/RSE/PERF/RUF), and the untriaged judgement families tracked as the program's continuation in Extend the maximal-strictness lint program: remaining judgement families (B, TRY, RET, ARG, PTH, N, ERA, FBT, DTZ, S-rest) #239.docs/ASSURANCE-CASE.mddrops the strictness caveat (criterion 4);CONTRIBUTING.mdand the remediation plan close out the ratchet;.bestpractices.jsonflipswarnings_strictto Met quoting the select list, the pyright mode, and the measured zero (criterion 5).tests_py/invariants/test_I2_canonical_writer.pyre-pinned in its own commit (same writers, seven line shifts, each verified identical).Mutation check (scoped, §12)
scripts/mutation_check.shon the new SQLite parity queries: 0 surviving non-equivalent mutants. The one real survivor (default limit 20→21) is now pinned bytest_default_limit_caps_at_twenty; the 21 documented equivalents are SQL keyword/identifier case mutants (SQLite is case-insensitive there) and the deliberately-unusedchunk_sizesignature-parity default.Evidence
Full suite (sequential directory chunks on the committed tree; DB_PATH/SQLITE_FALLBACK_PATH/CLAUDE_DIR isolated to temp dirs):
Monkeypatch seams retargeted per the #238 rule (tests patch the consumer's actual binding):
test_agent_briefing/test_ble001_sweep_hooksfollow theConnection[DictRow].connect/get_shared_storecall paths.Completion Ledger
§13.1 checklist
test_sqlite_parity_197.py(17 behavior tests)find_co_accessed_pairs([]), zero query vector), duplicates (pair dedup), ordering (similarity/recency order), limit boundary (21-row default-cap test), substring false-positive (x-tag-suffix), stale exclusionOperationalErrorasserted;MaterializedCursor.one()empty → raise asserted; lastrowid-None →ProgrammingErrorasserted; cargo-less install →missing_toolchainasserted; direction-less feature →ValueErrornaming it; observability seams re-verified (test_pg_recall_silent_failures,test_write_gate, ble001 sweeps green)one(),executemany, parity methods,PgStoreHost,StoreConnection); I2 invariant re-pinned and green (52 passed)acquire_*yield the store's existing single WAL connection (the documented shape of PG's POOL_DISABLED path)get_memories_by_tagusesjson_eachEXISTS instead of a full-table Python filter;search_newer_neighborsdocuments the established client-side vec-join idiom;iter_memories_for_decaysingle-chunk rationale stated (local-install corpus, matches PG kill-switch path)pip cache purgeafter installsobject-typed reads narrowed with isinstance before use (wiki_migrate/schema_loader/sync/identity)one()); no signature of an existing public tool changedsys.platformliteral (checker resolves per-platform; behavior identical —IS_WINDOWSis defined as exactly that comparison)recall_skillsbackend log,embedding_upgradereason field,_executeOperationalError to the documented mechanism boundaries) — each asserted"store has no fallback worklist","backend has no procedural-skills store",requires_postgresqlwiki_view guard (now a TypeGuard)test_sqlite_parity_197.py, style/sparse/installer/forgetting additions)_normalize_memory_row×5, hasattr dances ×5,_ENTRY_WITH_CONFIDENCE, staletype: ignore)acquire_*shape reused so the parallel PR merges clean.bestpractices.jsonall updated in this PRtype: ignore[return-value], the bare hasattr dances, the coverage.py narrowing miss); the one out-of-blast-radius deferral is filed: #239 (remaining lint families), plus pre-existing #233/#237 cycles untouched by this diffFixed type-error classes (one row each)
_executeon Pg mixins (115)PgStoreHosttyped contract__mro__probe in commit)__getitem__/row-typing (~60)MaterializedCursor[DictRow]+ honest_executereturntests_py/infrastructure660→green pre-commitone()raising contractTestMaterializedCursorOneboth armsStoreConnectionin 16 modulestest_sqlite_parity_197.py18 tests; scoped mutation 0 non-equivalent survivorstest_semantic_fallback_169,test_procedural_skill_wiringgreenConnection[DictRow].connectFinalDDL provenance)safe_handleracceptsAwaitableMemoryStore(...)(3)get_shared_storeobjecttyping (~40)Acceptance criteria of #197
selectexplicit and broad; every excluded family carries a measured, stated reason (pyproject block; continuation tracked in Extend the maximal-strictness lint program: remaining judgement families (B, TRY, RET, ARG, PTH, N, ERA, FBT, DTZ, S-rest) #239)ruff check .clean; CI fails on any findingstandard(abovebasic) at zero — floors didn't just hold, they ceased to existwarnings_strict→ Met, citing the configuration and the measured zero🤖 Generated with Claude Code
Closes #197.