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
Activity
- addedpriority:p2Medium: important, M3Medium: important, M3
on Sep 8, 2026 分诊:
domain:engine/Bug/priority:p2/pm:blocking/pm:queue域 ——
packages/drivers/driver-sql/src/schema-drift.ts⇒domain:engine。⭐ 定级:卡面把优先级明确交给一次测量,本席按「已确定的那一半」定,并把测量记为抬级触发器
⛔ Population 2 is REASONED, NOT MEASURED, and it is the first thing this card must settle. … 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.
⇒ 本席据此:
- 已确定的部分 ⇒
p2+pm:blocking。 无论 population 2 是否为空,卡面的这条边界都成立且是硬的:⛔ This must be settled before step 3 ships, or every migrated deployment is told by its own tooling to undo the migration.
⇒ 它挡着 ADR-0104 的 step 3。pm:blocking按派工排序把它提到 p1 之前 —— 这正是「本身不大、但别人在等」该有的位置。 ⚠️ 抬级触发器:若 population 2 非空(即今天每一个 Postgres/MySQL 部署上,一个file字段在 varchar 列上就已经在报 error),那它就不是「step 3 的前置」而是当刻活着的缺陷,⇒ 请在本卡回帖抬到 p1。
⛔ 在做完那次测量之前不要动代码。
测量怎么做(卡面已给出仓内路线,本席保留)
stand up a generator-created datastore on live Postgres (the
Temporal Conformance (live PG + MySQL)job'spostgres:16is the in-repo route) and read whatdetectTableDriftactually returns for afilefield. Publish the finding object you get, or publish that you got none and why.⭐ 「拿到什么就发什么,或者发出你什么都没拿到以及为什么」—— 这是正确的要求,⛔ 不要用源码推理代替它。本卡的四个合取式分析(
JSON_COLUMN_FIELD_TYPES含FILE_REFERENCE_TYPES⇒declaresJsonColumn为真 ⇒jsonColumnTypeIsLoadBearing(postgres|mysql)为真 ⇒acceptsStringifiedJson(varchar)为真)是读源码得出的,⚠️ 本席同样未复核这四处,也未驱动detectTableDrift。缺陷的性质 —— 为什么它比「多报一条」严重
detectTableDrift对 ADR-0104 裁定的终态报出:kind: 'type_mismatch', severity: 'error', category: 'needs_confirm', op: { type: 'manual_column_type_change', to: 'json', from: '<the varchar>' }⇒ ⭐ 那个 op 是裁定的反面。 每一个到达(或被要求到达)ADR-0104 终态的部署,都被它自己的工具、以 error 级、指示把列转回
json。而且因为字段是单值,
declaresArray为假,消息走的是「carries neither the command nor the statement」那一半 ⇒ 运维拿到一个没有可执行修复的 error,只有一个指向错误方向的manual_column_type_change。⭐ 修法的判据已经被卡面找到了 —— 但它同时指出这不是三行改动
The discriminator is the deployment's
adr-0104-file-referencesflag, not the field type — the same key the driver's own encoding arm is ruled to use.两个种群都对,且不能共用同一个谓词:
部署状态 varchar 列上的 media 字段 应当 仍是 JSON 编码(step 3 之前,或没有 flag) #15771 的静默损坏 保持为 finding 裸 id 编码(step 3 之后,带 flag) ADR-0104 的裁定终态 保持沉默 ⇒
detectTableDrift必须能问出「这个部署的 flag 是什么」,而它今天没有这个输入。⚠️ ⚠️ 卡面点出的排序陷阱,本席提为最重要的一条移交:isJsonField's answer is frozen intojsonFields[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.⇒ ⛔ 不要按"加个参数"去估这张卡的工作量。
⛔ 两条不得走的捷径(卡面已划死,本席加固)
- ⛔ 不要靠把
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。)
为什么它现在才可达
This became reachable for single-value media tonight, when #15771's widening landed as PR #16073: before it,
declaresJsonColumnwasfield.multiple === truealone and a single-valuefilewas out of scope.⇒ ⭐ 一个正确的加宽让一个先前不可达的矛盾浮出来 —— 与本轮 #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
- 已确定的部分 ⇒
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
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
- added a commit that references this issue
on Sep 17, 2026 objectstack-fleet commented
on Sep 27, 2026 ContributorMore actionsClosed
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:- is still reported on an UNMOVED deployment. That is driver-sql schema-drift: the json-vs-text
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; - is NOT reported on a MOVED deployment. ADR-0104's ruled end-state is no longer told to convert back to
json.
An omitted
fileColumnsMovedreads as unmoved, so a direct caller keeps the unmoved verdict. Multi-valuefile/imagestay json-class on both arms. - is still reported on an UNMOVED deployment. That is driver-sql schema-drift: the json-vs-text
-
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.-
- added a commit that references this issue
on Sep 28, 2026
What is wrong
ADR-0104's addendum rules that a media field (
FILE_REFERENCE_TYPES) ends up in astring column —
VARCHAR(2048)on Postgres and MySQL — holding the baresys_fileid.detectTableDriftnow 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-value
file.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', andop: { 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,
declaresArrayis false, so the message takes thehalf that "carries neither the command nor the statement" — the operator gets an error
with no runnable repair, only the
manual_column_type_changeop 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,
declaresJsonColumnwasfield.multiple === truealone and asingle-value
filewas out of scope. It is now in scope, and there are two populationsthat can present a varchar media column on Postgres/MySQL:
os migrate files-to-references --apply— the ruled end-state,by construction.
FIELD_TYPE_SQL_MAPalready emitsVARCHAR(2048)for all five media members, andits 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'spostgres:16is the in-repo route) and read whatdetectTableDriftactually returns fora
filefield. 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:
the flag) over a varchar column is driver-sql schema-drift: the json-vs-text
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 silent corruption and must stay a finding.column is the ruled end-state and must be silent.
⇒ The discriminator is the deployment's
adr-0104-file-referencesflag, not thefield type — the same key the driver's own encoding arm is ruled to use. Whatever shape
the fix takes,
detectTableDrifthas to be able to ask that question, and today it hasno such input. ⛔ Do not "fix" this by dropping
FILE_REFERENCE_TYPESfromJSON_COLUMN_FIELD_TYPES: that set is pinned in both directions against the driver's ownisJsonFieldbyschema-drift.json-column-parity.test.ts, and unpinning it re-opens thefork that module exists to close.
isJsonField's answer is frozen intojsonFields[object]at registration time, so keying the readers on a per-deploymentflag 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
its own tooling to undo the migration.
docs/adr/**— the ruling is not in question here, only the detector'sagreement with it.
schema-drift.json-column-parity.test.tsor anyother pin to make this quiet.
Provenance
Found by the
#15989execution 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