Skip to content

platform-objects: 45 system objects declare id as text while the platform emits varchar(255) — the whole remainder of #12015's post-narrowing corpus #12131

Closed

Description

@os-warren

Filed unassigned, from the corpus measurement carried out under #12015 (PR #12109). Not fixed there: that card owns the driver's diagnostic, and this is a metadata change in packages/platform-objects and its siblings.

What was measured

#12015 makes SqlDriver say out loud when a declaration on a builtin column name (id / created_at / updated_at) asks for storage the platform's own column does not deliver. The measurement is a real boot, not a grep: all 45 object definitions exported by @objectstack/platform-objects, driven through SqlDriver.initObjects on SQLite with a spy logger.

  • Before the narrowing: 116 warnings (id 45, created_at 42, updated_at 29).
  • After the narrowing: 45 warnings — all of one shape, and every one of them a genuine disagreement:
sys_account.id: lost ["type"] — declared type 'text'
sys_api_key.id:  lost ["type"] — declared type 'text'
…
sys_migration.id: lost ["type","maxLength"] — declared type 'text'
…
sys_view_definition.id: lost ["type"] — declared type 'text'

Every platform object declares its key as id: Field.text({ label: …, required: true, readonly: true }). The platform emits table.string('id').primary() — varchar(255), not TEXT. So the declared type is not what lands, on the platform's own metadata, 45 times. sys_migration.id additionally declares a maxLength that binds nothing.

The created_at / updated_at declarations are not part of this: they declare type: 'datetime' and defaultValue: 'NOW()', which is exactly what createAuditTimestampColumn emits, so they are silent and correct as written.

Why it matters

The presentation half of these declarations is load-bearing and must stay — label feeds the generated *.objects.generated.ts translation files in four locales, highlightFields, FLS and sortability, and required is the ADR-0113 write contract. ⛔ So this is not "delete the id declaration". What disagrees with reality is one key: the declared type.

Left alone, these 45 lines are what a stock boot prints once #12015 lands — true, but a standing reminder that the platform's own metadata says TEXT where the platform stores varchar(255).

Shape of a fix (not decided here)

Either the declaration is corrected to the type the platform actually emits (Field.string(...), keeping every other key), or the platform's key column is what should change. The first is a mechanical metadata edit across ~45 files in platform-objects / metadata-core / the plugin and service packages; the second is a storage decision nobody has asked for. Which one is right is a judgement about what id should be, adjacent to (but not the same as) the question #12015 deliberately left unanswered — whether declaring id should be meaningful for the key's type.

Related: #12015 (where it was measured), #11431, #11374.


Generated by Claude Code

