Skip to content

feat(store): sqlc vet in CI + promote inline-sql-gate to gate identifier-passed SQL (T7) - #809

Closed
rigel-mintaka wants to merge 1 commit into
compass-repo/rig-3034-sqlc-t6-remainderfrom
compass-repo/rig-3034-sqlc-t7-vet-gate
Closed

feat(store): sqlc vet in CI + promote inline-sql-gate to gate identifier-passed SQL (T7)#809
rigel-mintaka wants to merge 1 commit into
compass-repo/rig-3034-sqlc-t6-remainderfrom
compass-repo/rig-3034-sqlc-t7-vet-gate

Conversation

@rigel-mintaka

@rigel-mintaka rigel-mintaka commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

This PR is part of a stack containing 4 PRs:

  1. main
  2. feat(store): migrate messages + topics + delivery to sqlc (T4) #795
  3. feat(store): migrate agent sessions/transcripts/activity/config/placements to sqlc (T5) #796
  4. feat(store): migrate authz/tokens/secrets/issues/forge/tenant/linear to sqlc (T6) #806
  5. "feat(store): sqlc vet in CI + promote inline-sql-gate to gate identifier-passed SQL (T7)" (this PR)

Final slice of the sqlc adoption (RIG-3034). Adds the strongest sqlc check —
db-prepare against a live schema — to CI, and closes the inline-SQL gate's
last known gap now that every store domain has migrated.

sqlc.yaml: add database.uri: ${SQLC_DATABASE_URL} + rules: [sqlc/db-prepare]
so sqlc vet PREPAREs every generated query against a live Postgres. Also set
analyzer.database: false so sqlc generate (and the DB-free sqlc-drift gate)
keep working without a database — db-prepare (vet) still uses database.uri.
Without this, the database stanza would force generate to require a live DB and
break the design's 'sqlc-drift is fully local, no DB' invariant.

