During early access, Polycore prepares and validates this integration with
your team. The examples below show the expected IAM and configuration shape;
they are not a request to send us your connection string.
Architecture
The Postgres URL is supplied to the runner asPOLYCORE_POSTGRES_URL. It does
not appear in polycore.json, capability metadata, or an agent prompt.
Read-only enforcement
The integration uses independent controls:Database role
The connection should use a role with
CONNECT, schema USAGE, and
SELECT only, or connect to a read replica with equivalent permissions.Runner execution
PostgreSQL first describes caller SQL without executing it. The extended
protocol accepts exactly one statement, and the runner requires result
columns before executing that same SQL with bound params inside
BEGIN TRANSACTION READ ONLY. Multi-statement payloads, transaction control, and
non-row-returning utilities fail before execution; writes with RETURNING
still fail in the read-only transaction. End-to-end and per-statement
deadlines plus a bounded admission queue protect the database if the role is
accidentally over-privileged.truncated and either maxRows or maxBytes as truncationReason. Agents are
instructed to aggregate in SQL and request only the columns they need.
1. Create the database role
We adapt this SQL to your database, schemas, ownership model, and migration roles:Role template
SELECT can
invoke a function. Review EXECUTE grants on customer functions and extensions,
especially security-definer functions, dblink, foreign-data wrappers, and any
function with network or filesystem effects. Do not grant this role membership
in a broader role. A physical read replica is the strongest database-level
backstop when operational isolation matters more than freshness; we confirm
acceptable replication lag during scoping.
2. Store the connection string
Your deployment layer stores a URL for the read-only role and injects it only into the runner:- Require TLS and verify the server certificate.
- Prefer private network connectivity from the runner.
- Rotate the password through your existing secret manager.
- Restrict source networks where the database supports it.
- Use a dedicated role per environment.
3. Enable the query family
The committed runner config declares the capability surface: An empty object takes the production defaults; alimits block commits
stricter ceilings:
polycore.json
POLYCORE_POSTGRES_URL remains absent from the file.
Available capabilities
read
Searches visible non-system schemas with a bounded limit. Returns schema
comments and explicit truncation metadata.
read
Searches visible relations by schema, name, and type. Returns comments,
estimated rows, total bytes, and explicit truncation metadata.
read
Returns precise formatted column types, comments, identity/generated flags,
primary and unique keys, composite-safe outgoing and incoming foreign keys,
checks, indexes, enum values, row estimates, and table size.
read
Plans the same structurally bounded query and bound params without executing
it, reporting estimated root cost and rows.
read
Describes and then runs one row-returning extended-protocol
SELECT or WITH ... SELECT with bound positional params. Duplicate result names are rejected
so object-row mapping cannot overwrite a column. Returns {rows, rowCount, truncated, truncationReason?} within the configured row and encoded-byte
ceilings.write
Previews one
UPDATE and reports the exact rows, columns, and values it would
change, then applies only those rows and only if none has been written since.write
Previews one
DELETE, reporting the rows it would remove plus every table
reachable through a foreign key, including rows a cascade would take and any
restraint that would make the delete fail.How an agent reads safely
1
Discover
The agent lists schemas and tables rather than guessing relation names.
2
Ground
It describes the relevant relations and uses foreign-key metadata to form
valid joins.
3
Plan when needed
For a potentially expensive request, it asks Postgres for a query plan
before execution.
4
Bind values and query a bounded result
It writes runtime values as
$1, $2, and so on and sends them separately
in params. The runner binds them, adds structural read-only enforcement,
and stops collection at the row or byte ceiling.5
Return through the governed path
Rows or a structured database error return to the control plane and are
linked to the original caller and request.
Representative request
SELECT. It does not need a custom
action for that question.
Parameterized runtime filters
Values from a dashboard control, form, URL, or caller must never be interpolated into SQL:Writes
The query family above cannotINSERT, UPDATE, DELETE, or run DDL, and it
never can: it is the read path, and its role should be one the database itself
prevents from mutating.
A stable, repeated write workflow belongs in a named
action with a narrow input schema, explicit business
rules, and a separately scoped credential.
For one-off changes, the optional Postgres write family adds
postgres.update and postgres.delete. It is off until the runner config
grants it, and the grant may name a separate connection so the read path stays
mutation-incapable at the database:
Every write is previewed before it runs
A SQL predicate does not name its rows until it is evaluated, so showing an approver the statement shows them the wrong thing.UPDATE profiles SET status = 'suspended' WHERE email LIKE '%@acme.com' reads as one careful change and hits
fourteen rows, including the CI bot.
So the statement is parsed with PostgreSQL’s own parser, rewritten into an
equivalent SELECT, and run inside a read-only transaction. That reports:
- every row that would change, with before and after values;
- the columns the statement assigns;
- every table reachable through a foreign key, and whether a cascade would delete those rows or a restraint would make the whole delete fail;
- anything the preview could not see, such as a
BEFOREtrigger that may rewrite the values actually written.
The approved set cannot grow
The apply names primary keys, so a row that starts matching the predicate
while a human is deciding is not eligible. Fourteen rows were shown; exactly
those fourteen can change.
It lands whole or not at all
Each row is matched on the version it had at preview time. A row that was
written in the meantime aborts the entire apply inside one transaction, so a
stale approval changes nothing rather than half.
WHERE clause, a WITH clause, UPDATE ... FROM,
DELETE ... USING, DDL, a table with no primary key. A statement matching more
rows than the configured limit (1000 by default) is refused too, on the grounds
that an approval which does not name its rows was not really an approval.
Why this is what makes unattended SQL writes reasonable
Because the preview reports what a statement would do, your access policy can decide on the consequence instead of the text. A rule can let a small, in-scope status change dispatch immediately while the same capability, used against a different table or two thousand rows, waits for a human. An agent gets to write the SQL the task actually needs, inside bounds you declared.Production validation
We verify all of the following with your team:Schema and relation discovery work only in the intended database and schemas.
A normal aggregate read succeeds and produces a bounded result.
COMMIT; DELETE, multiple statements, transaction control, utility commands,
INSERT, UPDATE, DELETE, and DDL all fail before they can mutate data.Hostile quote/comment text sent as a bound param remains a literal value.
Oversized row sets and wide values stop with
maxRows or maxBytes
truncation metadata.Long-running work stops at the configured statement timeout.
The runner can reconnect without exposing the database URL to the control
plane.
Query arguments, outcome, target environment, and caller appear in the audit
path.
Common design questions
Can this connect to Supabase?
Can this connect to Supabase?
Yes. Supabase runs Postgres. We create a dedicated database role and supply
its connection string to the runner rather than using a broad service
credential in an agent.
Does row-level security still apply?
Does row-level security still apply?
It depends on the role and your policies. We review RLS behavior explicitly
rather than assuming it. Table grants and RLS solve different parts of the
access model.
What about multiple schemas?
What about multiple schemas?
Grant
USAGE and SELECT only for the schemas in scope. The agent can call
postgres.listSchemas and qualify relation names as schema.table.What about large analytics queries?
What about large analytics queries?
Prefer an analytics replica or warehouse. The 10-second timeout and result
cap protect the interactive path; they are not a substitute for workload
isolation.