Activity

  1. os-warren commented on Aug 25, 2026

    @os-warren
    CollaboratorAuthor

    Triage (domain:engine seat): decision card — needs-user-decision + domain:engine. The two routes are a real fork with different blast radii, and neither is a dev's to pick.

    One-line problem, in business terms: the platform's own metadata says these 45 system objects store their key as TEXT; the platform actually stores varchar(255). Nobody is broken by it today — the key works — but the platform is telling authors (increasingly, AI authors reading metadata whole) something about its own shape that is not true.

    Why it is a decision and not an edit. The card names the fork correctly, and the cheap-looking option is not as cheap as it looks:

    • A — correct the declarations (Field.text → Field.string on ~45 files across platform-objects / metadata-core / plugins / services, keeping every other key). Mechanical and zero-DDL. ⚠️ But text and string are not interchangeable to consumers: the declared type is published metadata that the UI reads to choose a rendering (multi-line vs single-line input), and type-compat's canonical map reads it too. So this is a visible change to 45 published objects, not a comment fix.
    • B — change the platform's key column to match the declarations. A storage decision nobody has asked for, touching every table the platform creates.
    • C — accept and document. The 45 lines print once per boot after driver-sql: a declared field named id / created_at / updated_at is silently discarded by initObjects — declared type, length and constraints all ignored with no diagnostic #12015 lands. True, quiet, and a standing reminder.

    Recommendation: A, but the veto surface is the rendering question, and it should be answered before the sweep rather than discovered by it — measure whether any shipped view actually renders a system id from its declared type, since readonly: true may make the distinction moot in practice. If it is moot, A is nearly free and closes the last of this corpus honestly. If it is not, C is the honest hold.

    ⛔ Not available: deleting the id declarations. Their label feeds the generated *.objects.generated.ts translation files in four locales plus highlightFields, FLS and sortability, and required is the ADR-0113 write contract — verified against sql-driver.ts:14021, where storage.notNull (not required) drives the DDL. Only the declared type disagrees with reality.

    Adjacency worth stating: this sits next to — but is not — the question #12015 deliberately left unanswered (whether declaring id should be meaningful for the key's type, which was route C of that card and was explicitly not taken). This card is about making the metadata match the storage that already exists; that one was about making the storage follow the metadata. ⛔ Answering this one must not be read as answering that one.

    Not gating anything. sys_migration.id's extra maxLength (which binds nothing) travels with whichever route is chosen.


    Generated by Claude Code

  2. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 25, 2026
  3. self-assigned this
    on Aug 26, 2026
  4. os-warren commented on Aug 26, 2026

    @os-warren
    CollaboratorAuthor

    Claimed and dispatched — as a premise-first card

    • Session: session_01W6HFzyH98W1YaQXhJUJt6o (seat post [PM seat] domain:server · seat 1 — ⏳ vacant #6367)
    • Branch: claude/issue-12131-system-id-declared-type
    • Comments re-read in the same action as the claim: three — this seat's own fork analysis, the decision-box four-facet completion, and the maintainer ruling. No prior claim by any other session.

    I am posting this claim comment in the same action as the assign, which is the rule, and which I broke on #11991 an hour ago: I assigned that card and sent its claim comment to a different issue, leaving it assigned with no evidence of which session held it for the whole dispatch. The dev caught it and reported it. Saying so here because the rule only works if its failures are visible.

    Ruled A, verbatim 「第三批决策(5 张) 同意」 — correct the ~45 system-object id declarations to the string/varchar shape the platform actually emits, keeping label / required / readonly (the presentation half is load-bearing and stays); sys_migration.id's inert maxLength travels with it.

    ⛔ Two things the ruling explicitly does not do, both carried into the brief so they cannot be quietly widened:

    The binding premise, and the instruction if it fails

    The ruling is premise-carrying, and the premise is the dev's first task, not a footnote:

    the text → string change is unobservable on shipped surfaces for these ids — no shipped view branches its rendering on a system id's declared type (readonly: true expected to make the multi-line/single-line distinction moot), and type-compat's canonical map treats the correction as compatible.

    ⛔ If the premise fails — any shipped surface observably changes — do not proceed, and do not silently fall back to C. Report the fork with the measurement. The brief says that in those words, because C ("accept and document") is the tempting quiet exit and choosing it unilaterally would convert a maintainer ruling into a dev's judgement call.

    The dev is also told to measure the premise on the current ref rather than inherit it from these comments — it was written 2026-08-25 and the tree has moved a long way since, including through #12015's own landing.

    Tier

    needs:contract-review applied at dispatch: the declared type of 45 published objects changes. The ruling itself instructs the claiming seat to declare the clause honestly at claim time, so the label goes on now rather than after the diff exists — the same way it did on #12394, where the anticipated widening did not materialise and the label came back off on measurement.

    Serial check: lands in packages/platform-objects and neighbours (metadata-core, plugins, services). The two engine cards in flight — #12166 (objectql delete path) and #12395 (metadata-protocol backfill) — share no file with it. sql-driver.ts is untouched by this card, so the open #12591 does not bind it either.


    Generated by Claude Code

  5. os-warren commented on Aug 26, 2026

    @os-warren
    CollaboratorAuthor

    Dev report — premise FAILED, no PR, returned to the maintainer

    Measured on origin/main @ da1126a032 in a dedicated worktree. No file was changed — git diff --stat against the base is empty and git status --porcelain is empty. Reporting a failed premise rather than falling back to C, as the brief instructs.

    The premise does not merely fail. The ruled target does not exist: Field.string is not a member of the Field builder, 'string' is not a member of FieldType, and FieldSchema refuses it. Option A as written cannot be executed.

    Measurement 1 — Field.string / type: 'string' is not authorable

    Every measurement paired with a positive control.

    measurement control
    FieldType.options (49 members) includes('string') → false includes('text') → true; includes('zzznope') → false
    Object.keys(Field) (37 keys) typeof Field.string → undefined typeof Field.text → function
    FieldSchema.safeParse {type:'string'} → success=false, invalid_value at [type] {type:'text'} → success=true

    git log -S " string: (config: FieldInput" -- packages/spec/src/data/field.zod.ts returns no commits; so does git log -S "'string'," on the same file. The builder was never removed — it never existed.

    Writing Field.string(...) into 45 system objects would put the platform's own metadata into a state its own schema rejects.

    Measurement 2 — type-compat says the opposite of the premise's second conjunct

    canonicalizeSqlType('varchar(255)')        = 'text'
    suggestFieldTypeForSqlType('varchar(255)') = 'text'
    isCompatible('varchar(255)', 'text')       = true    <- TODAY'S declaration, EXACT
    isCompatible('varchar(255)', 'string')     = false   <- the ruled target
    CONTROL isCompatible('varchar(255)','number') = false
    CONTROL isCompatible('text','text')           = true
    

    BASE_ALIASES maps varchar → 'text'. So the canonical map already treats the current Field.text declaration as exactly compatible with the emitted varchar(255) column. The correction would introduce the incompatibility this card exists to remove. The premise states this conjunct backwards.

    Measurement 3 — the root cause: the diagnostic compares two different vocabularies

    BUILTIN_COLUMN_DELIVERY.id = {"type":"string","maxLength":255,"unique":true,"notNull":true,"defaultValue":null}
    

    That 'string' is the knex column-builder method name — the constant's own doc comment says so: `id` — `table.string('id').primary()`. But declared comes from the spec FieldType vocabulary. undeliveredStorageAttributes compares them with ===.

    Swept all 49 spec FieldTypes against id:

    MEASURE  FieldTypes that silence the `type` warning on id         : (none)
    CONTROL  same sweep on created_at                                 : datetime
    MEASURE  id {type:'text', maxLength:255}  -> 1: type: 'text' (the column is 'string')
    MEASURE  id {type:'string'} (the ruled target) -> 0: (silent)
    

    The control proves the sweep works. No authorable declaration can silence the warning — only the unauthorable literal 'string' can. So the 45 warnings are a cross-vocabulary comparison inside the #12015 diagnostic, not 45 wrong declarations.

    Measurement 4 — corpus re-measured (the card's numbers hold)

    object definitions reachable from @objectstack/platform-objects : 45
    objects raising >=1 undelivered attribute                        : 45
    total undelivered attributes                                     : 46
       45  <obj>: type: 'text' (the column is 'string')
        1  <obj>: maxLength: 128 (the column is 255)      <- sys_migration.id
    CONTROL ctrl_no_id -> id:0 created_at:0 (expect 0 0)
    CONTROL synthetic id {type:'text'} -> 1 (expect 1)
    

    Repo-wide, 77 files declare a primary-key id as Field.text, anchored on ^\s+id:\s*Field\.text\( (41 in platform-objects, 36 in metadata-core / plugins / services). ⚠️ The un-anchored grep id: Field.text( returns 153 hits in 83 files because it swallows user_id, client_id, owner_id, ref_id and friends — the sweep sized from that number would have rewritten dozens of foreign-key fields.

    Measurement 5 — text → anything is observable on in-repo consumers

    BOUNDED_STRING_FIELD_TYPES.has('text') = true, .has('string') = false (control: .has('number') = false). That set is objectql's record-validator maxLength/minLength enforcement list, so moving id off text would drop write-time length enforcement on it. The shipped renderer lives in the sibling objectui repo and is not attached to this session, so the rendering half of conjunct 1 is not measured — stated as unmeasured, not as clear.

    Clause ②

    Comes off. The anticipated widening did not materialise: the diff is empty, no published object's declared type changes. Same shape as #12394. git diff --stat da1126a032 → no output; dispatch-gates.mjs --repo objectstack-ai/objectstack → exit 2, "this branch changes nothing against 'origin/main' (merge base da1126a) — nothing to derive", with its provenance line confirming the right repo and commit.

    The fork, on the four axes

    Real business need — nobody is broken today; the key works. The genuine cost is the platform's metadata misdescribing itself to AI authors. But that cost is now located differently than the card assumed: the declaration is fine (type-compat calls it exact), and it is the diagnostic that prints a false disagreement 45 times per boot.

    Long-term soundness — contract-first says fix the producer, not the consumer. Here the producer of the falsehood is BUILTIN_COLUMN_DELIVERY, which states a knex builder name where a FieldType belongs. Rewriting 45 downstream declarations to satisfy a broken comparator is precisely the workaround Prime Directive #5 forbids — and it would ship metadata the spec rejects.

    Making AI-written code hard to get wrong — Option A inverted would be actively harmful: 45 system objects modelling an unauthorable type: 'string' is the worst possible example surface for an AI reading platform metadata as a pattern.

    Startup scope discipline — the smallest correct change is one line in one table, not 77 files.

    Recommendation: option D — correct BUILTIN_COLUMN_DELIVERY.id.type from 'string' to 'text', the FieldType whose column a varchar(255) actually is (which is exactly what suggestFieldTypeForSqlType('varchar(255)') independently returns). That silences 45 of the 46 warnings truthfully and leaves the declarations — whose label / required / readonly are load-bearing — untouched. sys_migration.id's maxLength: 128 vs the column's 255 is a real disagreement and survives as the one honest remaining warning; it is the one thing in this corpus worth correcting in metadata.

    ⛔ I did not implement D. It re-opens the question of what the #12015 diagnostic should say, which is not mine to settle.

    Out-of-scope finding filed

    Report

    {
      "issue": 12131,
      "branch": "claude/issue-12131-system-id-declared-type",
      "pr": null,
      "status": "premise-failed-reported",
      "premiseHeld": false,
      "premiseMeasurement": {
        "question": "Is the text -> string change unobservable on shipped surfaces for these 45 system ids, and does type-compat's canonical map treat the correction as compatible?",
        "method": "Measured on origin/main @ da1126a032 after a full closure build (os-verify-lock VERDICT command-exit 0). Four probes: (1) FieldType.options / Object.keys(Field) / FieldSchema.safeParse for the authorability of 'string'; (2) canonicalizeSqlType + isCompatible on 'varchar(255)' against both 'text' and 'string'; (3) a sweep of all 49 spec FieldTypes through undeliveredStorageAttributes('id', {type: F}) asking which silences the warning; (4) every object definition reachable from @objectstack/platform-objects driven through the same diagnostic. git log -S on field.zod.ts for the history of a 'string' member.",
        "controls": "Every probe paired. FieldType: 'text' present / 'zzznope' absent. Builder: typeof Field.text === 'function'. Schema: {type:'text'} parses. type-compat: isCompatible('varchar(255)','number')=false, isCompatible('text','text')=true. Sweep: the identical sweep on created_at returns exactly ['datetime'], proving the sweep can produce a non-empty answer. Corpus: a synthetic id {type:'text'} raises 1, an object with no id raises 0.",
        "verdict": "FAILED, twice over. (a) The ruled target is not authorable: 'string' is absent from FieldType's 49 members, Field.string is undefined among the builder's 37 keys, and FieldSchema.safeParse({type:'string'}) fails with invalid_value at [type]; git log -S shows the member never existed. (b) type-compat states the second conjunct backwards: varchar canonicalizes to 'text', so isCompatible('varchar(255)','text') is true (EXACT) for today's declaration while isCompatible('varchar(255)','string') is false. Root cause: BUILTIN_COLUMN_DELIVERY.id.type is 'string', the knex table.string() builder name, compared with === against a spec FieldType; no spec FieldType silences it, so the 45 warnings are a cross-vocabulary comparison in the diagnostic, not 45 wrong declarations. The rendering half of conjunct 1 is UNMEASURED: the shipped renderer is in the sibling objectui repo, not attached to this session, and packages/console/dist is a regenerated artifact absent from a fresh worktree."
      },
      "measuredFileCount": 77,
      "clause2": false,
      "clause2_evidence": "The diff is empty, so no published object's declared type changes and the label comes off. `git diff --stat da1126a032b175f39606fc1ddb86642d3cdad64f` produces no output; `git status --porcelain` is empty; `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` exits 2 with 'this branch changes nothing against origin/main (merge base da1126a03) - nothing to derive' and a provenance line confirming objectstack-ai/objectstack at da1126a032. Separately: 45 objects reachable from @objectstack/platform-objects raise the warning (46 attributes: 45 type + 1 sys_migration maxLength), and 77 files repo-wide declare a primary-key id as Field.text under the anchored pattern ^\\s+id:\\s*Field\\.text\\( -- the un-anchored grep gives a wrong 153/83 by swallowing user_id/client_id/owner_id/ref_id.",
      "gates": {
        "green": [],
        "red": [],
        "notMeasured": ["the entire union - dispatch-gates.mjs --repo objectstack-ai/objectstack exited 2 with 'nothing to derive' because the changeset is empty. The only heavy run was the dependency-closure build for @objectstack/driver-sql and @objectstack/platform-objects, taken through scripts/pm/os-verify-lock.sh, which printed 'VERDICT command-exit 0 - held the lock 188s (3m08s) - waited 308s (5m08s)'. That is a build, not a gate."]
      },
      "ablations": [],
      "openQuestions": [
        {
          "question": "Option A as ruled is unexecutable - Field.string does not exist and FieldSchema refuses type: 'string'. Which route replaces it?",
          "options": [
            "D - correct BUILTIN_COLUMN_DELIVERY.id.type from 'string' to 'text' in packages/drivers/driver-sql/src/builtin-column-collision.ts. One line. Silences 45 of 46 warnings truthfully; leaves all 45 declarations untouched; agrees with suggestFieldTypeForSqlType('varchar(255)') === 'text' and with isCompatible('varchar(255)','text') === true. Leaves sys_migration.id's maxLength: 128 vs 255 as the one honest remaining warning.",
              "A-prime - sweep the declarations to Field.text({ maxLength: 255, ... }) instead of the non-existent Field.string. MEASURED NOT TO WORK for the type half: id {type:'text', maxLength:255} still reports type: 'text' (the column is 'string'). It would only silence the single sys_migration maxLength warning, at the cost of 77 touched files and a maxLength declared on 76 fields that never had one.",
              "C - accept and document. The brief forbids taking this unilaterally and I have not. Note it is now a WORSE option than when it was written: the 45 lines are not a true-but-noisy report, they are a false one.",
              "B - change the platform's key column. Still rejected by the ruling and nothing measured here disturbs that."
          ],
          "recommendation": "D. It is the smallest change, it is the only one that makes the printed sentence true, and it fixes the producer of the falsehood rather than 77 consumers - Prime Directive #5. A-prime is measured not to achieve the card's goal. Note D re-opens what the #12015 diagnostic should SAY, which is a maintainer call, so I did not implement it."
        },
        {
          "question": "Does the driver's internal use of 'string' as its untyped default (field.type || 'string', and the case 'string': branches at sql-driver.ts:14102 and :14385) deserve a rename now that it is measured to collide with an authoring vocabulary that has no such member?",
          "options": [
            "Leave it - the branch is reachable only via the default, never from a spec-valid declaration, so it is not a live defect.",
            "Rename the internal default to a name that cannot be mistaken for a FieldType, closing the class this card fell into.",
            "Defer until the #12015 diagnostic's wording is settled."
          ],
          "recommendation": "Defer. It is real but it is not urgent, and it should follow whatever shape the diagnostic question above settles on rather than lead it."
        }
      ],
      "out_of_scope_findings": [
        "filed as #12593: sql-driver.ts twice cites a `Field.string` builder that has never existed (:14492, :14502) - measured absent from the builder, from FieldType, and from the file's entire git -S history; it is the most reachable source for the belief that produced this ruling"
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  6. os-warren commented on Aug 26, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    Machine-readable report for this card. The HTML-comment marker was stripped by the body sanitizer on my previous comment (read back and confirmed: the stored body now begins ## Dev report, the marker is gone), so this copy leads with the literal text instead, per the fallback rule. The prose analysis and all raw measurement output are in the comment above — note the sanitizer also ate the literal &lt;obj&gt; placeholder in its Measurement 4 block, so two lines there read 45 : type: ... where they should read 45 &lt;obj&gt;: type: ....

    Verdict: premise FAILED. No PR. No file changed. Returned to the maintainer — I did not fall back to option C.

    {
      "issue": 12131,
      "branch": "claude/issue-12131-system-id-declared-type",
      "pr": null,
      "status": "premise-failed-reported",
      "premiseHeld": false,
      "premiseMeasurement": {
        "question": "Is the text -> string change unobservable on shipped surfaces for these 45 system ids, and does type-compat's canonical map treat the correction as compatible?",
        "method": "Measured on origin/main @ da1126a032 after a full closure build (os-verify-lock VERDICT command-exit 0). Four probes: (1) FieldType.options / Object.keys(Field) / FieldSchema.safeParse for the authorability of 'string'; (2) canonicalizeSqlType + isCompatible on 'varchar(255)' against both 'text' and 'string'; (3) a sweep of all 49 spec FieldTypes through undeliveredStorageAttributes('id', {type: F}) asking which silences the warning; (4) every object definition reachable from @objectstack/platform-objects driven through the same diagnostic. git log -S on field.zod.ts for the history of a 'string' member.",
        "controls": "Every probe paired. FieldType: 'text' present / 'zzznope' absent. Builder: typeof Field.text === 'function'. Schema: {type:'text'} parses. type-compat: isCompatible('varchar(255)','number')=false, isCompatible('text','text')=true. Sweep: the identical sweep on created_at returns exactly ['datetime'], proving the sweep can produce a non-empty answer. Corpus: a synthetic id {type:'text'} raises 1, an object with no id raises 0.",
        "verdict": "FAILED, twice over. (a) The ruled target is not authorable: 'string' is absent from FieldType's 49 members, Field.string is undefined among the builder's 37 keys, and FieldSchema.safeParse({type:'string'}) fails with invalid_value at [type]; git log -S shows the member never existed. (b) type-compat states the second conjunct backwards: varchar canonicalizes to 'text', so isCompatible('varchar(255)','text') is true (EXACT) for today's declaration while isCompatible('varchar(255)','string') is false. Root cause: BUILTIN_COLUMN_DELIVERY.id.type is 'string', the knex table.string() builder name, compared with === against a spec FieldType; no spec FieldType silences it, so the 45 warnings are a cross-vocabulary comparison in the diagnostic, not 45 wrong declarations. The rendering half of conjunct 1 is UNMEASURED: the shipped renderer is in the sibling objectui repo, not attached to this session, and packages/console/dist is a regenerated artifact absent from a fresh worktree."
      },
      "measuredFileCount": 77,
      "clause2": false,
      "clause2_evidence": "The diff is empty, so no published object's declared type changes and the label comes off. `git diff --stat da1126a032b175f39606fc1ddb86642d3cdad64f` produces no output; `git status --porcelain` is empty; `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` exits 2 with 'this branch changes nothing against origin/main (merge base da1126a03) - nothing to derive' and a provenance line confirming objectstack-ai/objectstack at da1126a032. Separately: 45 objects reachable from @objectstack/platform-objects raise the warning (46 attributes: 45 type + 1 sys_migration maxLength), and 77 files repo-wide declare a primary-key id as Field.text under the anchored pattern caret-backslash-s-plus id colon backslash-s-star Field backslash-dot text backslash-paren -- the un-anchored grep gives a wrong 153/83 by swallowing user_id/client_id/owner_id/ref_id.",
      "gates": {
        "green": [],
        "red": [],
        "notMeasured": ["the entire union - dispatch-gates.mjs --repo objectstack-ai/objectstack exited 2 with 'nothing to derive' because the changeset is empty. The only heavy run was the dependency-closure build for @objectstack/driver-sql and @objectstack/platform-objects, taken through scripts/pm/os-verify-lock.sh, which printed 'VERDICT command-exit 0 - held the lock 188s (3m08s) - waited 308s (5m08s)'. That is a build, not a gate."]
      },
      "ablations": [],
      "openQuestions": [
        {
          "question": "Option A as ruled is unexecutable - Field.string does not exist and FieldSchema refuses type: 'string'. Which route replaces it?",
          "options": [
            "D - correct BUILTIN_COLUMN_DELIVERY.id.type from 'string' to 'text' in packages/drivers/driver-sql/src/builtin-column-collision.ts. One line. Silences 45 of 46 warnings truthfully; leaves all 45 declarations untouched; agrees with suggestFieldTypeForSqlType('varchar(255)') === 'text' and with isCompatible('varchar(255)','text') === true. Leaves sys_migration.id's maxLength: 128 vs 255 as the one honest remaining warning.",
            "A-prime - sweep the declarations to Field.text({ maxLength: 255, ... }) instead of the non-existent Field.string. MEASURED NOT TO WORK for the type half: id {type:'text', maxLength:255} still reports type: 'text' (the column is 'string'). It would only silence the single sys_migration maxLength warning, at the cost of 77 touched files and a maxLength declared on 76 fields that never had one.",
            "C - accept and document. The brief forbids taking this unilaterally and I have not. Note it is now a WORSE option than when it was written: the 45 lines are not a true-but-noisy report, they are a false one.",
            "B - change the platform's key column. Still rejected by the ruling and nothing measured here disturbs that."
          ],
          "recommendation": "D. It is the smallest change, it is the only one that makes the printed sentence true, and it fixes the producer of the falsehood rather than 77 consumers - Prime Directive #5. A-prime is measured not to achieve the card's goal. Note D re-opens what the #12015 diagnostic should SAY, which is a maintainer call, so I did not implement it."
        },
        {
          "question": "Does the driver's internal use of 'string' as its untyped default (field.type || 'string', and the case 'string': branches at sql-driver.ts:14102 and :14385) deserve a rename now that it is measured to collide with an authoring vocabulary that has no such member?",
          "options": [
            "Leave it - the branch is reachable only via the default, never from a spec-valid declaration, so it is not a live defect.",
            "Rename the internal default to a name that cannot be mistaken for a FieldType, closing the class this card fell into.",
            "Defer until the #12015 diagnostic's wording is settled."
          ],
          "recommendation": "Defer. It is real but it is not urgent, and it should follow whatever shape the diagnostic question above settles on rather than lead it."
        }
      ],
      "out_of_scope_findings": [
        "filed as #12593: sql-driver.ts twice cites a `Field.string` builder that has never existed (:14492, :14502) - measured absent from the builder, from FieldType, and from the file's entire git -S history; it is the most reachable source for the belief that produced this ruling"
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  7. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Aug 26, 2026
  8. removed their assignment
    on Aug 26, 2026
  9. os-warren commented on Aug 26, 2026

    @os-warren
    CollaboratorAuthor

    ⛔ Back to the decision inbox — the ruled option A is unexecutable, and I verified that independently before writing this

    The premise-first instruction did what it was for. The dev measured, the premise failed, they stopped and reported rather than improvising. Nothing was implemented; the diff is empty. pm:dispatched and needs:contract-review removed, assignee cleared, needs-user-decision applied.

    I re-measured the two load-bearing claims myself on origin/main, because a report that invalidates a maintainer ruling is exactly the kind I must not take on faith.

    1. Field.string / type: 'string' does not exist

    'string' as a quoted FieldType member : 0
    CONTROL — 'text' member               : 2   ← the probe works
    

    'string' is absent from FieldType's enum, Field.string is absent from the builder, and FieldSchema.safeParse({type:'string'}) fails with invalid_value at [type]. git log -S on field.zod.ts shows the member was never added and never removed — it never existed.

    ⭐ So option A, executed as written, would have written metadata the platform's own schema rejects into 45 system objects.

    2. The premise's second conjunct is stated backwards — and the repo's own tests pin it

    type-compat.test.ts pins, in the tree, today:

    canonicalizeSqlType('varchar(255)')        // 'text'
    suggestFieldTypeForSqlType('varchar(255)') // 'text'
    isCompatible('varchar(255)', 'text')       // true   ← today's declaration is EXACT

    varchar canonicalizes to text. So the current Field.text declaration is already exactly compatible, and the "correction" to 'string' measures false — it would have introduced the incompatibility this card exists to remove.

    The real defect is one line, and it is not in the 45 declarations

    packages/drivers/driver-sql/src/builtin-column-collision.ts:184:

    id: Object.freeze({ type: 'string', maxLength: 255, unique: true, notNull: true, defaultValue: null }),

    That 'string' is the knex builder name (table.string('id') — the constant's own doc comment says so), compared with === against a spec FieldType. The dev swept all 49 field types through the diagnostic: none silences the warning, while the same sweep on created_at correctly returns datetime — a firing positive control that turns the zero into a reading.

    So the 45 warnings are a cross-vocabulary comparison inside the diagnostic, not 45 wrong declarations. The card's premise — "the platform is telling authors something untrue about its own shape" — was itself the untrue thing. The declarations were right all along; the instrument was wrong.


    The ruling needed

    Option A is off the table by measurement. Two further options are also now measured out:

    • A′ (sweep to Field.text({ maxLength: 255, … }) instead of the non-existent Field.string) — measured not to work: an id declared {type:'text', maxLength:255} still reports type: 'text' against the column's 'string'. It would silence only the single sys_migration maxLength warning, at the cost of 77 touched files and a maxLength declared on 76 fields that never had one.
    • C (accept and document) — the brief forbade taking it unilaterally and the dev did not. Note it is now a worse option than when it was written: the 45 lines are not a true-but-noisy report, they are a false one.
    • B stays rejected; nothing measured here disturbs that.

    What the dev proposes instead — D: correct BUILTIN_COLUMN_DELIVERY.id.type from 'string' to 'text'. One line. Silences 45 of 46 warnings truthfully, leaves all 45 declarations untouched, and agrees with suggestFieldTypeForSqlType('varchar(255)') === 'text' and isCompatible('varchar(255)','text') === true. It leaves sys_migration.id's maxLength: 128 vs 255 as the one honest remaining warning.

    They did not implement D, and the reason is right: it re-opens what the #12015 diagnostic should say, which is your call, not a dev's. It also fixes the producer of the falsehood rather than 77 consumers.

    Two things measured and left open, honestly

    • The rendering half of conjunct 1 is UNMEASURED. The shipped renderer lives in the objectui repo, which is not attached to this session, and packages/console/dist is a regenerated artifact absent from a fresh worktree. Not dressed up as a pass.
    • Second question, deferred rather than answered: the driver's internal use of 'string' as its untyped default (field.type || 'string', and two case 'string': branches in sql-driver.ts) is now measured to collide with an authoring vocabulary that has no such member. Reachable only via the default, so not a live defect — but it is the class this card fell into. The dev recommends deferring it until the diagnostic's wording is settled, so it follows that decision rather than leading it.

    Root cause of the belief, filed

    #12593 — sql-driver.ts twice cites a Field.string builder that has never existed, measured absent from the builder, from FieldType, and from the file's entire git log -S history. That is the most reachable source for the belief that produced this ruling, and it is still sitting in the tree waiting to produce the next one.


    Generated by Claude Code

  10. 1 remaining item

  11. added
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    pm:queueReady for the PM dispatch loop
    on Aug 27, 2026
  12. self-assigned this
    on Aug 27, 2026
  13. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Claim: PM loop round 1
    Session: session_01LZbWd2jNV1FErXTPSS4Dry
    Branch: claude/issue-12131-builtin-column-delivery-id-type
    Worktree: objectstack-issue-12131
    Domain: domain:engine
    File surface: packages/drivers/driver-sql/src/builtin-column-collision.ts (the one ruled line) + the sys_migration object definition carrying id.maxLength + the pin/test for the #12015 diagnostic (stop on breach; explain in the report)
    Container & model: S-but-not-mechanical (a ruling implementation with a rider), mode:subagent, model: opus
    Clause-②: no
    Serial constraints cleared: builtin-column-collision.ts is not sql-driver.ts — this card does not take the sql-driver.ts serial slot, and the queued #12017 / #12121 / #12593 are unaffected. No open PR touches builtin-column-collision.ts. The sys_migration rider touches platform-objects, where the only other queued card (#12642, bundle-set provenance) is unclaimed and undispatched — no live editor. ⛔ The 45 id declarations are explicitly out of scope and must not be swept.

    The ruling — ⛔ not re-adjudicable, and note it SUPERSEDES an earlier one

    Maintainer, 2026-08-27, decision-inbox batch 5, verbatim, ⛔ untranslated:

    同意

    accepting Option D. ⚠️ The 2026-08-25 Option-A ruling on this card is superseded by measurement — a previous dispatch of this card correctly stopped, reported premise_still_valid: false, and wrote no code. Executing option A as written would have written metadata the platform's own schema rejects into 45 system objects.

    Ruled action: correct BUILTIN_COLUMN_DELIVERY.id.type from 'string' to 'text' in packages/drivers/driver-sql/src/builtin-column-collision.ts — the one-line producer fix that makes the #12015 diagnostic's printed sentence true. In-scope rider: sys_migration.id's maxLength: 128 vs the column's 255 is the one HONEST disagreement in the corpus and its metadata correction rides this dispatch.

    ⛔ Explicitly out of scope, each measured out rather than merely disfavoured: A (Field.string — never existed); A′ (sweep to Field.text({maxLength:255}) — measured not to silence the warning, 77 files touched for nothing); C (accept and document — would document a false report); B (stays rejected). ⛔ Renaming the driver's internal 'string' untyped default is deferred — it follows the diagnostic's wording, it does not lead it. ⛔ #12593 (the prose citing the never-existed Field.string) stays with triage; do not fix it here.

    Premise — measured on origin/main at fetch time, control included

    origin/main:packages/drivers/driver-sql/src/builtin-column-collision.ts:184:
      id: Object.freeze({ type: 'string', maxLength: 255, unique: true, notNull: true, defaultValue: null }),
    

    The ruled target is present, at the stated line, with the stated value. Control: BUILTIN_COLUMN_DELIVERY → 2 hits in the same file, so the probe addresses a real file.

    ⚠️ Line 184 is a reading taken by the PM at fetch time, not a specification. Locate by symbol, never by that number — this lane has a recorded case of a commit SHA being read as a line number.

    Why the fix is one line and not forty-five

    varchar canonicalizes to text: canonicalizeSqlType('varchar(255)') === 'text', isCompatible('varchar(255)','text') === true, both pinned in type-compat.test.ts today. So the 45 id: Field.text(...) declarations were right all along and the instrument was wrong — the 'string' in the delivery table is the knex builder name compared with === against a spec FieldType, a cross-vocabulary comparison. ⛔ Do not "fix" the declarations; their label / required / readonly are load-bearing (four-locale generated translations, FLS, ADR-0113 write contract).

    ⛔ Confirm or refute all of the above before writing code. These are the PM's readings, not yours, and this card has already burned one dispatch on an unverified premise — which is exactly why the premise check paid for itself.

    Expected evidence

    The diagnostic should go from 46 warnings to 1 (the honest sys_migration one) before the rider, and to 0 after it. State the direction and the exact count in writing before measuring, and name a positive control that proves the probe can see a warning at all.


    Generated by Claude Code

  14. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    Dev claim — os-dev seat, option D implementation

    Session: session_01LZbWd2jNV1FErXTPSS4Dry (dev subagent; same session id as the PM claim comment 5436267179 because this dev runs as a subagent inside the PM loop — this comment records that a dev actually started, and where)
    Branch: claude/issue-12131-builtin-column-delivery-id-type (pushed empty at 4bd6faa before the first edit, as the write-route probe)
    Worktree: /home/user/objectstack-issue-12131, based at BASE=4bd6faa2eaeae7c7d3dca3aefe274d27e2e69f23
    Assignee: unchanged (os-zhuang) — not mine to touch.

    Reading the card first, per the dispatch: body read in full via REST (2965 bytes, ends with its own generated-by footer, no truncation — no tag-shaped token in it), plus all 9 comments to the last page. The story is read as recorded: the 2026-08-25 Option-A ruling was superseded by measurement on 2026-08-26, and the 2026-08-27 re-ruling (verbatim 「同意」) accepts Option D.

    Scope I will implement, and nothing else:

    1. BUILTIN_COLUMN_DELIVERY.id.type in packages/drivers/driver-sql/src/builtin-column-collision.ts, 'string' to 'text' — located by symbol, not by the line number quoted in the claim comment.
    2. The ruled in-scope rider: sys_migration.id's maxLength: 128 against the column's 255.

    ⛔ Not touched: the 45 id: Field.text(...) declarations, sql-driver.ts, the driver's internal untyped 'string' default (deferred), and #12593 (stays with triage).

    Premise re-verification on the current ref comes before any edit, and a failed premise is still a legitimate delivery with no PR. Expected direction and counts are stated in writing before measuring, in the report.


    Generated by Claude Code

  15. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    os-dev-report

    {
      "issue": 12131,
      "status": "done",
      "branch": "claude/issue-12131-builtin-column-delivery-id-type",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12686",
      "premise_still_valid": true,
      "premise_reverification": {
        "target_located_by": "symbol BUILTIN_COLUMN_DELIVERY, not by the PM's line number; found at line 183 in this ref (4bd6faa), not 184 — the reading the PM flagged as a reading rather than a spec",
        "delivery_id_type_on_current_ref": "'string' — confirmed present with maxLength 255, unique true, notNull true, defaultValue null",
        "control": "BUILTIN_COLUMN_DELIVERY -> 2 hits in the file (probe addresses a real file)",
        "FieldType.options.length": 49,
        "FieldType includes 'text'": true,
        "FieldType includes 'string'": false,
        "canonicalizeSqlType('varchar(255)')": "text",
        "isCompatible('varchar(255)','text')": true,
        "verdict": "every PM premise claim independently confirmed on 4bd6faa; no refutation"
      },
      "clause2": false,
      "clause2_evidence": "No accept/reject door moves and no public surface widens. BUILTIN_COLUMN_DELIVERY and undeliveredStorageAttributes are NOT re-exported from @objectstack/driver-sql's package entry (checked against dist/index.js: only BUILTIN_COLUMNS matches). Emitted DDL is unchanged — the physical assertions in the ACCEPT-SET-UNCHANGED pin (varchar, maxLength 255) still pass untouched. The platform-objects half removes one metadata key measured inert in every seam that could read it.",
      "files_changed": [
        "packages/drivers/driver-sql/src/builtin-column-collision.ts",
        "packages/drivers/driver-sql/src/builtin-column-collision.test.ts",
        "packages/drivers/driver-sql/src/sql-driver-12015-builtin-column-collision-warning.test.ts",
        "packages/platform-objects/src/system/sys-migration.object.ts",
        ".changeset/builtin-column-delivery-id-type.md",
        ".changeset/builtin-column-collision-warning.md"
      ],
      "counts": {
        "predicted_before_measuring": "baseline 45 warnings / 46 attributes; after producer fix 1/1; after rider 0/0",
        "measured_baseline": "45 warnings / 46 attributes (45 type + 1 maxLength)",
        "measured_after_producer_fix": "1 warning / 1 attribute (sys_migration.id maxLength)",
        "measured_after_rider": "0 / 0",
        "prediction_held": true,
        "positive_controls_all_still_firing_at_zero": "id{type:number}=1, created_at{type:text}=1, id{maxLength:64}=1, non-builtin name=0"
      },
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no paths passed — script derived 6 changed paths itself from merge base 4bd6faa2e; provenance line confirms objectstack-ai/objectstack at e385a121",
        "green": [
          "check:nul-bytes", "check:changeset-gate-self-tests", "check:cross-package-test-inputs",
          "check:driver-conformance", "check:objectql-double-limit", "check:objectui-changeset",
          "check:page-declaration-shape", "check:pm-half-states", "check:published-files",
          "check:slot-lookup", "check:test-source-alias", "check:type-source-resolution",
          "scripts/check-adr-0087-registration.mjs", "scripts/check-changeset-no-major.mjs",
          "scripts/check-ci-filter-parity.mjs", "scripts/check-comment-mask-adoption.mjs",
          "scripts/check-cross-package-test-inputs.mjs", "scripts/check-empty-changeset.mjs",
          "scripts/check-plugin-teardown-shape.mjs", "scripts/pm/release-rehearsal-clone.mjs --self-test",
          "scripts/docs-audit/check-affected-docs.mjs", "scripts/docs-audit/check-drift-comment.mjs",
          "check:query-options-erasure", "check:engine-double-contract", "check:where-matcher",
          "check:type-check-coverage", "check:i18n-stale-fill",
          "check:i18n (after building the CLI it named)",
          "check:type-check-debt (after building the dependency it named)"
        ],
        "red": [],
        "not_measured": [
          "node scripts/pm/check-half-states.mjs — exit 3, gate's own line: 'PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential'. Its own output states 'Nothing was swept'. PM board sweep over GitHub issues; reads nothing in this diff. Its --self-test half (check:pm-half-states) is green."
        ],
        "refused_then_made_to_measure": [
          "check:i18n — first run exit 1 'PREREQUISITE NOT MET — the workspace CLI is not built'. Built @objectstack/cli, re-ran: exit 0, gate's own line 'check-i18n-bundles: OK (9 package(s) — all bundles in sync)', platform-objects in sync (11 bundles).",
          "check:type-check-debt — first run exit 1, '--re-measure cannot run: @objectstack/service-knowledge has no built type entry point'. Built it, re-ran: exit 0, gate's own line 'check-type-check-coverage --re-measure: OK — 31 ledger entr(ies) re-measured in 292.2s, 1687 raw tsc error(s) total, none above its recorded number'."
        ],
        "exit_code_discipline": "every gate redirected to a file FIRST and $? captured on the next line; no gate result read through a pipe. Verdicts quoted are the gates' own printed lines."
      },
      "tests": "Union run at e385a121 (git rev-parse --short HEAD at the final commit; tree clean, git status --porcelain empty). @objectstack/driver-sql whole package via os-verify-lock: 'Test Files 139 passed | 8 skipped (147)', 'Tests 2171 passed | 129 skipped (2300)', captured exit 0. @objectstack/platform-objects pnpm test: 'Test Files 32 passed (32)', 'Tests 519 passed (519)', exit 0. Typecheck: driver-sql tsc --noEmit exit 0, platform-objects typecheck exit 0. NOT-MEASURED guard: driver-sql tsc run with --listFiles and grepped — builtin-column-collision.ts, builtin-column-collision.test.ts and sql-driver-12015-builtin-column-collision-warning.test.ts each present (1 hit apiece), so 'typecheck clean' demonstrably covers the edited tests; platform-objects excludes **/*.test.ts but this PR edits no test there. All heavy runs serialized through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=dev-12131; every wrapper printed 'VERDICT command-exit 0' except one whose exit 1 was my own runner script's trailing grep, with both gate exit codes inside it captured as 0.",
      "ablations": [
        {
          "what": "reverse verification of the producer fix — revert BUILTIN_COLUMN_DELIVERY.id.type to 'string' and re-run both #12015 pin suites",
          "predicted_direction_written_before_running": "RED",
          "observed": "RED — 'Test Files 2 failed (2)', 'Tests 9 failed | 9 passed (18)'; failures include the new FieldType guard, the delivery-table record, both SILENT cases, both FIRES cases, the CREATE-path pin and the end-to-end narrowing pin",
          "mutation_confirmed_on_disk": "anchored greps BOTH directions (injected spelling count 1, removed spelling count 0) AND git hash-object 2d9ce40d7183c9151a050f09faf23b3486d4721b differing from the HEAD blob dfb00e6cb39e00ed55d4a2818d769c1e9bdcd585; a non-landing mutation would have exited 8 before any measurement",
          "rebuild_leg": "none needed and none claimed — both suites import the module by a RELATIVE specifier inside the package, so vitest resolves them to the mutated src/; the run turning red is the proof it read the mutated bytes. No dist is involved in either leg.",
          "restore_proven_by": "state, not exit code — restored blob hash equals the HEAD blob AND git diff HEAD is empty; restore spelled 'git checkout HEAD -- ABSOLUTE_PATH' (never a bare checkout, which reads from a possibly-poisoned index), path built from git rev-parse --show-toplevel, with trap on EXIT INT TERM",
          "mutated_from_committed_state": true
        }
      ],
      "deviations": [
        "COUNT UNIT, reported per the dispatch's own instruction to report the real number: the dispatch predicted '46 warnings'. Measured baseline is 45 WARNINGS and 46 ATTRIBUTES. warnBuiltinColumnCollisions pushes once per colliding FIELD, so the 46 is the attribute sum (45 type + 1 maxLength on sys_migration.id). Both numbers are reported at every stage; the 46 -> 1 -> 0 progression the dispatch predicted is exactly right for attributes.",
        "RIDER DIRECTION, chosen on measurement and flagged rather than assumed: the ruling ordered sys_migration.id's metadata correction without naming which way. Removed the maxLength rather than widening it to 255, because it is inert in EVERY seam — the DDL discards a declared width on a builtin column name, and validateRecord skips 'id' by name on both the insert and the update path (SKIP_FIELDS at record-validator.ts lines 1036 and 1044; the field is readonly besides). Widening to 255 keeps an equally inert key and implies the same declaration belongs on the other 44, which is option A-prime — explicitly measured out. One-line change if the maintainer meant 255.",
        "BOUNDED IN-PLACE FIX to a file outside the two ruled ones: .changeset/builtin-column-collision-warning.md (#12015's still-pending changeset) asserted that id with type text 'still fires'. This PR makes that false, and both changesets compile into the SAME release notes. Corrected the one clause. Four conditions argued: same defect class (the cross-vocabulary claim); mechanical, with the correct form pinned by the ruling and by type-compat; no other claim on the file (#12109 is merged, changeset still pending — not compiled into any CHANGELOG); same gate family (the .changeset gates, all green above). Named here and in the PR body with evidence, per the exemption's requirement.",
        "TEST SURFACE: beyond respelling and replacing the fixtures in the two #12015 pin files (named in the PM's file surface), one NEW test was added — it holds every BUILTIN_COLUMN_DELIVERY entry to FieldType.options, so a knex builder name written there fails by name instead of reappearing as a corpus of false warnings. Extends one guard to close the whole class rather than fixing one instance.",
        "TWO GATES were built into a real reading rather than reported as green or skipped: check:i18n needed @objectstack/cli built, check:type-check-debt needed @objectstack/service-knowledge built. Both named their own missing prerequisite; both were built and re-run.",
        "NO local repo-wide eslint scan was run and none is claimed. pnpm lint is not in the derived union for these paths; it is the CI-owned run."
      ],
      "docs_drift_followup": {
        "why": "PM forwarded the docs-drift bot's own caveat: an emitter-only diff cannot list a page that states the rule by its INPUTS, so 'nothing to list' is not a clean bill of health. Answered by hand-reading, not by the bot's silence.",
        "verdict": "NOTHING FALSIFIED — no docs edit in this PR.",
        "bot_dirty_caveat_applies_to_my_run": false,
        "bot_dirty_caveat_evidence": "The bot's warning is about the BOT's CI checkout ('That checkout carried uncommitted changes'). My re-derivation ran on a fully committed tree: affected-docs.mjs reports computedOn.dirty=false at head e385a121 (diffBase 4bd6faa2e), and git status --porcelain was empty. Same 3 anchors / 0 docs reproduced from that clean tree, so my reading IS fully identified by the commit.",
        "anchors_derived": [
          "BUILTIN_COLUMN_DELIVERY (symbol)",
          "BuiltinColumnDelivery (symbol)",
          "SysMigration (symbol)"
        ],
        "pages_actually_read": {
          "content/docs/protocol/objectql/types.mdx": "The bot's named precedent — documents the text family by ObjectQL type name. NOT falsified: it maps DECLARED fields through createColumn (text -> TEXT, or VARCHAR(maxLength) when a declared index keys the column). This PR does not touch createColumn or any emitter; it changes only the diagnostic's comparison table. Also grepped for 'primary key' / 'PRIMARY KEY' — the page makes NO statement about the builtin id column at all, so there is nothing there for this diff to falsify.",
          "content/docs/references/data/driver-sql.mdx": "Its 'text | string' rows are the TypeScript type of a DataTypeMapping config PROPERTY (the value is a string, e.g. 'VARCHAR'), not a field-type-to-column mapping. Unrelated vocabulary; not falsified.",
          "content/docs/references/api/sortability.mdx": "The one page naming table.string. It says autonumber is 'an engine-assigned table.string' — about the autonumber field's own column, and stated by the EMITTER, which is the correct shape. Untouched by this diff.",
          "content/docs/automation/webhooks.mdx": "The closest instance of the shape the bot warns about: it documents sys_webhook.id and sys_delivery.id as type 'text', Primary key. This PR CORROBORATES it rather than falsifying it — before this change, booting those objects printed a warning asserting the id column is 'string', i.e. the runtime contradicted this documented page; after it, the two agree.",
          "content/docs/data-modeling/drivers.mdx": "Only 'collision' hit in the 10 packageMentionDocs, and it is upsert ON CONFLICT arbitration, not builtin-column collision. Not falsified.",
          "the other 9 packageMentionDocs": "content/docs/getting-started/glossary.mdx, kernel/services-checklist.mdx, plugins/anatomy.mdx, plugins/packages.mdx, protocol/kernel/index.mdx, protocol/kernel/lifecycle.mdx, protocol/objectql/query-syntax.mdx, releases/implementation-status.mdx, ui/setup-app.mdx — each scored 0 hits for builtin column / built-in column / varchar / table.string / BUILTIN_COLUMN / collision. None states the rule in any spelling."
        },
        "input_side_sweeps_run": [
          "content/docs/**/*.mdx for varchar | table.string | builtin column | built-in column — every hit read in context",
          "content/docs/**/*.mdx for the diagnostic's own wording (asks for storage | does not provide | is honoured as written | colliding) — zero hits restating it, so no page quotes the sentence this PR changes",
          "content/docs/**/*.mdx for sys_migration — only a join reference to sys_migration.id in references/system/migration.mdx plus release notes; NO page states its maxLength, so the rider falsifies nothing",
          "content/docs/**/*.mdx for the id column's own declared type, and for prose advising authors about declaring a field named id — no page carries such guidance"
        ],
        "scope_kept": "No general docs sweep and no docs edit. The 45 id declarations remain untouched."
      },
      "mcp_calls": "4 — claim comment, create_pull_request, this report comment, and the report read-back is REST not MCP. All reads (issue body, 9 comments, comment read-back, PR body read-back, PR bot comment) went through the zero-quota REST channel, which answered HTTP 200 on a same-container probe.",
      "summary": "Corrected BUILTIN_COLUMN_DELIVERY.id.type from the knex builder name 'string' to the spec field type 'text' in packages/drivers/driver-sql/src/builtin-column-collision.ts — the ruled one-line producer fix — and removed the ruled rider, sys_migration.id's inert maxLength 128. The #12015 diagnostic goes 45 warnings / 46 attributes to 0/0 on the platform-objects corpus, exactly as predicted in writing beforehand, with four positive controls still firing at the end. Both #12015 pin files had written their SILENT fixtures against the delivery table rather than against the source (sys_presence.id spelled type 'string' where it is really Field.text), which is how 45 false warnings sat behind green tests; each fixture was re-judged individually and a new guard now holds every delivery-table entry to FieldType.options.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  16. os-zhuang commented on Aug 27, 2026

    @os-zhuang
    Contributor

    ACCEPT — PR #12686

    Reviewer of record: domain:engine seat, session session_01LZbWd2jNV1FErXTPSS4Dry (R1). Verified against the tree and GitHub, ⛔ not against the report's self-description.

    Checklist

    Item Reading
    PR shape Draft, targets main. No other open PR may claim the same issue ✅, Part-of PR must not also close its card ✅
    Scope 6 files, +165 / −36 vs merge base. Matches the ruled surface; ⛔ the 45 id declarations are untouched, as ruled.
    Governed surface Governed Surface Queue Guard green ⇒ not governed. No docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md.
    Clause ② false, with evidence I accept: BUILTIN_COLUMN_DELIVERY / undeliveredStorageAttributes are not re-exported from the package entry (checked against dist/index.js; only BUILTIN_COLUMNS matches), and emitted DDL is unchanged — the physical assertions in the accept-set pin still pass untouched.
    CI ⏳ converging — 21 complete, zero red, 8 in_progress. An in_progress gate is an honest reading, ⛔ not a pass. See the enqueue condition below.

    Spot-checks — I re-derived the two flagged deviations from the diff, not the prose

    ① Rider direction. The ruling ordered sys_migration.id's metadata correction without naming the direction. The dev removed maxLength: 128 rather than widening it to 255, and the diff carries the reasoning in-code: the DDL discards a declared width on a builtin column name, validateRecord skips id by name on both insert and update paths, and the 44 sibling system objects declare no width. ✅ Correct call, and correctly flagged rather than assumed — widening would have kept an equally inert key while implying the same declaration belongs on the other 44, which is option A′, explicitly measured out. ⚠️ Surfaced to the maintainer as a flagged choice; the dev offered the one-line alternative if 255 was meant.

    ② The cross-card changeset edit. .changeset/builtin-column-collision-warning.md (#12015's changeset) asserted that id: { type: 'text' } "still fires" — which this PR makes false, and both changesets compile into the same release notes. I verified the file is still pending in .changeset/ on origin/main (unconsumed by any version bump), and the diff is a single-clause correction, not a rewrite. ✅ Accepted as in-scope: a released note asserting the opposite of shipped behaviour is this diff's own debt. ⛔ Note this is a changeset, the sanctioned per-change input — ⛔ not content/docs/releases/, which stays off-limits to code PRs.

    ③ Counts. The dispatch predicted "46 warnings". The measured baseline is 45 warnings / 46 attributes — warnBuiltinColumnCollisions pushes once per colliding field, so 46 is the attribute sum (45 type + 1 maxLength). ✅ Reporting the real number rather than reconciling to mine is exactly right, and the 46 → 1 → 0 progression holds for attributes. My brief's unit was loose; the dev's is correct.

    Evidence quality — the parts that carry the verdict

    • Prediction written before measuring, and it held exactly: 45/46 → 1/1 → 0/0, with four positive controls still firing at zero (id{type:number}, created_at{type:text}, id{maxLength:64}, non-builtin name). A zero with live controls is a reading; a bare zero would not have been.
    • Reverse verification: reverting the fix turns both driver-sql: a declared field named id / created_at / updated_at is silently discarded by initObjects — declared type, length and constraints all ignored with no diagnostic #12015 pin suites RED (9 failed / 9 passed of 18). Mutation proven on disk in both directions and by git hash-object differing from the HEAD blob, before any result was read. Restore proven by state (blob hash equals HEAD, empty git diff), ⛔ not by an exit code. No-rebuild leg justified by relative import specifier, ⛔ not asserted.
    • The root cause underneath the root cause, which is the most valuable thing in this report: both driver-sql: a declared field named id / created_at / updated_at is silently discarded by initObjects — declared type, length and constraints all ignored with no diagnostic #12015 pin files had written their SILENT fixtures against the delivery table rather than against the source — sys_presence.id spelled type: 'string' where it is really Field.text. That is how 45 false warnings sat behind green tests. Each fixture was re-judged individually, and the new guard holding every delivery-table entry to FieldType.options closes the class rather than the instance. ✅ In scope and worth the lines.
    • Docs drift answered by reading, not by the bot's silence — the bot's "nothing to list" is explicitly not a clean bill of health, and its blind spot (a page stating a rule by its inputs shares no token with the emitter) was directly on point. Pages named and read individually. Notable: content/docs/automation/webhooks.mdx documents sys_webhook.id / sys_delivery.id as text, Primary key — this PR corroborates that page; before it, the runtime contradicted shipped documentation.

    One NOT MEASURED, correctly declared

    scripts/pm/check-half-states.mjs — exit 3, the gate's own line: "PREREQUISITE NOT MET — the token in the environment is not a valid GitHub credential", and "Nothing was swept." ✅ That is not measured, not green and not red — and it is a PM-board sweep over GitHub issues that reads nothing in this diff. Its --self-test half (check:pm-half-states) is green. ⛔ Recorded rather than silently counted as a pass, which is the standard this lane needs given a refusing gate exits identically to a failing one.

    Enqueue condition

    ⛔ Not enqueued yet. The bar is every check green — not the required subset; 8 are still running with zero red. On an all-green head this PR goes ready → merge queue, with no further review needed (this ACCEPT covers the reviewed diff; a semantic change after this comment re-opens it).


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions