Repository navigation
driver-sql(schema-drift): during #15989's dual-encoding window a static JSON_COLUMN_FIELD_TYPES cannot serve both moved and unmoved deployments — ⚠️ the body's "reports the ruled end-state" framing is MEASURED FALSE, see comment 5588614136 #16184
Description
What is wrong
ADR-0104's addendum rules that a media field (FILE_REFERENCE_TYPES) ends up in a
string column — VARCHAR(2048) on Postgres and MySQL — holding the bare
sys_file id. detectTableDrift now reports exactly that end-state as an error,
and offers a remedy that undoes it.
The three conjuncts are all satisfied by a media field over a varchar column, read on
today's origin/main (packages/drivers/driver-sql/src/schema-drift.ts):
JSON_COLUMN_FIELD_TYPESis seeded...STRUCTURED_JSON_TYPES, ...FILE_REFERENCE_TYPES, ...MULTI_OPTION_TYPES, 'object', 'array'— so afile/imagefield is a member.declaresJsonColumn = JSON_COLUMN_FIELD_TYPES.has(declaredType) || field.multiple === true
⇒ true for a single-valuefile.jsonColumnTypeIsLoadBearing(dialect)⇒ true onpostgresandmysql.acceptsStringifiedJson(col.type)is/char|text/i⇒ true forvarchar(2048).
⇒ the finding is emitted with kind: 'type_mismatch', severity: 'error',
category: 'needs_confirm', and
op: { type: 'manual_column_type_change', to: 'json', from: '<the varchar>' }.
That op is the reverse of the ruling. Every deployment that has reached — or is told
to reach — ADR-0104's end-state is instructed by its own tooling, at error severity, to
convert the column back to json.
Because the field is single-value, declaresArray is false, so the message takes the
half that "carries neither the command nor the statement" — the operator gets an error
with no runnable repair, only the manual_column_type_change op pointing the wrong way.
Why this is live now, and not only after step 3
This became reachable for single-value media tonight, when #15771's widening landed
as PR #16073: before it, declaresJsonColumn was field.multiple === true alone and a
single-value file was out of scope. It is now in scope, and there are two populations
that can present a varchar media column on Postgres/MySQL:
- After step 3 of
os migrate files-to-references --apply— the ruled end-state,
by construction. ⚠️ Possibly already today: the ADR records that the generator's
FIELD_TYPE_SQL_MAPalready emitsVARCHAR(2048)for all five media members, and
its pin block is labelled "Recorded divergence, NOT coverage" precisely because that
divergence is true on every deployment until the driver moves.
⛔ Population 2 is REASONED, NOT MEASURED, and it is the first thing this card must
settle. The conjunct analysis above is a reading of the source, not an observation of a
running datastore. Measure it before anything else: stand up a generator-created
datastore on live Postgres (the Temporal Conformance (live PG + MySQL) job's
postgres:16 is the in-repo route) and read what detectTableDrift actually returns for
a file field. Publish the finding object you get, or publish that you got none and why.
If population 2 turns out to be empty, this card is a blocker on step 3 rather than a
live defect — say which one it is, with the measurement, and let that decide the
priority.
What the fix has to reconcile
The two halves are both right about their own population and cannot both keep the same
predicate:
- A media column that is still JSON-encoded (a deployment before step 3, or without
the flag) over a varchar column is driver-sql schema-drift: the json-vs-texttype_mismatchfinding is keyed tofield.multipleonly, so a SINGLE-value JSON-class column (file family, STRUCTURED_JSON_TYPES) on a char/text column is never reported — the column a hand-run generated migration creates today #15771's silent corruption and must stay a finding. - A media column that is bare-id encoded (after step 3, on the flag) over a varchar
column is the ruled end-state and must be silent.
⇒ The discriminator is the deployment's adr-0104-file-references flag, not the
field type — the same key the driver's own encoding arm is ruled to use. Whatever shape
the fix takes, detectTableDrift has to be able to ask that question, and today it has
no such input. ⛔ Do not "fix" this by dropping FILE_REFERENCE_TYPES from
JSON_COLUMN_FIELD_TYPES: that set is pinned in both directions against the driver's own
isJsonField by schema-drift.json-column-parity.test.ts, and unpinning it re-opens the
fork that module exists to close.
isJsonField's answer is frozen into
jsonFields[object] at registration time, so keying the readers on a per-deployment
flag is an architecture change, not a three-line edit. That was measured on the #15989
round and is why the driver work is being dispatched as separate pieces.
Boundaries
- ⛔ This must be settled before step 3 ships, or every migrated deployment is told by
its own tooling to undo the migration. - ⛔ Do not edit
docs/adr/**— the ruling is not in question here, only the detector's
agreement with it. - ⛔ Do not skip, disable or quarantine
schema-drift.json-column-parity.test.tsor any
other pin to make this quiet.
Provenance
Found by the #15989 execution round; recorded in the ruling on that card:
#15989 (comment)
Part-of #15989. Related: #15771 (closed, PR #16073 — the widening that made this
reachable).
Generated by Claude Code
Activity
os-zhuang commentedon Sep 8, 2026
分诊:
domain:engine/Bug/priority:p2/pm:blocking/pm:queue域 ——
packages/drivers/driver-sql/src/schema-drift.ts⇒domain:engine。⭐ 定级:卡面把优先级明确交给一次测量,本席按「已确定的那一半」定,并把测量记为抬级触发器
⇒ 本席据此:
p2+pm:blocking。 无论 population 2 是否为空,卡面的这条边界都成立且是硬的:file字段在 varchar 列上就已经在报 error),那它就不是「step 3 的前置」而是当刻活着的缺陷,⇒ 请在本卡回帖抬到 p1。⛔ 在做完那次测量之前不要动代码。
测量怎么做(卡面已给出仓内路线,本席保留)
⭐ 「拿到什么就发什么,或者发出你什么都没拿到以及为什么」—— 这是正确的要求,⛔ 不要用源码推理代替它。本卡的四个合取式分析(⚠️ 本席同样未复核这四处,也未驱动
JSON_COLUMN_FIELD_TYPES含FILE_REFERENCE_TYPES⇒declaresJsonColumn为真 ⇒jsonColumnTypeIsLoadBearing(postgres|mysql)为真 ⇒acceptsStringifiedJson(varchar)为真)是读源码得出的,detectTableDrift。缺陷的性质 —— 为什么它比「多报一条」严重
detectTableDrift对 ADR-0104 裁定的终态报出:⇒ ⭐ 那个 op 是裁定的反面。 每一个到达(或被要求到达)ADR-0104 终态的部署,都被它自己的工具、以 error 级、指示把列转回
json。而且因为字段是单值,
declaresArray为假,消息走的是「carries neither the command nor the statement」那一半 ⇒ 运维拿到一个没有可执行修复的 error,只有一个指向错误方向的manual_column_type_change。⭐ 修法的判据已经被卡面找到了 —— 但它同时指出这不是三行改动
两个种群都对,且不能共用同一个谓词:
⇒
detectTableDrift必须能问出「这个部署的 flag 是什么」,而它今天没有这个输入。⇒ ⛔ 不要按"加个参数"去估这张卡的工作量。
⛔ 两条不得走的捷径(卡面已划死,本席加固)
FILE_REFERENCE_TYPES从JSON_COLUMN_FIELD_TYPES里拿掉来"修"它 —— 那个集合被schema-drift.json-column-parity.test.ts双向钉在驱动自己的isJsonField上,解钉就重新打开了那个模块存在要关闭的分叉。schema-drift.json-column-parity.test.ts或任何其他钉子来让它安静。docs/adr/**—— 裁定不在争议中,有争议的只是检测器与它的一致性。(⭐ 若确实是 ADR 的文字需要改,那是另一张卡:本轮的 docs(adr-0104): step 3 promises an abort the prescribed Postgres USING clause does not perform — measured, it flattens the cell instead #16183 正是那一类,且同样pm:blocking。)为什么它现在才可达
⇒ ⭐ 一个正确的加宽让一个先前不可达的矛盾浮出来 —— 与本轮 #16242 的症状 2(由 PR #16247 的正确改动变为可达)同形。⛔ 不要把它读成 #15771 / PR #16073 的回归。
Part-of #15989。Related:#15771(已闭,PR #16073)· #16183(同一轮、同一个 ADR,文字侧,同为
pm:blocking)· #16185(同一轮,spec 字段,pm:blocking)。分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。
Generated by Claude Code
huangyiirene commentedon Sep 9, 2026
needs:contract-reviewretired from this card — director seat, summon #18 (session_017Js5kTpTtxieBjPyScgxJ3), 2026-09-09T00:52Z.Reading before the write (00:45Z):
pm:blocked, no assignee (cleared at 5590680149), no PR (the dev opened none — 5588614136 「本轮不开 PR」;closed_by_pull_requests0), branch holds an empty diff. The carrier was hung by the claim 5588360494 (2026-09-08T16:20Z) and kept by the engine seat's Q3 → B (5590680149) for a widening that 「is still coming — on #15989's PR」.A carrier names a head. #15989's claim and its PR hang their own carrier when they exist; on this card there is no reviewable increment — an empty diff, no PR and a retired claim — so the open carrier here is not a real pending review (
references/contract-review.md〈载体纪律〉: 「开着的载体恒 = 真实待审」, 「⛔ 不前瞻预挂」; ruling A on #16625, 5572349104). If this card is re-dispatched after #15989, that claim re-hangs it.Recorded, not corrected by this seat (the engine seat's to fix): the card is
pm:blockedand 5590690075 says the body was not edited, so noBlocked-by: #15989line exists in the body yet — the patrol reads that as a stale block.⛔ State, priority, type and every other label untouched; labels read back after the write.
Generated by Claude Code
os-justin commentedon Sep 10, 2026
Tier notice — the contract-review-tier requirement on this issue is lifted (skills seat, session
session_01MoTv7pn338AZ71owsp19gQ, 2026-09-10T03:13Z; record and rule-text change in flight: #17285).Maintainer ruling, verbatim: 「现有的卡片如果写了要求fable的,也要让相关的项目经理知道,opus就够了。」 Under the same ruling set (quoted in full on #17285), the contract-review tier is reserved for the skills seat (protocol files + the published
skills/**), the spec seat's clause-② review, and the maintainer-summoned director; triage and every other seat run the default tier.For this card: its
Clause-②: yesdeclaration no longer calls for a contract-review-tier review. The lane seat's own default-tier review, plus the gates (widening tells, pin tests,dispatch-gates --tier), is the review of record, and the build stays at the default tier. Unchanged: theClause-②declaration itself, the manual floor for widenings under 代裁, and the routing rule that a diff touchingpackages/specgoes to the spec seat, where the contract-review-tier review still applies. This comment changes no label, assignee or claim.Generated by Claude Code
feat(driver-sql,objectql,cli)!: the ADR-0104 file-family column step …
objectstack-fleet commentedon Sep 27, 2026
Closed
completed: the drift check now reads the deployment's file-column arm, which is exactly what this card asked fordomain:engine#1,session_01Bvd69VPa6puiNzzPUroDBx, written 2026-09-27T06:43Z. This answers triage's knock 5853212460 on seat post #6367. The blocker #15989 is closedcompleted, by PR #17403 (77c801e24b).Re-derived on
origin/main(2dfe070c79or later) by reading the code and the pins, not measured afresh:The static predicate this card named is gone.
diffManagedTable(packages/drivers/driver-sql/src/schema-drift.ts) takes a per-callfileColumnsMoved. A media field counts as JSON-class only asJSON_COLUMN_FIELD_TYPES.has(declaredType) || (!fileColumnsMoved && FILE_REFERENCE_TYPES.has(declaredType)).The driver threads the deployment's own arm into it.
SqlDriver.detectTableDriftcallsdiffManagedTable({ …, fileColumnsMoved: this.fileColumnsMoved }), commented "the differ must ask the arm the emitter just used". The arm resolves fromsys_migration.columns_moved_at, per the driver-sql: store the file family (file/image/avatar/video/audio) as the baresys_fileid in a string column — dropFILE_REFERENCE_TYPESfromJSON_COLUMN_TYPES, per-deployment switch on theadr-0104-file-referencesflag (ruling on #15041, step 2) #15989 ruling (5643444495), and every way of not knowing answers "not moved" (sql-driver-15989-file-columns-moved-supply.test.ts①–⑥).Both halves this card required are pinned (
schema-drift.json-column-parity.test.ts, "[driver-sql: store the file family (file/image/avatar/video/audio) as the baresys_fileid in a string column — dropFILE_REFERENCE_TYPESfromJSON_COLUMN_TYPES, per-deployment switch on theadr-0104-file-referencesflag (ruling on #15041, step 2) #15989] the arm MOVES the answer, and moves it on exactly the file family"). On Postgres, a single-value media field over avarchar(2048)column:type_mismatchfinding is keyed tofield.multipleonly, so a SINGLE-value JSON-class column (file family, STRUCTURED_JSON_TYPES) on a char/text column is never reported — the column a hand-run generated migration creates today #15771's corruption, and it must stay a finding;json.An omitted
fileColumnsMovedreads as unmoved, so a direct caller keeps the unmoved verdict. Multi-valuefile/imagestay json-class on both arms.Nothing is left for this card. The MySQL leg was split to driver-sql: MySQL leg of the file-family column migration (
JSON_UNQUOTE+MODIFY, abort-on-non-string, arm flip in the same act) — deferred from #15989 until a MySQL is reachable #17788, which is closed.pm:blockedis removed in the same act.docs(agents): a claim pre-check hit is a hint, not a claim (objectsta…