Skip to content

test(db): prove the statement observer's production path is unchanged - #62

Merged
sebyx07 merged 6 commits into
mainfrom
feat/db-statement-observer
Aug 13, 2026
Merged

test(db): prove the statement observer's production path is unchanged#62
sebyx07 merged 6 commits into
mainfrom
feat/db-statement-observer

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Closes out PR group "The statement observer seam (tier 1, zero prod cost)" — last task in the group.
  • observe.test.ts: with no StatementObserver installed, neither client.ts nor pglite.ts ever reads the clock (the signal an event was about to be assembled) and a silently-installed observer changes nothing about what a statement returns — the "byte-identical runOn behavior" guarantee, proven directly rather than inferred from "the observer saw nothing."
  • Document, don't fix: @ultimat3/jobs has no @ultimat3/db dependency and never will, but the framework's own boot wiring (packages/cli/src/dev-queue.ts's startQueue, reached by every role through serve.ts/dev-runtime.ts) backs the job driver's PgExecutor with a real @ultimat3/db client — so queue statements do pass through the observer today, just unattributed. Corrected this in packages/db/CLAUDE.md and packages/jobs/CLAUDE.md, since the plan's "invisible by design" framing overstated it.

Test plan

  • bun test packages/db/src/observe.test.ts — 10 pass
  • bun test packages/db — 188 pass / 2 skip
  • bun run verify — 12/17 green (5 honest skips: job, eval, drift, contract-diff, budgets)

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added database statement observers capturing SQL, parameters, timing, row counts, errors, and query-loop attribution.
    • Added utilities for marking intentional query loops with explanatory reasons.
    • Added database tracing with low-cardinality span names and SQL statement details.
    • Added embedded database integration coverage for CRUD, transactions, rollbacks, and concurrent operations.
  • Documentation

    • Documented statement observation, tracing, expected query loops, and migration diagnostics.
    • Clarified diagnostic behavior and supported database integrations.

sebyx07 and others added 5 commits August 12, 2026 19:40
- new `observe.ts`: `StatementObserver`/`StatementEvent`/`StatementAttribution`
  plus `setStatementObserver()` / `statementObserver()`, the `setDbClient` shape
- uninstalled is one property read and one branch: the accessor returns the
  installed identity so the call site guards before building an event, and a
  throw from `onStatement` still reaches the caller (strict test mode)
- one observer, not a list — a second install replaces the first (axiom 1)

Co-Authored-By: Claude <noreply@anthropic.com>
- `runOn` (client.ts) and `statement()` (pglite.ts) split into a raw
  `sendOn`/`send` plus an observed shell: guard on the accessor first, so
  uninstalled is one property read and one branch — no clock, no event
- both settle paths emit; a failure carries `rows: 0` and the wrapped
  X_DB_UNAVAILABLE the caller is about to be thrown
- notify outside the statement's own `try`, or a throwing observer would be
  re-reported as a database failure and strict test mode would vanish
- `rows` comes from the helper `execute()` already uses, so the report and the
  return value cannot disagree; reserve, boot and close stay out
- split `pglite-embedded.test.ts` out of `pglite.test.ts` (500-line ceiling):
  fakes pin the adapter, the real WASM boot pins the binding

Co-Authored-By: Claude <noreply@anthropic.com>
- `statement-span.ts`: `withStatementSpan` wraps the send alone — `db.<verb>`,
  OTel kind `client`, the text on `db.statement`
- opened inside the observed branch, so installing a `StatementObserver` is the
  one switch for statement instrumentation and production allocates no span
- `dev-traces.ts`: `db.` joins the prefix table as `sql`, so the `/_x` timeline
  gains DB children and `repeatedSql` groups SQL text instead of span names

Co-Authored-By: Claude <noreply@anthropic.com>
- expectedQueryLoop(reason, fn) rides an AsyncLocalStorage; both funnels
  stamp the innermost reason onto StatementEvent.expected at settle time,
  inside the branch that already found an observer — prod pays nothing
- one mechanism only: no comment pragma, no exempt-call-site list (axiom 1);
  a blank reason is X_INVARIANT, since an exemption with no argument is a pragma
- applied at source to the framework's own deliberate loops: migrate()/rollback()
  (one transaction per migration) and admin search (one indexed lookup per field)
- a verdict is suppressed, never a statement: the SQL still sends, still observes,
  still opens its span

Co-Authored-By: Claude <noreply@anthropic.com>
- observe.test.ts: with no observer installed, neither driver ever reads
  the clock (the signal an event was about to be assembled), and a silent
  observer changes nothing about what a statement returns — closes the
  loop on client.ts/pglite.ts/statement-span.ts's existing "sees nothing"
  coverage by proving the uninstalled branch, not just its output
- document, don't fix: jobs has no @ultimat3/db dependency, but the
  framework's own boot wiring (dev-queue.ts's startQueue, reached by every
  role) backs the job driver's PgExecutor with a real db client, so queue
  statements do pass through the observer today, unattributed — corrects
  the plan's "invisible by design" premise in packages/db/CLAUDE.md and
  packages/jobs/CLAUDE.md

Co-Authored-By: Claude <noreply@anthropic.com>
@sebyx07 sebyx07 added the claudetm Created by Claude Task Master label Aug 13, 2026
@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 37 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 99ee05e6-9e40-4a4c-a020-687fd4c40359

📥 Commits

Reviewing files that changed from the base of the PR and between eb004dd and 524f2a8.

📒 Files selected for processing (17)
  • CHANGELOG.md
  • packages/admin/src/registry.test.ts
  • packages/admin/src/search.test.ts
  • packages/cli/CLAUDE.md
  • packages/cli/src/dev-traces.test.ts
  • packages/cli/src/dev-traces.ts
  • packages/db/CLAUDE.md
  • packages/db/README.md
  • packages/db/src/client.ts
  • packages/db/src/expected-loop.test.ts
  • packages/db/src/expected-loop.ts
  • packages/db/src/index.ts
  • packages/db/src/migrate.test.ts
  • packages/db/src/observe.test.ts
  • packages/db/src/observe.ts
  • packages/db/src/statement-span.ts
  • packages/jobs/CLAUDE.md
📝 Walkthrough

Walkthrough

Changes

Database diagnostics

Layer / File(s) Summary
Diagnostic contracts and scopes
packages/db/src/observe.ts, packages/db/src/expected-loop.ts, packages/db/src/index.ts, packages/db/README.md
Adds statement observer APIs, statement event types, and async-local expected query-loop helpers.
Observed statement execution
packages/db/src/client.ts, packages/db/src/pglite.ts, packages/db/src/statement-span.ts, packages/db/src/*test.ts
Routes pooled and embedded statements through observation and optional database spans. Tests cover events, errors, row counts, attribution, and disabled observers.
Framework query-loop declarations
packages/admin/src/search.ts, packages/admin/src/registry.test.ts, packages/db/src/migrate.ts, packages/db/src/migrate.test.ts
Marks admin search and migration loops with explicit reasons and verifies reason propagation and cleanup.
Embedded database validation
packages/db/src/pglite-embedded.test.ts
Adds real PGlite coverage for CRUD operations, transactions, rollback, and concurrent reads and writes.

Estimated code review effort: 4 (Complex) | ~45 minutes

Mergeability Score: 🔵 Low · up to eb004

The PR adds statement observation while preserving existing query behavior, but events currently omit attribution and an edge case can produce different affected-row counts between database paths. The change is mergeable with explicit owner awareness and follow-up on these bounded observability and consistency risks.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant StatementSpan
  participant DatabaseDriver
  participant StatementObserver
  Client->>StatementSpan: send SQL statement
  StatementSpan->>DatabaseDriver: execute statement
  DatabaseDriver-->>StatementSpan: return rows or error
  StatementSpan->>StatementObserver: report settled event
  StatementObserver-->>Client: propagate observer result or error
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: tests that verify the statement observer production path remains unchanged.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/db-statement-observer

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/admin/src/registry.test.ts`:
- Around line 201-235: Move the test named “search declares its per-field loop,
and the scope ends with it” from registry.test.ts to search.test.ts beside the
search implementation. Preserve its assertions and required fixtures or imports,
removing any registry-only setup that is no longer needed.

In `@packages/cli/src/dev-traces.test.ts`:
- Around line 34-45: Centralize the db.statement attribute key by exporting the
existing STATEMENT_ATTRIBUTE from statement-span.ts through the db package
index. Update dev-traces.ts and the fixture around withSpan to import and use
that exported symbol instead of literal "db.statement" values, preserving the
existing span shape and behavior.

In `@packages/db/README.md`:
- Around line 40-41: Date the load-bearing documentation claims with “As of
2026-07”: update the public observer and query-loop API descriptions at
packages/db/README.md:40-41 and the scope, observation, and framework
integration claims at packages/db/README.md:175-197. Ensure each affected claim
clearly carries the date without changing the documented behavior.

In `@packages/db/src/client.test.ts`:
- Around line 268-290: Add coverage for StatementEvent.expected propagation: in
packages/db/src/client.test.ts:268-290, run client.query inside
expectedQueryLoop, assert the observed event contains the reason, and assert a
statement outside the scope has undefined expected; in
packages/db/src/pglite.test.ts:361-387, add the equivalent assertion through the
in-transaction embedded funnel, ensuring the reason survives queued execution
and awaits. Make both tests fail if expectedQueryLoop scope is not attached to
emitted events.

In `@packages/db/src/expected-loop.test.ts`:
- Around line 138-149: Update the blank-reason test around expectedQueryLoop to
capture the thrown value as unknown and assert toBeUltimateError('X_INVARIANT')
after the call, removing the manual bare Error control-flow guard. Keep the
existing bare errors in the caller-body failure tests unchanged.

In `@packages/db/src/expected-loop.ts`:
- Line 6: Add a concise explanatory comment immediately above the
AsyncLocalStorage import, stating that Bun provides no non-node: async context
primitive and this Node API is therefore unavoidable. Preserve the existing
node: import and keep the explanation specific to this dependency.
- Around line 33-40: Add core’s X_INVARIANT code to the DB_BORROWED_ERROR_CODES
declaration in errors.ts so assertion failures from expectedQueryLoop’s assert()
are recognized by the database error-code set.

In `@packages/db/src/migrate.test.ts`:
- Around line 120-121: Update the test helper reasonFor to fail loudly when no
statement text matches the requested needle, rather than returning undefined
from an optional find result. Preserve returning the matched statement’s reason,
so the scope assertions distinguish an executed statement without a reason from
a statement that never ran.

In `@packages/db/src/observe.test.ts`:
- Around line 126-155: Move the performance.now spy cleanup into the existing
afterEach alongside the Bun.SQL and observer cleanup, ensuring it restores the
per-test spy even when assertions fail. Assign each test’s spy to the shared
cleanup variable, and remove the trailing clock.mockRestore() calls from the
three tests: “the pooled client never reads the clock,” “the embedded client
never reads the clock,” and “installing an observer is what makes the clock read
happen at all.”
- Around line 99-115: The production paths in runOn and statement do not
populate StatementEvent.attribution, making the field unused outside manually
constructed tests. Either add attribution generation and include it in events
emitted by runOn and statement, or remove the attribution field and update the
test and related types to match.

In `@packages/db/src/pglite.ts`:
- Around line 112-121: The client-side affected-row helper must match the
positive-count-or-row-count behavior of rowsOf in pglite.ts. Update affectedBy
in the client implementation to return the command count only when it is
positive, otherwise return the result row count, preserving zero for writes that
modify no rows.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a1921a43-070b-4827-9187-2157e51e90f3

📥 Commits

Reviewing files that changed from the base of the PR and between dc05e11 and eb004dd.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock, !**/bun.lock
📒 Files selected for processing (25)
  • CHANGELOG.md
  • packages/admin/CLAUDE.md
  • packages/admin/package.json
  • packages/admin/src/registry.test.ts
  • packages/admin/src/search.ts
  • packages/cli/CLAUDE.md
  • packages/cli/src/dev-traces.test.ts
  • packages/cli/src/dev-traces.ts
  • packages/db/CLAUDE.md
  • packages/db/README.md
  • packages/db/src/client.test.ts
  • packages/db/src/client.ts
  • packages/db/src/expected-loop.test.ts
  • packages/db/src/expected-loop.ts
  • packages/db/src/index.ts
  • packages/db/src/migrate.test.ts
  • packages/db/src/migrate.ts
  • packages/db/src/observe.test.ts
  • packages/db/src/observe.ts
  • packages/db/src/pglite-embedded.test.ts
  • packages/db/src/pglite.test.ts
  • packages/db/src/pglite.ts
  • packages/db/src/statement-span.test.ts
  • packages/db/src/statement-span.ts
  • packages/jobs/CLAUDE.md

