Skip to content

fix(agent): aggregate the PostgreSQL grounding column read per table (B52) - #537

Open
koraysrn wants to merge 2 commits into
libredb:mainfrom
koraysrn:fix/agent-postgres-grounding-object-rows
Open

fix(agent): aggregate the PostgreSQL grounding column read per table (B52)#537
koraysrn wants to merge 2 commits into
libredb:mainfrom
koraysrn:fix/agent-postgres-grounding-object-rows

Conversation

@koraysrn

@koraysrn koraysrn commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

What

The composed PostgreSQL grounding capture (composePostgresCatalog()) projected one row per column. Against maxResultRows: 200, a stock image's own wide catalog refused the read before the user created anything — measured on TimescaleDB, Cloudberry and AlloyDB Omni and recorded as B52.

This change aggregates the columns per table with json_agg(json_build_object(...) ORDER BY ordinal_position) + GROUP BY table_schema, table_name, so the projection is one row per object, symmetric with the SQLite side. buildPostgresTables() now parses the aggregated columns array through a new parsePostgresColumns() that accepts both the pg-parsed array and a JSON-string transport form, and returns an empty list for malformed values rather than losing the snapshot.

Why

On those images the agent was unusable out of the box: the grounding capture was refused and every plan run answered ungrounded.

Live verification (compat profile)

Server flat rows (before) aggregated rows (after)
TimescaleDB latest-pg17 385 46
Cloudberry 2.1.0 (gpadmin) 479 67
Cloudberry 2.1.0 (least-privilege) 53
AlloyDB Omni 17.9.0 (postgres) 542 70
AlloyDB Omni 17.9.0 (least-privilege) 69

All under the 200-row budget, and the user's tables are named in every capture. Column order is preserved (idcustomer_idtotal), and the privilege model is unchanged — a least-privilege role aggregates exactly what it is allowed to see.

Notes

  • The byte budget (maxResultBytes, 256 KB) remains the payload backstop; an extreme single-table column count would be refused by bytes rather than rows. Documented on composePostgresCatalog.
  • A schema with more than 200 tables is still refused — B52 removes the per-column overflow, not the per-table one.
  • New follow-up recorded as B76: the capture now admits the image's own extension views (AlloyDB's least-privilege role sees 67 ai.* / google_db_advisor_* views beside its 2 user tables) — not a privilege leak, but grounding noise to address separately.
  • Docs: B52 removed from docs/BACKLOG.md and docs/AGENT.md; the compatibility.ts caveats for the three servers updated to match.

Verification

  • bun run typecheck — pass
  • bun run lint — 0 errors
  • bun run knip — pass
  • bun run build — pass
  • Targeted suites all green: composed-sql.test.ts (79), context-snapshot.test.ts (98, incl. wide-catalog simulation + malformed/string/wide-table robustness), backlog-structure.test.ts (93), compatibility.test.ts, plan-grounding.test.ts, types.test.ts.

…(B52)

The composed column capture projected one row per column, so a stock image's own wide catalog exceeded maxResultRows: 200 and the run grounded with no inventory on TimescaleDB, Cloudberry and AlloyDB Omni. Aggregate the columns per table so the projection is one row per object, symmetric with SQLite, and parse the aggregated array in buildPostgresTables.

Live-verified on the compat images: TimescaleDB 385→46 rows, Cloudberry 479→67, AlloyDB Omni 542→70 (69 as a least-privilege role).
B52's backlog entry was deleted when the fix landed; the new B76 entry and the compat table still cited it. Reword so no citation outlives its entry, and update the TimescaleDB/Cloudberry/AlloyDB compat rows to the post-fix behaviour.
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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