Skip to content

docs(adr-0104): step 3 promises an abort the prescribed Postgres USING clause does not perform — measured, it flattens the cell instead #16183

Description

@claude

What is wrong

ADR-0104's --apply R4 block promises that step 3 moves a media column while

aborting on the first cell that is not a JSON string

and the paragraph immediately below prescribes the realisation:

Postgres retypes the column to a varchar with a USING clause that unquotes the JSON string

Measured on live PostgreSQL 16.13, those two sentences contradict each other. The
USING clause does not abort — it converts. With one un-backfilled inline-object row
present, ALTER TABLE … ALTER COLUMN … TYPE varchar(2048) USING (col #>> '{}') was
ACCEPTED, and the object was flattened to the literal text
{"url":"https://x/y.png"} in a varchar column. #>> '{}' extracts any json type
as text; "unquotes the JSON string" is only what it does when the cell happens to be a
string.

⇒ An implementer who reads the prescribed statement as delivering the stated abort
silently destroys exactly the rows the backfill has not converted — the rows the
reconciliation exists to find.

The ADR's own confidence gap 2 already says the sketch is unrehearsed. This card is not
that gap; it is the result of the rehearsal it asked for, and the result is that one
half of the prescription does not implement the other half.

What was measured, and what was not

  • Measured (live PostgreSQL 16.13, first step-3 rehearsal on that dialect): the
    retype above accepts a non-string cell and flattens it. The pre-check that does
    implement the ADR's requirement:
    select count(*) from t where json_typeof(col) IS DISTINCT FROM 'string' — returned
    1 on the same fixture, i.e. it sees the row the DDL did not refuse.
  • Measured (SQLite, the same rehearsal): json_extract(col,'$') gated on
    json_valid + json_type = 'text' converts a quoted cell, leaves an already-bare
    cell untouched
    , and is idempotent on re-run. json_type of an un-backfilled
    inline-object cell is 'object', so that is the correct abort discriminator there.
  • ⚠️ NOT measured: MySQL. Its ordering question (gap 2's own "the retype must precede
    the unquote or the two must be one statement") is untouched by this card.
  • ⚠️ NOT measured: whether some other Postgres realisation would abort on its own.
    #>> '{}' is the idiom the ADR's words describe, and it does not; whether a different
    USING expression could is an open question, not a finding.

What this card changes

docs/adr/0104-field-runtime-value-shape-contract.md only — the Confidence gaps
section (and, if the author judges it clearer, the sketch paragraph itself), so that the
governed document no longer reads as though the USING clause performs the abort.
The requirement ("aborting on the first cell that is not a JSON string") is correct
and stays exactly as written; it is the statement list under it that was optimistic.

Two things the wording must end up saying, however it is phrased:

  1. The abort is a pre-check step 3 runs before any DDL, not a property of the retype.
  2. The Postgres rehearsal has now happened and this is what it returned — gap 2 is
    partly closed by a measurement whose result was negative, and gap 1's Postgres half
    moved from "reasoned" to "measured" for this one statement.

Boundaries

  • docs/adr/** is a governed surface: docs-only PR, draft, human merge. No code
    rides along.
  • Do not fold this into a code PR. The reason this is a separate card at all is
    that a code PR may not edit docs/adr/**.
  • ⛔ Do not re-open the ADR's decision. D1/D3, the window, and the end-state column are
    ruled; this card corrects a statement about how a step is performed, nothing else.
  • ⛔ Do not edit content/docs/releases/**.

Provenance

Measured by the #15989 execution round, which deliberately landed no code and stood up
a live PostgreSQL 16.13 to rehearse the sketch. Ruling and the full measurement:
#15989 (comment)

Part-of #15989.


Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions