Skip to content

fix(agent): exclude engine and extension objects from grounding capture - #625

Open
koraysrn wants to merge 1 commit into
libredb:mainfrom
koraysrn:fix/b76-agent-grounding-exclusion
Open

fix(agent): exclude engine and extension objects from grounding capture#625
koraysrn wants to merge 1 commit into
libredb:mainfrom
koraysrn:fix/b76-agent-grounding-exclusion

Conversation

@koraysrn

@koraysrn koraysrn commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Summary

The aggregated grounding capture that fixed the wide-catalog row-cap refusal
(B52) then admitted the image's own extension objects. This closes B76 by
composing the PostgreSQL column read with the same exclusions the provider's
object browser already applies, plus a relation-level ownership test.

What changed

  • src/lib/agent/composed-sql.ts
    • Adds POSTGRES_SYSTEM_SCHEMAS (copied from the provider's SYSTEM_SCHEMAS),
      POSTGRES_EXTENSION_OWNED_SCHEMAS_SQL and
      POSTGRES_EXTENSION_OWNED_RELATIONS_SQL.
    • composePostgresCatalog now filters by the full engine-builtin schema list,
      every schema an extension created (pg_depend on pg_namespace,
      deptype = 'e'), and every relation an extension created (pg_depend on
      pg_class, deptype = 'e').
    • The relation/index/statistics reads are aligned to the full schema list; the
      column read stays the single source of object identity, so the others need
      no relation test.
  • tests/unit/lib/agent/composed-sql.test.ts — pins the SQL shape, guard
    acceptance, and that the agent's schema list cannot drift from the provider's.
  • tests/unit/lib/agent/context-snapshot.test.ts — one test per shape
    (TimescaleDB, Cloudberry, AlloyDB Omni).
  • docs/BACKLOG.md — B76 removed (B2–B75 · 21).
  • docs/AGENT.md — B76 deferral record removed.
  • docs/providers/postgres.md — agent grounding description now documents the
    three-layer exclusion.

Verification

Measured live on 2026-09-07 against the three compat images with two user
tables seeded:

Image Before After
TimescaleDB 46 2
Cloudberry 67 2
AlloyDB Omni 70 2

AlloyDB is the sharp case: the schema half alone leaves 51 objects (2 user
tables plus the 49 extension views installed into public); pg_depend reports
68 extension-owned relations, and the relation ownership test is what removes
them. A user's own views are never extension-owned, so they survive.

Checks

  • composed-sql.test.ts + context-snapshot.test.ts + backlog-structure.test.ts: 282 pass, 0 fail
  • bun run typecheck: pass
  • biome format (changed files): pass
  • bun run lint: 0 errors

The aggregated column read that fixed the wide-catalog row-cap refusal (B52) then admitted the image's own extension objects, so a grounded run on TimescaleDB, Cloudberry or AlloyDB Omni reasoned over an inventory that was mostly internal noise.

Compose the PostgreSQL column read with the same exclusions the provider's object browser already applies, plus a relation-level ownership test that reaches AlloyDB's public-installed extension views: the full engine-builtin schema list, every schema an extension created (pg_depend on pg_namespace, deptype = 'e'), and every relation an extension created (pg_depend on pg_class, deptype = 'e').

The relation, index and statistics reads are aligned to the full schema list; the column read stays the single source of object identity, so the others need no relation test (their rows attach to the column inventory).

Measured live on 2026-09-07 against the three compat images with two user tables seeded: 46 -> 2 object rows on TimescaleDB, 67 -> 2 on Cloudberry, 70 -> 2 on AlloyDB Omni; on AlloyDB the schema half alone leaves 51 and pg_depend reports 68 extension-owned relations.

Closes B76.
@koraysrn koraysrn closed this Sep 7, 2026
@koraysrn
koraysrn deleted the fix/b76-agent-grounding-exclusion branch September 7, 2026 12:01
@koraysrn
koraysrn restored the fix/b76-agent-grounding-exclusion branch September 7, 2026 12:03
@koraysrn koraysrn reopened this Sep 7, 2026
@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri cevheri added documentation Improvements or additions to documentation enhancement New feature or request labels Sep 7, 2026

@cevheri cevheri left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the diff and ran the two touched test files locally: 189 pass, 0 fail.

Verified correct: the NOT IN composition has no AND/OR precedence hazard in any of the four statements, the copied POSTGRES_SYSTEM_SCHEMAS list is byte-identical to the provider's, the drift guard is non-vacuous, the B2-B75 - 21 count matches the 21 remaining ### B entries, and no stale B76 references are left.

Five comments below. The two in composed-sql.ts are behaviour: a missing fallback that breaks the agent path on Materialize, and an exclusion applied to one of the three catalog reads. The other three are a doc claim, a markdown break and a nominal test.

* question the name was standing in for, and `pg_depend` answers it directly.
* Same query the provider composes (`EXTENSION_OWNED_SCHEMAS_SQL`).
*/
const POSTGRES_EXTENSION_OWNED_SCHEMAS_SQL =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The provider does not read pg_depend / pg_extension unguarded. It carries a fallback: isMissingExtensionCatalogError -> withoutExtensionOwnershipTest (src/lib/db/providers/sql/postgres.ts:458 and :484), because Materialize raises on those catalogs.

The agent path has no equivalent. composeCatalogRead returns one composed statement and there is no rewrite chain around it, so on a connection typed postgres that is actually Materialize (and postgres is in AGENT_EXECUTION_ENGINES) all four catalog reads fail: the context capture goes silently unavailable and inspect_schema returns a DB failure.

The live verification covered TimescaleDB, Cloudberry and AlloyDB Omni, all of which do have pg_depend, so it could not see this. Either mirror the provider's fallback or state the Materialize exclusion explicitly.

"FROM information_schema.columns " +
"WHERE table_schema NOT IN ('pg_catalog', 'information_schema')" +
`WHERE ${postgresSchemaExclusion("table_schema")}` +
` AND (table_schema, table_name) NOT IN (${POSTGRES_EXTENSION_OWNED_RELATIONS_SQL})` +

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The relation-ownership test is applied on the columns read only, which leaves the exclusion inconsistent across the reads a run makes:

  • kind=indexes and kind=statistics filter by schema alone and go to the model through readCatalog, not through buildPostgresTables
  • the object browser has no pg_class ownership test at all

Concretely, with PostGIS installed, public.spatial_ref_sys stays visible in the browser and in the index and statistics reads, but disappears from the column inventory. Same object, three different answers in one run. Worth either extending the test to the other kinds or recording why columns is the only place it belongs.

escape, so `'a\'` would read as an unterminated literal.
schema/table selector and the server writes the `columns` statement itself, executed as
`sql.query.read` like any other statement. The model never supplies that SQL. The statement's
`WHERE` excludes the engine's own objects three ways, all copied from this provider's object

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

"all copied from this provider's object browser rather than invented on the agent path" is not accurate for the third way. The browser has no pg_class extension-ownership test; that one is new on the agent path. As written the sentence also hides the divergence: the relation test covers the columns read and not indexes / statistics or the browser.

Comment thread docs/AGENT.md
noise; the object set was the same under the flat projection, which refused before any of it reached
a run.

**Settled as limits rather than as work.** The seven below have no entry in `docs/BACKLOG.md`, and

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The B76 removal took the blank line before this paragraph with it, so "Settled as limits rather than as work." now renders as a continuation of the B65 bullet instead of a new paragraph. One blank line above this line fixes it.

});

describe("captureContextSnapshot — the capture excludes each image's own extension objects (B76)", () => {
const SHAPES = [

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These three "per shape" cases pass no fixture rows and assert substrings of the same composed string, so nothing shape-specific is actually exercised. They pass identically if the shapes are swapped. If the intent is a test per shape, each needs rows that only that shape's exclusion removes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants