Skip to content

feat(lattice): add ShadowDatabase simulator for the v2 state lattice (closes #657) - #674

Open
SakethSumanBathini wants to merge 2 commits into
sreerevanth:mainfrom
SakethSumanBathini:feat/657-shadow-database
Open

feat(lattice): add ShadowDatabase simulator for the v2 state lattice (closes #657)#674
SakethSumanBathini wants to merge 2 commits into
sreerevanth:mainfrom
SakethSumanBathini:feat/657-shadow-database

Conversation

@SakethSumanBathini

@SakethSumanBathini SakethSumanBathini commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Closes #657
Part of #651

Mirrors the structure ShadowFilesystem established in #655 — same module layout, same simulate/apply split, same "supplied rather than discovered" stance on external state.

Queries are accepted pre-parsed

The issue allows either parsing or accepting pre-parsed intentions. I took the second.

There's no SQL parser in pyproject.toml, and adding one is a real dependency decision. More to the point, a regex-based parser gets maybe 90% of statements right — and the 10% it misreads are quoted identifiers, subqueries and CTEs, which is precisely the shape a crafted query takes. A simulator that can be fooled by unusual syntax is worse than none, because it produces a verdict people trust.

QueryIntent describes what a statement would do. A caller with a parser feeds it; a caller with an ORM already has the structured form.

Blast radius

rows_affected=None means "the caller could not determine the reach", and it is treated as the whole table for DELETE, UPDATE, DROP and TRUNCATE.

That's the single most important decision here. Reading an unknown as zero is how an unqualified DELETE FROM users gets waved through — the risk is identical to an unbounded statement, so the estimate has to be the upper bound. QueryMutationResult.estimated marks when a count was derived rather than supplied.

The three invariants

max_row_delete_pct — more than 10% of a table removed in one statement. Exactly 10% passes; the check is "more than", not "at least". A table of unknown size yields no verdict rather than a pass: with no denominator there is no proportion, and silence is not approval.

protected_tables_immutable — any mutation touching credentials, permissions, billing or audit history, regardless of row count. One altered row in permissions is worth more than a thousand in logs. Reads are allowed. The check also fires when a protected table is reached by cascade rather than named directly.

no_cascade_deletes — a cascade into any table outside approved_cascade_tables.

All violations are reported together rather than short-circuiting, so a caller fixing one doesn't have to re-run to discover the next.

Two details worth flagging

Table names are matched case-insensitively and unquoted. SQL identifiers are case-insensitive unless quoted, so USERS, "users", `users` and [users] all normalise to one table. An invariant that USERS slips past is not an invariant, and there's a test for each form.

QueryMutationResult, not MutationResult. shadow_filesystem already exports MutationResult from this package. Reusing the name would mean ShadowDatabase.simulate() returns a type that can't be imported from agentwatch.lattice — the filesystem one wins the export. Both are lattice results but they carry different fields, so two clear names beat one ambiguous one.

Tests

26 tests in tests/test_shadow_database.py, covering blast-radius resolution, each invariant separately, the multi-violation case, apply folding results into the model, and the refusal path leaving it untouched.

Worth calling out one: test_apply_leaves_the_model_alone_when_the_query_is_refused. A blocked statement never ran, so pretending its rows are gone would make every later simulation wrong.

Verification

  • python -m pytest tests/test_shadow_database.py tests/test_shadow_filesystem.py62 passed, no regressions in the existing lattice suite.
  • ruff check and ruff format --check both clean on the new files.

The wider suite has pre-existing collection errors from optional dependencies (celery, litellm, redis, prometheus_client) in my environment. Comparing against unmodified main: identical 116 failures and 14 errors before and after, with passes going 820 → 846 — exactly the 26 tests added here.

Summary by CodeRabbit

  • New Features
    • Added a shadow database simulator that estimates query impact without modifying live data.
    • Reports affected tables, estimated row counts, destructive operations, and invariant violations.
    • Supports protected tables, cascade handling, deletion thresholds, and case-insensitive table matching.
    • Allows approved mutations to be applied while preserving state when changes are refused.
    • Exposed shadow database types and defaults through the lattice package.

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@SakethSumanBathini, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 466aad1d-1374-4698-9b10-ece09c9e4370

📥 Commits

Reviewing files that changed from the base of the PR and between 75f8156 and 98cc80d.

📒 Files selected for processing (2)
  • agentwatch/lattice/shadow_database.py
  • tests/test_shadow_database.py
📝 Walkthrough

Walkthrough

The PR adds a ShadowDatabase simulator for pre-parsed query intents. It estimates affected rows and tables, checks deletion, protected-table, and cascade invariants, returns structured results, applies approved mutations, and exposes the API through agentwatch.lattice.

Changes

Shadow database simulation

Layer / File(s) Summary
Shadow database contracts and state
agentwatch/lattice/shadow_database.py, agentwatch/lattice/__init__.py
Defines query operations, state invariants, immutable intent and result records, defaults, validated state, and public package exports.
Simulation, validation, and mutation application
agentwatch/lattice/shadow_database.py
Resolves tables and rows, validates protected tables, deletion thresholds, and cascades, then applies approved DROP, TRUNCATE, DELETE, and INSERT operations.
Simulator behavior validation
tests/test_shadow_database.py
Tests row estimation, invariant aggregation, case normalization, cascade handling, mutation application, constructor validation, and simulation immutability.

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

Sequence Diagram(s)

sequenceDiagram
  participant QueryIntent
  participant ShadowDatabase
  participant QueryMutationResult
  QueryIntent->>ShadowDatabase: simulate(intent)
  ShadowDatabase->>ShadowDatabase: resolve affected tables and rows
  ShadowDatabase->>ShadowDatabase: evaluate invariants
  ShadowDatabase-->>QueryMutationResult: return structured result
  QueryIntent->>ShadowDatabase: apply(intent)
  ShadowDatabase-->>QueryMutationResult: return applied or refused result
Loading

Possibly related PRs

Poem

I hop through tables, row by row,
Before real changes start to flow.
Protected leaves stay safe and still,
Cascades bend to approved will.
Simulate, then act with care—
A tidy shadow waits back there.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the new ShadowDatabase simulator and links it to the v2 state lattice objective.
Linked Issues check ✅ Passed The implementation satisfies #657 by simulating query impacts, tracking affected rows and tables, and enforcing all three database invariants.
Out of Scope Changes check ✅ Passed The code, public exports, and tests directly support the ShadowDatabase simulator objectives in #657.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

🧪 PR Test Results

Check Result
Tests (pytest tests/) ✅ success
Lint (ruff check .) ✅ success
Coverage (agentwatch) 74.40%

Python 3.12 · commit 98cc80d

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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 `@agentwatch/lattice/shadow_database.py`:
- Around line 262-279: Update _resolve_rows so operations in
_WHOLE_TABLE_WHEN_UNQUALIFIED always return table_size with was_estimated=True
before consulting intent.rows_affected, ensuring DROP/TRUNCATE cannot be
overridden by a supplied count. Preserve caller-supplied counts for other
operations, and update test_apply_handles_insert_and_truncate to omit the
explicit count or expect refusal under MAX_ROW_DELETE_PCT.
🪄 Autofix (Beta)

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: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5f739551-7d69-4ed5-b867-a4d8e3653997

📥 Commits

Reviewing files that changed from the base of the PR and between d94dfd0 and 75f8156.

📒 Files selected for processing (3)
  • agentwatch/lattice/__init__.py
  • agentwatch/lattice/shadow_database.py
  • tests/test_shadow_database.py

Comment thread agentwatch/lattice/shadow_database.py
@SakethSumanBathini

Copy link
Copy Markdown
Contributor Author

The one failing check is pre-existing on main and unrelated to this branch.

ruff format --check agentwatch/ runs across the whole package and flags a file this PR doesn't touch:

Would reformat: agentwatch/core/recursion_depth_detector.py
1 file would be reformatted, 180 files already formatted

I checked out upstream/main directly (d94dfd0) and ran the same command — identical result, with no changes applied. The three files in this PR pass ruff format --check individually, and ruff check is green; the PR Test Results bot confirms both.

One command fixes it:

ruff format agentwatch/core/recursion_depth_detector.py

Worth doing on its own, since the lint job currently fails on every PR regardless of content — which makes it hard to tell a real failure from the standing one. Happy to open that separately rather than bundle unrelated formatting into this change.

@SHAURYASANYAL3 SHAURYASANYAL3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Excellent work on the ShadowDatabase simulator! The code is clean and perfectly aligns with the V2 lattice architecture. Thank you!

@SHAURYASANYAL3

Copy link
Copy Markdown
Collaborator

I tried to merge this, but there are merge conflicts. Please resolve the conflicts and update the PR so I can merge it.

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.

[v2] Implement ShadowDatabase Simulator

2 participants