moon.yml: add a sqlc-vet task (modeled on sqlc-drift) — DSN-gated skip when
neither COMPASS_TEST_DATABASE_DSN nor SQLC_DATABASE_URL is set (dev-box posture,
mirroring the pgtest DSN-gated skip); otherwise it provisions a uniquely-named
throwaway DB (sqlcvet_$$) on the service, applies internal/store/migrations/*.sql
via psql, points SQLC_DATABASE_URL at it, runs sqlc vet, and drops the DB on a
cleanup trap. go/cmd/compass-postgres is deliberately NOT reused (it is the
stack supervisor's process-managed instance; the CI service DB is simpler and
already provisioned). sqlc-vet is NOT added to the moon ci deps: the moon
battery realizes no service.

ci.yml: run moon run compass-go:sqlc-vet in the existing pgtest job, which
already carries the postgres:16-alpine service + COMPASS_TEST_DATABASE_DSN. This
is where db-prepare runs live — the same peel-into-a-service-job pattern as
pgtest/microvm/forge/gtk4 (ci.yml: 'the moon battery has no business realizing
an environment'). A phase-two nixpkgs-tools step is added to that job so psql +
sqlc are on PATH. This wiring (Option B) was Matt's ruling on the CI fork: the
spec assumed the ci-aggregate job had the service, but it does not — the service
lives only on the pgtest job.

inline-sql-gate: promote identifier-passed SQL from advisory to GATED. In
addition to string-literal SQL at any receiver, a bare-identifier / simple-
selector SQL slot (q, ddl, m.sql) is now flagged when the call's receiver
is a pgx pool/tx/conn handle (last segment in {pool, tx, conn, c}). The
receiver scope is the load-bearing guard against runtime/compute false positives
(r.runtime.Exec(ctx, id, spec) / g.client.Exec(ctx, req) are not queries). Only
the SQL slot (arg after ctx) is tested, never params; a call/composite/concat
expression is not a hoisted-SQL name and is left alone. Stays GREEN on the
migrated tree: the only remaining identifier-passed sites are conn.Exec(ctx,ddl)
/ tx.Exec(ctx,m.sql) in the permanently-allowlisted store.go (allowlist=2, no
stale entry). Header + ALLOWLIST notes updated; fail-closed stale-entry ratchet
intact.

Tests: add identifier-passed detection cases to index.test.ts — bare-identifier
at pool/conn/tx receivers IS flagged (incl. the store.go conn.Exec(ctx,ddl) and
tx.Exec(ctx,m.sql) shapes), non-pgx runtime.Exec is NOT flagged, a call-valued
SQL slot is NOT flagged, SQL-slot-only (params never double-flag), and the
existing string-literal detection still works. 28/28 pass.

Open item (travels with the code): db-prepare runs live in the pgtest CI job per
Matt's Option-B ruling; it is not in the moon ci battery because that job has no
Postgres service.

RIG-3034

Co-authored-by: Matt Wilkinson matt@rigel.build

…ier-passed SQL (T7)

Final slice of the sqlc adoption (RIG-3034). Adds the strongest sqlc check —
db-prepare against a live schema — to CI, and closes the inline-SQL gate's
last known gap now that every store domain has migrated.

sqlc.yaml: add `database.uri: ${SQLC_DATABASE_URL}` + `rules: [sqlc/db-prepare]`
so `sqlc vet` PREPAREs every generated query against a live Postgres. Also set
`analyzer.database: false` so `sqlc generate` (and the DB-free sqlc-drift gate)
keep working without a database — db-prepare (vet) still uses database.uri.
Without this, the database stanza would force generate to require a live DB and
break the design's 'sqlc-drift is fully local, no DB' invariant.

moon.yml: add a `sqlc-vet` task (modeled on sqlc-drift) — DSN-gated skip when
neither COMPASS_TEST_DATABASE_DSN nor SQLC_DATABASE_URL is set (dev-box posture,
mirroring the pgtest DSN-gated skip); otherwise it provisions a uniquely-named
throwaway DB (sqlcvet_$$) on the service, applies internal/store/migrations/*.sql
via psql, points SQLC_DATABASE_URL at it, runs `sqlc vet`, and drops the DB on a
cleanup trap. go/cmd/compass-postgres is deliberately NOT reused (it is the
stack supervisor's process-managed instance; the CI service DB is simpler and
already provisioned). sqlc-vet is NOT added to the moon `ci` deps: the moon
battery realizes no service.

ci.yml: run `moon run compass-go:sqlc-vet` in the existing `pgtest` job, which
already carries the postgres:16-alpine service + COMPASS_TEST_DATABASE_DSN. This
is where db-prepare runs live — the same peel-into-a-service-job pattern as
pgtest/microvm/forge/gtk4 (ci.yml: 'the moon battery has no business realizing
an environment'). A phase-two nixpkgs-tools step is added to that job so psql +
sqlc are on PATH. This wiring (Option B) was Matt's ruling on the CI fork: the
spec assumed the ci-aggregate job had the service, but it does not — the service
lives only on the pgtest job.

inline-sql-gate: promote identifier-passed SQL from advisory to GATED. In
addition to string-literal SQL at any receiver, a bare-identifier / simple-
selector SQL slot (`q`, `ddl`, `m.sql`) is now flagged when the call's receiver
is a pgx pool/tx/conn handle (last segment in {pool, tx, conn, c}). The
receiver scope is the load-bearing guard against runtime/compute false positives
(r.runtime.Exec(ctx, id, spec) / g.client.Exec(ctx, req) are not queries). Only
the SQL slot (arg after ctx) is tested, never params; a call/composite/concat
expression is not a hoisted-SQL name and is left alone. Stays GREEN on the
migrated tree: the only remaining identifier-passed sites are conn.Exec(ctx,ddl)
/ tx.Exec(ctx,m.sql) in the permanently-allowlisted store.go (allowlist=2, no
stale entry). Header + ALLOWLIST notes updated; fail-closed stale-entry ratchet
intact.

Tests: add identifier-passed detection cases to index.test.ts — bare-identifier
at pool/conn/tx receivers IS flagged (incl. the store.go conn.Exec(ctx,ddl) and
tx.Exec(ctx,m.sql) shapes), non-pgx runtime.Exec is NOT flagged, a call-valued
SQL slot is NOT flagged, SQL-slot-only (params never double-flag), and the
existing string-literal detection still works. 28/28 pass.

Open item (travels with the code): db-prepare runs live in the pgtest CI job per
Matt's Option-B ruling; it is not in the moon ci battery because that job has no
Postgres service.

RIG-3034

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Aug 31, 2026

Copy link
Copy Markdown

RIG-3034

@github-actions

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-repo-rig-3034-sqlc-t-eizk.compass-eng-docs.pages.dev

Deployed from compass-repo/rig-3034-sqlc-t7-vet-gate at e436cb1.

Changed pages:

@rigel-mintaka

Copy link
Copy Markdown
Contributor Author

Superseded by #812 — the remaining sqlc slices (T5+T6+T7) are collapsed into a single PR rebased on merged T4 (#795), per Matt's request to merge them together. Closing this slice PR; its content and review (RevT7 clean) carry into #812.

@rigel-mintaka
rigel-mintaka deleted the compass-repo/rig-3034-sqlc-t7-vet-gate branch August 31, 2026 21:36
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