Comment thread packages/admin/src/registry.test.ts Outdated
Comment thread packages/cli/src/dev-traces.test.ts Outdated
Comment thread packages/db/README.md Outdated
Comment thread packages/db/src/client.test.ts
Comment thread packages/db/src/expected-loop.test.ts
Comment thread packages/db/src/expected-loop.ts
Comment thread packages/db/src/migrate.test.ts Outdated
Comment thread packages/db/src/observe.test.ts
Comment thread packages/db/src/observe.test.ts
Comment thread packages/db/src/pglite.ts
- STATEMENT_ATTRIBUTE is exported from @ultimat3/db and imported by
  dev-traces.ts and its fixture: the key was a literal in three places,
  so renaming it left the timeline grouping nothing with tests green
- attribution has no producer, and the docs now say so — observe.ts,
  db/CLAUDE.md, jobs/CLAUDE.md and CHANGELOG all claimed entity's driver
  threads it; every event in every process reads undefined today
- affectedBy trusts the command tag only when > 0, the rule rowsOf
  already used: one answer per statement whichever driver is behind it
- adminSearch's tests moved to search.test.ts, beside their source
- reasonFor fails when nothing matched: find(...)?.reason answered
  undefined both for "ran outside every scope" and "never ran", and the
  toBeUndefined() assertions were about the first
- the clock spy is restored in afterEach, not below the expect that can
  throw past it; the blank-reason test asserts on what it caught
- node:async_hooks carries the why comment the repo rule requires
- one more funnel test: the reason survives PGlite's turn queue and the
  awaits inside withTransaction
- date the new README claims As of 2026-08

Co-Authored-By: Claude <noreply@anthropic.com>
@developerz-ai

developerz-ai Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Looks good — CI is green and this is a routine feature PR (new statement observer API, minor semver impact). Ready to merge when you're ready.

🤖 Posted by developerz.ai — the maintainer agent, not a human.

@sebyx07
sebyx07 merged commit 0d308f0 into main Aug 13, 2026
5 checks passed
@sebyx07
sebyx07 deleted the feat/db-statement-observer branch August 13, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claudetm Created by Claude Task Master

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant