Skip to content

fix(introspect): a serial column is reported, not imported as an ordinary default (#119) - #124

Merged
jryannel merged 3 commits into
mainfrom
claude/serial-column-reported-not-imported
Aug 2, 2026
Merged

fix(introspect): a serial column is reported, not imported as an ordinary default (#119)#124
jryannel merged 3 commits into
mainfrom
claude/serial-column-reported-not-imported

Conversation

@jryannel

@jryannel jryannel commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Fixes #119, and writes down the schema opinions the survey work kept leaning on.

The bug

Postgres expands serial into three separate things — a plain integer column, a sequence it owns, and a nextval default — and marks none of them as an identity. So attidentity is empty and the identity check in buildColumn did not see it. The default carries a regclass cast, which is not the column's own type, so stripCast had no reason to touch it either.

The column therefore arrived as an ordinary bigint whose default happened to name a sequence, the table reported clean, and the DDL it produced did not run:

FAIL create table coprocess_steps — relation "coprocess_steps_seq_seq" does not exist
FAIL index coprocess_steps_session_idx — relation "coprocess_steps" does not exist

Two failures from one cause, with no warning before the bootstrap broke. TestBuildReportsWhatItCannotRepresent already states the principle this violated: "The failure that matters is the quiet one."

The fix

Refuse the column and report it, exactly as an identity column is refused.

The column goes rather than only its default. Declaring it without the default the database has would leave every Diff proposing to add one back — the permanently-red gate stripCast was written to avoid, and the reason that function's comment says a gate red for a reason that is not real is worse than no gate.

Verified against the schema that found it

process-mining, the studio-apps deployment where this surfaced:

before after
DDL apply failures 2 0
fixpoint residual 1 0
coprocess_steps clean partial

The table moves from a false clean to an honest partial, and the schema reaches a fixpoint. Re-running the other nine deployments shows no other schema changes — 233 tables, 0 apply failures fleet-wide.

Tests cover the spellings that reach the catalog (bigserial, serial, schema-qualified, unquoted, uppercase) and, in the other direction, that a default merely mentioning a sequence-like name — 'nextval'::text, 'seq_' || id — still imports as an ordinary column.

Declaring sequences outright remains open. This fixes the lie, not the gap; identity columns are still refused too.

docs/best-practices.md

Three buckets, kept apart on purpose: opinions the DSL enforces (each citing its ADR), practices it only recommends, and refusals that are merely unbuilt. Conflating the first and third is how a missing feature gets rewritten as a principle, so the gaps are listed on the same page with their issue numbers.

Counts come from the two surveys — valiro-go (68 tables) and studio-apps (ten deployments, 233 tables).

Two things came out of writing it:

  • ADR-0015 got corroboration. studio-apps documents the same prefixing rule and enforces it in CI, and it still drifted to 8 violations — whose tables are exactly the ones that collided when the survey built one database per app. A rule enforced by review drifts; a prefix applied by the registry cannot.
  • ADR-0034's revisit trigger fired again. It asks for "a real schema wanting a composite key on a table that is not exposed, expandable or cursor-paged", which had one instance. studio-apps has composite primary keys in 4 of 10 apps, including agentdeploy's (deployment_id, key) configuration tables that no REST resource mounts. The ADR already names the fix — move the refusal into rest.Resource's mount check rather than lift it — and asked for the stance to be stated in the schema docs, which this does.

🤖 Generated with Claude Code

jryannel and others added 3 commits August 2, 2026 17:31
…nary default (#119)

Postgres expands serial into three things — a plain integer column, a sequence
it owns, and a nextval default — and marks none of them as an identity, so
attidentity is empty and the identity check did not see it. The nextval default
carries a regclass cast, which is not the column's own type, so stripCast had no
reason to touch it either. The column therefore arrived as an ordinary bigint
whose default happened to name a sequence.

The table then reported clean and the DDL it produced did not run:

    FAIL create table coprocess_steps — relation "coprocess_steps_seq_seq" does not exist
    FAIL index coprocess_steps_session_idx — relation "coprocess_steps" does not exist

Two failures from one cause, and no warning before the bootstrap broke — the
quiet failure the report exists to prevent.

The column is refused rather than stripped of its default alone. Declaring it
without the default the database has would leave every Diff proposing to add one
back, which is the permanently-red gate stripCast was written to avoid.

On the corpus that found it, process-mining goes from 2 apply failures and a
residual of 1 to zero of both — a fixpoint — and coprocess_steps moves from a
false clean to an honest partial. No other schema in the corpus changes.

Declaring sequences outright remains open; this fixes the lie, not the gap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three buckets, kept apart deliberately: opinions the DSL enforces, practices it
only recommends, and refusals that are merely unbuilt. Conflating the first and
third is how a missing feature gets rewritten as a principle, so the gaps are
listed on the same page with their issue numbers.

Each enforced practice cites its ADR and states the reason it would survive if
sqlb vanished — which is the rule the two schema surveys were triaged by.

Counts come from those surveys: valiro-go (68 tables) and studio-apps (ten
deployments, 233 tables). ADR-0015's prefixing is the clearest case — studio-apps
documents the same rule and enforces it in CI, and it still drifted to 8
violations, whose tables are exactly the ones that collided when the survey built
one database per app. A rule enforced by review drifts; a prefix applied by the
registry cannot.

Also records that ADR-0034's revisit trigger has fired again: composite primary
keys appear in 4 of 10 apps, on configuration tables no REST resource mounts.
The ADR already names the fix — move the refusal into rest.Resource's mount
check rather than lift it — and asked for this to be said in the schema docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… naming

Written to be argued from when meeting a codebase on sqlc or anything else, so
every practice now leads with the reason it holds whether or not sqlb exists.
A practice justified by "the tool wants it" is special pleading, and an adopter
is right to reject it.

Three domains rather than a walk through the ADR list: table and column names,
SQL and schema design, REST. Each entry states the practice, the independent
reason, what goes wrong in real schemas without it, and the mechanism — or
admits there is none.

New material, measured across the same 301 tables:

- timestamptz is enforced and the corpus already agrees — all 82 naive timestamp
  columns are inside goose's own bookkeeping tables, none in an application table
- 8 foreign-key columns have no index leading with that column, which is the one
  practice here that is only half automatic: ExternalRef adds a join index
  because it emits no FK, a local Ref does not
- 227 varchar against 481 text

The gaps stay on the same page, and the overstated claims are marked as such:
#116 is unresolved, ADR-0034's refusal is wider than its justification and the
narrowing is what to argue, and restcompat only judges the contract sqlb
generates.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jryannel
jryannel merged commit 8474d89 into main Aug 2, 2026
3 checks passed
@jryannel
jryannel deleted the claude/serial-column-reported-not-imported branch August 2, 2026 15:49
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.

Diff renders a serial column's nextval default without its sequence, so the generated DDL fails to apply and takes the table's indexes down with it

1 participant