Skip to content

feat(OMN-20329): the projection read node serves the local SQLite store - #3212

Merged
jonahgabriel merged 3 commits into
devfrom
jonah/omn-20329-readnode-sqlite
Oct 2, 2026
Merged

jonahgabriel merged 3 commits into
devfrom
jonah/omn-20329-readnode-sqlite

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

OMN-20329. node_projection_read_effect now serves contract-declared exposures from the local SQLite store when the runtime projection binding's database_url is a sqlite: or file: DSN (local MVP mode 1). Decision D1 (a) routes the local dashboard's reads through this node (OMN-20159), so this is the read path for that mode. Before this change the resolver refused every non-Postgres binding with projection_binding_unsupported.

  • ports/sqlite_row_source.py: SqliteTableRowSource serves the same window, tenant scoping, row shape and named refusals as the Postgres TableRowSource, in SQLite's dialect. It opens the file read-only per read and never creates a missing file (projection_database_unavailable).
  • Before reading, it checks the table and every column the read names against pragma_table_info. SQLite reads a double-quoted unknown column as a string literal, so without the check a missing column is served as its own name. It answers projection_column_missing instead.
  • resolve_projection_read_source: postgres/postgresql resolve to TableRowSource unchanged; sqlite/file resolve to SqliteTableRowSource; any other scheme is still refused projection_binding_unsupported; no binding is still projection_binding_unconfigured (no fallback).
  • sqlite_path_from_dsn moves to the shared projection/sqlite_database.py, and the delegate orchestrator's evidence DB resolver imports it instead of keeping a private copy, so the writer and the reader resolve a DSN to the same file.

dod_evidence

  • tests/unit/nodes/node_projection_read_effect/ (incl. new test_projection_read_effect_sqlite_omn20329.py), test_evidence_db_resolution_omn14015.py, test_golden_chain_projection_read_effect.py: 39 passed on lab host h202.
  • tests/integration/nodes/node_projection_read_effect/test_projection_read_effect_real_postgres_omn20159.py: 5 passed against a disposable postgres:16-alpine on h202, including the new case proving a Postgres binding still reads and close() still drains its pools.
  • Real local store on h202 (~/.omninode/delegation/delegation.sqlite, 22 delegation_events rows, one tenant), read through the node with a sqlite: binding: before the catalogue check the decisions exposure answered ok with 5 rows; after it, ok=False 503 projection_column_missing. That store lacks id, authority_source, score_source, cost_tier_name and cost_tier_type, which the exposure declares. Making the local writers declare those columns is the plan's T3.0 store-neutral writer work, not this change.
  • Dev lane binds Postgres and that path is unchanged, so no .201 lab step applies.
  • ruff format/check clean, mypy --strict clean on touched files, pre-commit passed.

Evidence-Ticket: OMN-20329
Evidence-Source: OCC#12231

node_projection_read_effect now answers contract-declared exposures from the
local SQLite store when the runtime projection binding's database_url is a
sqlite: or file: DSN (local MVP mode 1). Decision D1 (a) routes local
dashboard reads through this node (OMN-20159).

SqliteTableRowSource (the node's ports/sqlite_row_source.py) serves the same
window, tenant scoping, row shape and named refusals as the Postgres
TableRowSource, in SQLite's dialect. It opens the file read-only per read and
never creates a missing file (projection_database_unavailable).

Before reading, it checks the table and every column the read names against
pragma_table_info. SQLite reads a double-quoted unknown column as a string
literal, so without the check a missing column is served as its own name.
It answers projection_column_missing instead.

Postgres bindings resolve to TableRowSource unchanged; other schemes are
still refused as projection_binding_unsupported, and no binding is still
projection_binding_unconfigured. sqlite_path_from_dsn moves to the shared
projection/sqlite_database.py, and the delegate orchestrator's evidence DB resolver
imports it instead of keeping a private copy.

The real-Postgres integration test gains a case proving a Postgres binding
still reads and that close() still drains the source's pools.
@github-actions

github-actions Bot commented Oct 1, 2026

Copy link
Copy Markdown

✅ Architectural Review — PASSED

Errors: 0
Warnings: 0

What this checks

Rule Description
ARCH-TOPIC-001 No hardcoded Kafka topic strings in handler code
ARCH-DI-001 No event_bus=None bypass
ARCH-DI-002 No direct Handler instantiation outside workflow_runner/adapters
ARCH-DI-003 No reinvented DI containers
ARCH-TOPIC-002 contract.yaml topics follow `onex.{cmd

No architectural violations found.

Static architectural lint — no model inference (OMN-14176).

@github-actions

github-actions Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

✅ Hostile Reviewer — PASSED

Blocking findings (critical/error): 0
Total findings: 12
Models succeeded: qwen3-review,gpt-oss-review


Gate semantics

Verdict Meaning Blocks merge?
passed >=2 models succeeded, no critical/error findings No
blocked CRITICAL or ERROR findings found Yes
degraded Fewer than 2 models succeeded (infra unavailable/timeout) Yes (OMN-15110)

Powered by omniintelligence.review_pairing.cli_review — node-based adversarial review via HandlerLlmCliSubprocess (OMN-8524)

Moved marker comment from closing-paren line to sqlite3.connect( line in
src/omnimarket/nodes/node_projection_read_effect/ports/sqlite_row_source.py
to satisfy imperative-contract-guard (CI run 36924302867). Verified locally:
live 0, ruff format clean, sqlite unit test file passes (11 tests). No behavior change.
Ticket OMN-20329.

Bumped project.version in pyproject.toml and uv.lock from 0.4.276 to 0.4.277
to satisfy release-identity gate (failing at a14ebdb).
@jonahgabriel
jonahgabriel merged commit d471dcb into dev Oct 2, 2026
146 of 147 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-20329-readnode-sqlite branch October 2, 2026 00:03
Patel230 added a commit that referenced this pull request Oct 2, 2026
omnimarket#3208 gave ProtocolProjectionRowSource a `selection` on rows() and a
walk_origin(); #3212 landed SqliteTableRowSource four minutes later without
either. On dev every read_page call against the local store raised TypeError
(5 of 11 OMN-20329 tests fail) and mypy rejects handler_projection_read.py:99.

- rows() takes `selection`: "walk" windows from the first cursor ascending,
  "ranked" windows by the contract's order, "newest" is unchanged.
- walk_origin() answers one below the tenant's smallest integer cursor, None
  for any other cursor, and refuses a missing table or store by name.
jonahgabriel added a commit that referenced this pull request Oct 2, 2026
Merged origin/dev (d471dcb) into the OMN-19513 branch.
Brings in #3212 (OMN-20329), the SQLite row source for the projection read node.
No textual conflicts were encountered during the merge.
jonahgabriel added a commit that referenced this pull request Oct 2, 2026
dev was excluded from the CI push triggers, and dev is not strict and has no
merge queue. #3212 merged on CI graded against a base older than #3208, so dev
head d471dcb broke typecheck and Tests (Split 14/20) with no check run on it
until the release fast-forward to main. This adds dev to push.branches, matching
omnibase_infra, omnibase_core and omniclaude; the push concurrency group is
ref-scoped and never cancels.
jonahgabriel added a commit that referenced this pull request Oct 2, 2026
dev was excluded from the CI push triggers, and dev is not strict and has no
merge queue. #3212 merged on CI graded against a base older than #3208, so dev
head d471dcb broke typecheck and Tests (Split 14/20) with no check run on it
until the release fast-forward to main. This adds dev to push.branches, matching
omnibase_infra, omnibase_core and omniclaude; the push concurrency group is
ref-scoped and never cancels.
Patel230 added a commit that referenced this pull request Oct 2, 2026
…te store (#3210)

* feat(OMN-19968): the tenant credentials writer runs on the local SQLite store

node_projection_tenant_credentials was the last writer the ticket names with
no SQLite path: its three writes were asyncpg SQL with $n, ::TEXT and NOW(),
and the local schema had neither of its tables.

- handlers/handler_tenant_credentials_store.py: apply_credential_registered and
  apply_credential_revoked write the catalog row and the routing overlay
  through the store-neutral upsert_returning, keeping every rule the runner's
  SQL encodes: register never touches created_at or revoked_at, the route is
  minted only for a key that is not revoked, a revoke keeps the earliest
  revoked_at and leaves a tombstone for an unknown key, a revoke blanks only
  the route that points at that key, and an undeclared provider is catalogued
  but unrouted. The deployed asyncpg runner is unchanged.
- projection/sqlite_database.py: tenant_inference_credentials and
  delegation_routing_tenant_overlay, declared from the node migrations
  0000/0001 and the overlay migrations 0001/0004.
- contract.yaml: both tables declare access read_write instead of write.
  The runner already reads them (the overlay's WHERE NOT EXISTS revoke guard
  and every RETURNING) and the local path reads before a revoke; the
  projection contract-access gate requires the declaration to say so. The
  checked-in relation inventory is regenerated (the overlay gains this node
  as a reader), and the OMN-17372 contract-shape test now pins that the
  overlay write is declared, as write or read_write.
- tests/test_omn19968_writer_store_parity.py: six scenarios compared between
  the SQLite store path and the REAL asyncpg runner on PostgreSQL, the SQLite
  path checked for Postgres-only tokens, and the tombstone, revoke and
  secret-field rules on SQLite alone.

* fix(OMN-20329): the SQLite row source speaks the OMN-20327 walk protocol

omnimarket#3208 gave ProtocolProjectionRowSource a `selection` on rows() and a
walk_origin(); #3212 landed SqliteTableRowSource four minutes later without
either. On dev every read_page call against the local store raised TypeError
(5 of 11 OMN-20329 tests fail) and mypy rejects handler_projection_read.py:99.

- rows() takes `selection`: "walk" windows from the first cursor ascending,
  "ranked" windows by the contract's order, "newest" is unchanged.
- walk_origin() answers one below the tenant's smallest integer cursor, None
  for any other cursor, and refuses a missing table or store by name.

* chore(OMN-19968): bump version to 0.4.278 past the published 0.4.277

The release identity gate refuses a packaged-source change on an already
published version. 0.4.277 is published; this PR changes packaged source.
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.

1 participant