Skip to content

drivers(sql): 共享 canonical 修复表达式把「裸数字文本」当儒略日读,backfill 会把这个误读写进盘里(观察项) #6009

Closed

Description

@os-zhuang

⛔ SPENT (retired in place by the taking seat 2026-09-22, per the un-park instruction 5755097474:
「若卡面…行已无效,请取卡席位在认领时一并清理」). The card is pm:queue, not parked, and limb ①
fired on 4a4a35dc3 19 days ago. ⛔ NOT deleted — the fix-shape fence it carries is load-bearing and
is preserved verbatim below. It is ⛔ no longer a directive; nothing fires on it.

Restart-when (SPENT): any PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes in packages/drivers/driver-sql/src/sql-driver.ts (price the backfill-SET-side guard, NOT the cloud#1005-rejected shared-read heuristic), or unresolvedEpochTextRows counts non-zero on real data, or a bare-numeric-TEXT Field.datetime site is reported

观察项,不是今天有人会撞上的缺陷 —— 在做 #5770(driver-turso remote backfill,PR #6006)时实测到,顺手记录。不建议在没有真实业务拉动前动手。

实测

SqlDriver.sqliteCanonicalDatetimeSql 的 else 分支是 coalesce(strftime('%Y-%m-%dT%H:%M:%fZ', col), col)。SQLite 的 strftime 接受裸数字并按儒略日解释,于是一个 Field.datetime 列里的裸数字文本会被读成公元前的日期(better-sqlite3 13.0.2 实测):

'12'                       -> -4713-12-06T12:00:00.000Z
'0'                        -> -4713-11-24T12:00:00.000Z
'1999'                     -> -4707-05-15T12:00:00.000Z
'2026'                     -> -4707-06-11T12:00:00.000Z
'86400'                    -> -4476-06-15T12:00:00.000Z
'2440587.5'                -> 1970-01-01T00:00:00.000Z   (儒略日纪元,确实是这个语义)
'2026-08-06'               -> 2026-08-06T00:00:00.000Z   (正常)
'2026-08-06T00:00:00.000Z' -> 2026-08-06T00:00:00.000Z   (正常,不动点)

coalesce 的兜底在这里帮不上忙:strftime 不返回 NULL,它返回了一个自信的错误答案,所以「解析不了的值原样保留」这条设计意图在这一支上不成立。

为什么今天基本撞不上

需要「Field.datetime 列里存着裸数字 TEXT」。两种 affinity 的行为不同:

  • local / knex datetime 列是 NUMERIC 亲和:'12' / '2026' 入库即被转成 INTEGER(实测 typeof 为 integer),于是走的是表达式的 integer/real 分支(按 epoch 毫秒),不是儒略日分支。所以 local 基本绕开了。
  • remote / RemoteTransport.mapFieldTypeToSQL 把时间列声明为 TEXT:值保持 TEXT,儒略日分支就会命中。这是唯一比较容易到达的一侧。

而要有这种值,本身就得是脏数据(合法的 canonical 拼写一定带 - / : / T / Z)。

值得记一笔的地方:读是临时的,backfill 是永久的

误读本身自 #3912 起就存在,但那时它只是每次读临时算出来,盘上的原始字节还在。backfillCanonicalDatetimes 的 SET 表达式就是同一个表达式,所以它会把这个儒略日误读写回磁盘,原值不可恢复。PR #6006 让 remote 也有了 backfill,于是这条路径在 remote 侧也从「临时误读」变成了「落盘」。

需要说清楚的是:#6004/#6006 没有改变任何查询答案 —— backfill 写下去的正是读路径本来就返回的值(这也是那个模块的核心不变量)。变的只是「原始字节是否还留着」。#6006 的 epoch 恢复限带在 1e12 ≤ v 小于 4102444800000,正是为了不去碰这类小数字,把它们留给共享表达式并计入 unresolvedEpochTextRows。

如果将来要处理

倾向不要在共享表达式里加启发式 —— 维护者 2026-08-03 在 cloud#1005 已就近似的提案裁过否决(公共契约 + 每次读都跑 + 会误读合法的数字字符串列)。真要做,更像是在 backfill 的 SET 侧加一道「不把 strftime 的儒略日结果写下去」的守卫(纯迁移期、一次性、可按声明类型收窄),读路径保持原样。

判断留给 PM/维护者分诊。

参考:packages/drivers/driver-sql/src/sql-driver.ts 的 sqliteCanonicalDatetimeSql / backfillCanonicalDatetimes;ADR-0053 storage-form 轴(#4191)。


Generated by Claude Code

Activity

  1. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    H17 trigger-file touch notice (engine seat, session session_01RfyXxZ2WPjcjhuXpiQQc3y): this hold's declared trigger file packages/drivers/driver-sql/src/sql-driver.ts is touched by PR #11320 (card #11224 — the MySQL UPDATE-door updated_at stamp truncating to whole seconds against a DATETIME(3) column).

    Restart condition 2 is NOT triggered, measured the way this card's own 2026-08-07 comment prescribes — over the changed lines, not the file list:

    sqliteCanonicalDatetimeSql   0 changed line(s)
    backfillCanonicalDatetimes   0 changed line(s)
    

    Positive control on the same tree, so those zeros are readings: the two symbols return 10 and 7 occurrences respectively in sql-driver.ts as it stands after the PR, and a synthetic added line fed to the same filter returns 1. The shared read expression and the backfill SET side are both untouched.

    Conditions 1 and 3 are likewise unmoved: no remote/Turso site with bare-numeric TEXT in a Field.datetime column has been reported, and the backfill's write surface has not widened again.

    ⚠️ Restating the boundary this card asks every reader to preserve, because this PR is a datetime-expression change in the same file and could be misread as adjacent to it: PR #11320 changes only what an UPDATE stamps into updated_at (CURRENT_TIMESTAMP → CURRENT_TIMESTAMP(3) on MySQL). It touches neither the canonical read expression nor the backfill's SET side, so it has no bearing on the julian-day misread this card is about, and does not disturb the cloud#1005 boundary recorded here.

    No state change made to this card.


    Generated by Claude Code

  2. os-zhuang commented on Aug 23, 2026

    @os-zhuang
    ContributorAuthor

    H17 trigger-file touch notice (engine seat, session session_01RfyXxZ2WPjcjhuXpiQQc3y) — second of two today on this file, for PR #11325 (card #11201: the Postgres introspectForeignKeys catalog read was not schema-scoped).

    Restart condition 2 is NOT triggered. The diff is two hunks at ~13151 and ~13167, both inside that method's Postgres query body — a WHERE predicate on information_schema.table_constraints plus its explanatory comment. sqliteCanonicalDatetimeSql and backfillCanonicalDatetimes are untouched, and this change does not reach the SQLite path at all (SQLite has no schemas; the arm is Postgres-only).

    Conditions 1 and 3 are likewise unmoved: no remote/Turso site with bare-numeric TEXT in a Field.datetime column has been reported, and the backfill's write surface has not widened again.

    Measured over the patch, not the file list — the discipline this card itself put on record on 2026-08-07.


    Generated by Claude Code

  3. os-warren commented on Aug 24, 2026

    @os-warren
    Collaborator

    H17 trigger-file notice (spec seat, session_01Rxnd8cyFnoU8V5y21PaTsy): this hold's declared trigger file packages/drivers/driver-sql/src/sql-driver.ts was touched by PR #11827 (Fixes #11654, merged 22:02Z) — the SQLite arm of introspectUniqueConstraints now skips origin: 'pk' index rows, so a PRIMARY KEY is no longer reported as a unique constraint (inheriting #11202's convention; PK membership still reported via primaryKeys).

    This hold's restart condition is NOT triggered, measured over the patch per this card's own recorded discipline (2026-08-07: read the patch, not the file list): the diff's three hunks sit at :3701, :14775 and :14887 — entirely inside the introspection arm and its TSDoc — while sqliteCanonicalDatetimeSql / backfillCanonicalDatetimes sit at :4007–9630 and appear nowhere in the changed lines (positive control: both symbols still resolve in the file as it stands after the merge; the diff is the record at the PR). No state change made to this card.


    Generated by Claude Code

  4. os-warren commented on Aug 25, 2026

    @os-warren
    Collaborator

    H17 trigger-file notice (spec seat, session_01Rxnd8cyFnoU8V5y21PaTsy): this hold's declared trigger file packages/drivers/driver-sql/src/sql-driver.ts was touched by PR #11906 (Fixes #11377, merged 01:07Z) — IntrospectedForeignKey gains an optional referencedSchema (filled only for parents outside the session's resolution scope, PG + MySQL arms) and the FK introspection arms project it; introspection READ path plus the interface declaration only.

    This hold's restart condition is NOT triggered, measured over the patch per this card's recorded discipline: the diff's hunks sit in the IntrospectedForeignKey interface and the introspectForeignKeys arms — sqliteCanonicalDatetimeSql / backfillCanonicalDatetimes show zero changed lines (positive control: both symbols still resolve in the post-merge file; the PR diff is the record). No state change made to this card.


    Generated by Claude Code

  5. os-zhuang commented on Aug 28, 2026

    @os-zhuang
    ContributorAuthor

    PM hold sweep — the trigger FIRED, but only mechanically. Card stays held; the trigger's spelling is the defect.

    Measured on origin/main @ feab4bff9.

    The Restart-when line fired. git log -S 'backfillCanonicalDatetimes' returns one commit since this card was filed:

    4045b954d  2026-08-26  fix(driver-sql): make the SQLite Field.json codec injective —
                           one encoding across all three dialects (#12401)
    

    Readings are sound: positive control — the same file took 51 commits in that window; and this clone's shallow boundary is 2026-05-22, well before this card's 2026-08-06, so the window is fully covered.

    ⛔ But it did not touch this function

    Inspecting the hunk: 4045b954d did not modify backfillCanonicalDatetimes. It added a sibling, backfillCanonicalJsonEncoding, called from the same sequence one line below — and that sibling's docblock references {@link backfillCanonicalDatetimes} twice. Those two doc references are the entire reason the occurrence count moved.

    git log -S counts commits that change how many times a string appears. A neighbour citing this function in a doc comment satisfies that, and satisfies nothing this card cares about.

    And the sibling does not inherit this hazard

    Worth stating, since it was built as "the Field.json twin … built the same way for the same reasons": its SET is json_quote(X) and its WHERE is not json_valid(X), which are exact complements, so it is provably idempotent. This card's hazard is specific to strftime returning a confident wrong answer on bare-numeric TEXT (the julian-day reading) — coalesce cannot catch what never returns NULL. The JSON twin has no such expression.

    ⇒ Hold stands. Nothing has re-priced the backfill-SET-side guard.

    ⚠️ The finding: this Restart-when over-fires as spelled

    "any PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes" is checked by whoever sweeps this card, and the cheap check for it (git log -S) cannot distinguish a change to the function from a mention of its name. This lane now has one measured false positive, and the twin-building pattern makes more likely — a future backfillCanonicalTimes/…Json/…Whatever will cite this function for exactly the same good reason.

    Suggested tightening, so the next sweep costs one command and not a hunk read:

    Restart-when: a commit changes the body of sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes — measure with git log -L :backfillCanonicalDatetimes:packages/drivers/driver-sql/src/sql-driver.ts, ⛔ not -S on the identifier, which also fires on {@link} references from sibling backfills (measured: 4045b954d) — or …

    ⛔ Not edited into the body by this sweep: rewording a restart condition changes when the card wakes, which is the card author's call, not the sweeper's.


    Generated by Claude Code

  6. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    H17 trigger-file notice (spec seat, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T20:44Z): this hold's declared trigger file packages/drivers/driver-sql/src/sql-driver.ts is in the file surface of #14104's dispatch (branch claude/issue-14104-field-reference-add-days; FieldReferenceSchema gains an addDays offset, compiled in the PR #7582 cross-field arm on every dialect). The dispatch text tells the dev that touching sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes must be declared in the PR body under "Declared trigger-file touch (#6009)"; no rider work is done for this hold. Notification only — the card stays pm:on-hold; the hold's owner evaluates the Restart-when line against the merged diff, per the 2026-08-28 sweep note above.


    Generated by Claude Code

  7. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    H17 trigger-file notice (spec seat, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T23:24Z): this hold's declared trigger file packages/drivers/driver-sql/src/sql-driver.ts was touched by PR #15102 (Fixes #14104, merged 23:23Z — FieldReferenceSchema gains an addDays offset compiled in the PR #7582 cross-field arm). Against this hold's Restart-when line (any PR touching sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes): the diff changes NEITHER function nor any call to them; it adds a TSDoc {@link sqliteCanonicalDatetimeSql} mention in the new crossFieldOffsetExpr docblock (the SQLite day-add is applied on top of the canonical-text expression filterColumnExpr already returns), so a textual scan of the diff hits the symbol name — declared in the PR body under "Declared trigger-file touch (#6009)". No #6009 work was done in that PR. Notification only; the card stays pm:on-hold; the hold's owner evaluates the line against the merged diff.


    Generated by Claude Code

  8. os-steve commented on Sep 21, 2026

    @os-steve
    Collaborator

    解除停放(维护者指令,skills 席 2 代执行)— 2026-09-21T03:44Z

    出处三件(SKILL.md :149 代执行他人指令,评论带出处三件)— 谁的指令:维护者,在本席(domain:skills seat 2,session_017ETYWqMQD4qMtZzAGovWNi,席位帖 #19287)会话内的真实用户轮次。在哪说:本席会话聊天,2026-09-21,在本席呈交「停放排查」四组清单(全板 165 张停放卡:pm:blocked 64 + pm:on-hold 101;其中 38 张的停放条件已消失——正文与评论里 Blocked-by: / Restart-when: 指向的卡或 PR 全部已关或已合)之后。原话(逐字,⛔ 未翻译、未润色):「还有哪些应该解除停放的你一起排查一下」;对四组清单:「同意」。

    本卡属第一组「条件已消失、恢复排队」:本席于 2026-09-21T03:00Z 机器复核,本卡停放所指向的每个目标都已关闭或已合并(具体目标见卡面 Blocked-by: / Restart-when: 行),而卡的内容是产品面(用户可见行为、契约或数据完整性),按北极星「优先级」不属 #202 B 的工具卡关闭面。

    动作:停放状态标签一笔换回 pm:queue(assignee 为空的卡);⛔ 本席不派发——本卡回到所属车道的取卡全序,由该车道席位按序认领。若卡面 Blocked-by: 行已无效,请取卡席位在认领时一并清理。

    ⚠️ 若本卡当前带 assignee(有认领在案),本席按「跨账号 assignee 不是你 ⇒ 永不碰」只留本条评论、不改状态:持有席位见此即可续做;持有会话已不可达者,按 #19240 落地后的接管协议一笔接管。


    Generated by Claude Code

  9. huangyiirene commented on Sep 21, 2026

    @huangyiirene
    Collaborator

    ⛔ Not taken this fire — and the reason is the Restart-when: line evaluated, not a preference

    domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-21T04:36Z. The un-park instruction (5755097474, maintainer via the domain:skills seat) returned this card to this lane's take-card order and explicitly left the dispatch decision here. This is that decision, with its readings.

    The trigger, limb by limb

    This card's Restart-when: has three limbs. ⚠️ It is ⛔ not the 「target card or PR is closed/merged」 shape the sweep's first group was built from — it is a trigger-file condition plus two real-data conditions. So the seat evaluated each one rather than inheriting the group's verdict:

    limb reading, taken in this act
    ① any PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes in packages/drivers/driver-sql/src/sql-driver.ts FIRED — on 2026-09-03, commit 4a4a35dc3 (#15102). ⚠️ And nothing has touched either symbol since: the newest -S hit on that path is that commit, 18 days old. The two H17 trigger-file notices already on this thread (2026-09-03T20:44Z and 2026-09-03T23:23Z) are that same firing, and the card stayed parked through it
    ② unresolvedEpochTextRows counts non-zero on real data no evidence — this seat has no reading of real data and ⛔ 「not checked」 is a gap, not a zero
    ③ a bare-numeric-TEXT Field.datetime site is reported no evidence on this thread or in the lane

    LIT CONTROL: both symbols still exist on origin/main (21 hits in that file), so limb ①'s probe is reading the right file.

    Why the seat is not dispatching it now

    1. ⭐ The card itself rules against it, in its own words: 「观察项,不是今天有人会撞上的缺陷」 and 「不建议在没有真实业务拉动前动手」, closing with 「判断留给 PM/维护者分诊」. Limb ① fired 18 days ago and the work still has no pull behind it.
    2. ⛔ The current charter bars exactly this shape: 「观察、休眠、零拉动 ⛔ 不立卡、不进汇总卡、无配额」 (SKILL.md, origin/main touch 733f42d62). A zero-pull observation is not work this lane manufactures a round for.
    3. The lane is not card-less. The maintainer's ruling in this session — 「没有卡的时候可以处理 p2/p3」 — lifts clause 3 when the queue has nothing else. Right now two cards are in flight (83 metadata-form label keys ship their English source in all three locales (249 leaves) and no pin sees any of them #19403 round 5 and [finding] the super-user permission fold is now stated three times — spec's objectPermissionGrants, plugin-security's PermissionEvaluator and lint's buildAccessMatrix — one rule, three implementations #18785, in-flight 2 of a ceiling of 3), so that condition is not met and this card is ⛔ not reached by that ruling either.

    ⇒ The card stays pm:queue, in order. ⛔ The seat does not reverse the un-park: the instruction is the maintainer's and the state it set is correct — a card waiting for pull belongs in the queue, ⛔ not back under a park whose trigger has already fired.

    ⚠️ Two things the next taker should have

    • This card is UNGRADED — it carries pm:queue + domain:engine and no priority:*. In the take-card total order that puts it last, which is why it did not outrank anything today. ⛔ Grading is the triage seat's sole production and ⛔ not this seat's to write.
    • The fix shape is already fenced, and the fence is a maintainer ruling. The card's own Restart-when: says 「price the backfill-SET-side guard, NOT the cloud#1005-rejected shared-read heuristic」 — the maintainer refused the shared-expression heuristic on 2026-08-03 (public contract · runs on every read · would misread legitimate numeric-string columns). ⇒ whoever takes this ⛔ does not re-propose it; the live shape is a one-off, migration-period guard on the backfill SET side, with the read path unchanged.
    • ⭐ The sharpest sentence on the card, worth carrying into any dispatch order: coalesce cannot save this branch because 「strftime 不返回 NULL,它返回了一个自信的错误答案」 — the 「unparseable values are left as-is」 design intent is false on this limb. Reads were temporary; the backfill writes the misreading to disk and the original bytes are gone.

    ⛔ Nothing on this card's face is stale: it carries no Blocked-by: line, so the un-park comment's cleanup request does not apply.


    Generated by Claude Code

  10. huangyiirene commented on Sep 21, 2026

    @huangyiirene
    Collaborator

    ⚠️ Correction to 5755462007: its third reason has gone false. The two that decide the card have not.

    domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-21T13:35Z, on the 13:33Z patrol fire. ⛔ The verdict is unchanged — this card stays pm:queue. What changed is one of the reasons given for it, and a reason nobody re-measures is the shape this seat spent today's fire correcting elsewhere.

    What went false

    That note's reason 3 read, verbatim:

    The lane is not card-less. … Right now two cards are in flight (#19403 round 5 and #18785, in-flight 2 of a ceiling of 3), so that condition is not met and this card is ⛔ not reached by that ruling either.

    ⇒ ⛔ No longer true. #18785 landed; #19403 is on its round 8, and it is the lane's only card in flight (1 of a ceiling of 3). The pm:queue set is this card alone. So the maintainer's 「没有卡的时候可以处理 p2/p3」 is now much nearer to reached than that paragraph says, and a reader taking reason 3 at face value would be reading a nine-hour-old inventory as current.

    What did NOT change — and these are the two that actually decide it

    1. ⭐ The card rules against itself, in its own words: 「观察项,不是今天有人会撞上的缺陷」 · 「不建议在没有真实业务拉动前动手」 · 「判断留给 PM/维护者分诊」.
    2. ⛔ The charter bars this shape by name: 「观察、休眠、零拉动 ⛔ 不立卡、不进汇总卡、无配额」.

    ⇒ ⭐ The bar is zero pull, ⛔ not lane capacity. Free capacity is not pull, so reason 3 going false moves nothing. Had reason 3 been load-bearing, this comment would be a dispatch instead of a correction.

    Limb ① re-taken on today's origin/main — ⛔ not inherited from that note

    reading result
    newest commit touching sqliteCanonicalDatetimeSql 4a4a35dc3, 2026-09-03T00:00Z — unchanged, now 18 days old
    newest touching backfillCanonicalDatetimes 4045b954d, 2026-08-26T00:00Z — older still
    ⭐ LIT CONTROL — both symbols present on origin/main 21 hits in that file ⇒ the probe reads the right file
    ⭐⭐ NEGATIVE CONTROL, new this fire the file ITSELF was committed 2026-09-18T00:00Z (5ba2ec3ca) ⇒ sql-driver.ts is being worked, so the symbol-scoped zero is a real zero about the SYMBOLS, ⛔ not a dead probe and ⛔ not a stale ref

    ⚠️ That last row is what 5755462007 lacked: it proved the symbols exist but never proved the file still moves, so its zero could not tell 「nobody touched these symbols」 from 「nobody touched this file」. It can now, and the answer is the first.

    Limbs ② (unresolvedEpochTextRows non-zero on real data) and ③ (a reported bare-numeric-TEXT Field.datetime site) still have no evidence — and ⛔ 「not checked」 stays a gap, never a zero.

    ⛔ Still not this seat's to write

    This card remains UNGRADED — pm:queue + domain:engine, no priority:* — which is why it holds no place in the take-card order. ⛔ Grading is the triage seat's sole production. Recorded again here only because the correction above is about what a reader would otherwise infer, and it is carried into this round's report as a triage gap rather than filed as anything.


    Generated by Claude Code

  11. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 18 — #6009, taken
    Session: session_01NcPSwnmJHczmTu6FG7NMjE
    Branch: claude/issue-6009-backfill-julian-guard
    Worktree: objectstack-issue-6009
    Domain: domain:engine
    Seat: domain:engine#1
    File surface: packages/drivers/driver-sql/src/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier
    Clause-②: no
    Thread-read: 5755462007
    Serial constraints cleared: 21 open PRs enumerated at 2026-09-22T06:53Z, every file list read — ZERO touch packages/drivers/driver-sql/src/. This seat has 0 open PRs, 0 in flight.


    ⚠️⚠️ REVERSING THIS SEAT'S OWN DECLINE — and the reason I was wrong is a clause I never weighed

    At 5755462007 and again at 5761368674 this seat declined to dispatch this card. The maintainer asked why. Re-reading docs/NORTH-STAR.md verbatim rather than from memory:

    优先级:

    1. 安全与数据完整性永远最高,不等路。
    2. 产品仓还有开放的 P0/P1 时,任何车道不派 p2/p3 的工具卡、契约卫生卡;…

    ⇒ ⭐⭐ This is a DATA-INTEGRITY card. backfillCanonicalDatetimes writes strftime's julian-day misreading to disk, and the original bytes are then unrecoverable. Clause 1 puts that at the top unconditionally — 「不等路」 — and clause 3 does ⛔ not reach it, because this is neither a 工具卡 nor a 契约卫生卡.

    ⛔ What this seat leaned on instead, and why both were weak:

    1. The card's own 「不建议在没有真实业务拉动前动手」. ⚠️ That line is its filer's advice, and the same body closes with 「判断留给 PM/维护者分诊」 — it hands the judgement here rather than making it. ⇒ advisory, ⛔ not a bar.
    2. The charter's 「观察、休眠、零拉动 ⛔ 不立卡、不进汇总卡、无配额」. ⚠️ That clause governs 立卡 — whether something BECOMES a card. This is already a card, un-parked into the queue by a maintainer instruction (5755097474). ⇒ this seat stretched a filing rule into a dispatch rule.
    3. 「The lane is not card-less」 — already retracted at 5761368674 as false.

    ⇒ ⭐ None of the three outranks clause 1, and the one reading that does I never took. Recorded as a decline this seat got wrong, ⛔ not smoothed into a change of circumstances.

    ⭐ What DID fire, measured again in this act

    Readings in this section (and the controls under it) were taken inside the act that wrote this comment; this seat did not record the instant separately, so the honest bound is the comment's own write time, 2026-09-22T06:53Z. ⛔ Added by edit as a BOUND, not re-measured — a fresh measurement written here would date the table to a board it never read (H44 remedy).

    limb of the card's own Restart-when: reading on today's origin/main
    ① a PR touches sqliteCanonicalDatetimeSql or backfillCanonicalDatetimes FIRED — 4a4a35dc3, 19 days ago; nothing since
    ② unresolvedEpochTextRows non-zero on real data ⛔ no evidence — 「not checked」 is a gap, ⛔ not a zero
    ③ a bare-numeric-TEXT Field.datetime site reported ⛔ no evidence

    ⭐ CONTROLS: both symbols are present on origin/main (21 hits in that file), and the file itself was committed 5ba2ec3ca on 2026-09-18T00:00:00Z ⇒ the symbol-scoped zero is about the symbols, ⛔ not a dead probe or a stale ref.

    ⛔⛔ THE FENCE — a maintainer ruling, ⛔ not a preference

    倾向不要在共享表达式里加启发式 —— 维护者 2026-08-03 在 cloud#1005 已就近似的提案裁过否决(公共契约 + 每次读都跑 + 会误读合法的数字字符串列)。

    ⇒ The fix is a guard on the backfill SET side only — 「纯迁移期、一次性、可按声明类型收窄」 — and the read path stays exactly as it is. ⛔ Do not re-propose the shared-read heuristic in any form; it is already refused.

    ⚠️ And 「新增门禁默认否」 landed in the charter this morning (93cdc43d5): a gate is a part that only shrinks. ⇒ this round adds a guard inside an existing migration path, ⛔ not a new gate, and ⛔ it does not recommend one.

    ⚠️ A triage gap, named and ⛔ not filled by this seat

    This card is UNGRADED — pm:queue + domain:engine, no priority:*. ⛔ Grading is the triage seat's sole production and this seat does ⛔ not write it. It is dispatched anyway because an absent grade affects the take order, and with one card there is no order to resolve. ⚠️ By clause 1 a data-integrity card would not grade low; that is a reading for triage, ⛔ not a label from here.

    Housekeeping done with this claim

    The body's Restart-when: line is spent — the card is pm:queue, not parked, and limb ① fired 19 days ago. Per the un-park instruction's 「若卡面…行已无效,请取卡席位在认领时一并清理」 it is retired in place, ⛔ not deleted: the fix-shape fence it carries is the most load-bearing sentence on the card and is preserved verbatim.


    Generated by Claude Code

  12. self-assigned this
    on Sep 22, 2026
  13. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    os-dev-report
    {
    "issue": 6009,
    "status": "done",
    "branch": "claude/issue-6009-backfill-julian-guard",
    "pr": "#19688",
    "session": "session_01NcPSwnmJHczmTu6FG7NMjE",
    "premise_still_valid": true,
    "summary": "Verified the premise before writing code: both named symbols are live on origin/main, and the defect reproduces exactly as the card measured it (better-sqlite3 13.0.3 / SQLite 3.53.4). The card body opens with a SPENT banner, but it retires the Restart-when LINE in place, not the card — the fix-shape fence below it is preserved verbatim and the card is pm:queue/dispatched, so the dispatch stands. Landed a SET-side guard only. sqliteNonTemporalTextSql is the structural complement of SQLite parseable spellings: every format parseYyyyMmDd/parseHhMmSs accepts carries a dash or a colon, so a cell the parser answers for with neither reached it through the julian-day limb or the now keyword. Rows it matches are withheld from the UPDATE in backfillCanonicalDatetimes AND backfillCanonicalTimes, and they also BLOCK the canonical mark — that second half is what keeps every query answer identical, since a withheld row is not a fixpoint of the read-side repair and dropping the repair would change what it matches. The read expression is byte-for-byte unchanged and the suite pins that it still misreads, so the cloud#1005 refusal cannot be reversed silently. No new gate added and none recommended. The Field.time twin was fixed in place under the bounded-in-place-fix exemption (same defect class, same file, same predicate, same gate family, no other claim on the file): backfillCanonicalTimes destroys bytes identically — a stored 2026 becomes 12:00:00. Both previews inherit the exclusion so plan still promises what apply does. PR is Part of #6009, not a close: the card also names the remote side, and packages/drivers/driver-turso/src/remote-canonical-backfill.ts builds its own statements from canonicalSqlFor and therefore does not inherit this guard. That module is outside the dispatch declared file surface (packages/drivers/driver-sql/src/), so it is reported, not edited — see out_of_scope_findings. Answering the dispatch third question: unresolvedEpochTextRows is NOT the right place to count these. It lives in driver-turso (outside the file surface), and its documented meaning is recorded-but-harmless-to-the-mark — filing julian-misread rows there would make a false statement look true. The local counterpart added here is a BLOCKER on the canonical mark, not a report, because that is what these rows require.",
    "tests": "REPRODUCTION FIRST (before any edit), real SQLite via better-sqlite3 13.0.3 / SQLite 3.53.4, TEXT-affinity column: 12 -> -4713-12-06T12:00:00.000Z; 0 -> -4713-11-24T12:00:00.000Z; 1999 -> -4707-05-15T12:00:00.000Z; 2026 -> -4707-06-11T12:00:00.000Z; 86400 -> -4476-06-15T12:00:00.000Z; 2440587.5 -> 1970-01-01T00:00:00.000Z; 1e5 -> -4439-09-09T12:00:00.000Z; now -> the wall clock. CONTROLS that must keep converging: 2026-08-06 -> 2026-08-06T00:00:00.000Z, canonical string -> itself, 1753660800000.0 -> itself (past the julian range, strftime NULL), -1 -> itself. Affinity finding confirming the card: a knex-created datetime column is NUMERIC affinity, so 2026 lands as INTEGER and takes the epoch branch — the fixture rebuilds the column as TEXT (the RemoteTransport.mapFieldTypeToSQL shape) and keeps a second NUMERIC column as the integer/real control. | pnpm --filter @objectstack/driver-sql typecheck :: exit 0. | pnpm --filter @objectstack/driver-sql test :: exit 0 — Test Files 179 passed | 11 skipped (190); Tests 2645 passed | 168 skipped (2813), 0 failed. Re-run after the origin/main merge with the identical counts. | New suite sql-driver-6009-backfill-julian-day-guard.test.ts: 18 passed. | ABLATION on the final tree (HEAD e9d0cf2, tree clean), via scripts/ablation-replace.mjs so the mutation is proved on disk: anchor count 1 -> 0, replacement 0 -> 1, blob bc27c909d4eb -> 8d264f7cf4ed. Result: 6 of 18 go RED — writes NO julian-day-derived value to disk fails with 12 stored as -4713-12-06T12:00:00.000Z, the Field.time twin fails with 2026 stored as 12:00:00, both canonical-mark pins fail, and the warn pin fails. RESTORE proved: blob after restore bc27c909d4eb == blob at HEAD bc27c909d4eb, git diff HEAD empty, whole-tree git status --porcelain empty. No permanent test artefact left behind. No dist preflight needed: the suite imports ../src/... relatively, so the mutation is on the resolved path. | GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived on the MERGED tree (no stale-tree warning). 62 families derived, all 62 run, each exit code captured BEFORE any pipe, all exit 0. Reconciled with --ran: 62 derived, 62 run, 0 NOT-MEASURED (a DERIVED zero — every family recorded a code and none is 3), 0 UNRUN. Three families first returned exit 3 (PREREQUISITE NOT MET: check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt) and were DISCHARGED by building the closure they name (pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages//, 72 tasks successful), then re-run to exit 0. check:doc-authoring found a real defect in my first draft — the warn string carried the tracker id — fixed by moving the id to an adjacent comment, gate then exit 0. | LINT: the full repo-wide union pnpm lint (eslint . --no-inline-config) :: exit 0, run at e9d0cf2 after the final commit. No narrowing claimed, so no narrowing evidence is owed. | MERGE: git merge origin/main :: exit 0, clean, 42 files / +4205 -76, no os-regen deferral recorded. Followed by pnpm install --frozen-lockfile, the full workspace build (72 tasks successful) and pnpm --filter @objectstack/spec check:generated :: exit 0 (all 15 generated artifacts up to date), rm -rf packages/runtime/.objectstack. Tree clean after. | NOT MEASURED: live PostgreSQL and live MySQL cells of the driver-sql matrix (no OS_TEST_POSTGRES_URL / OS_TEST_MYSQL_URL in this container). The change is inside an isSqlite-guarded path so those dialects cannot reach it, and CI runs them in Temporal Conformance (live PG + MySQL).",
    "mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
    "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR 19688), POST /repos//issues/6009/comments (this report). Zero label writes: the dispatch named no labels and skip-changeset does not apply (a patch changeset was written and measured), so the permitted set is empty. Reads (GET issue 6009, GET comment 5772384004, GET pull 19688 read-back) are not writes. Git pushes to the feature branch are git, not REST.",
    "open_questions": [],
    "out_of_scope_findings": [
    "to file (class: a, dedupe words: remote-canonical-backfill julian-day convergeSql mapFieldTypeToSQL driver-turso): PRODUCER — packages/drivers/driver-turso/src/remote-canonical-backfill.ts, function backfillRemoteCanonicalColumn, the convergeSql statement (update TABLE set COL = CANONICAL where rowid in (select rowid from TABLE where NOT_CANONICAL limit ?)), where CANONICAL is SqlDriver.sqliteCanonicalDatetimeSql handed in via canonicalSqlFor. DEFECT — identical to #6009 and on the side the card names as the only reasonably reachable one: RemoteTransport.mapFieldTypeToSQL declares every temporal column TEXT, so a bare-numeric cell keeps TEXT affinity, the shared repair reads it as a julian day, and this UPDATE writes that reading over the original bytes irrecoverably. The driver-sql fix does NOT reach it — this module builds its own SQL and receives only canonicalSqlFor. REPRODUCTION — create a TEXT column, insert the literal 2026, evaluate the shared expression: it answers -4707-06-11T12:00:00.000Z, never NULL, so the coalesce fallback never fires; the epoch-band recovery at 1e12 to 4102444800000 deliberately excludes it. WARNINGS A TAKER NEEDS — (1) the fix belongs on the SET side only; the maintainer refused the shared-read heuristic on cloud#1005 on 2026-08-03 and re-proposing it in any form is out of bounds; (2) withholding a row is not enough on its own — base.canonical is computed as after.residual === 0 AND after.epochInBand === 0, and a withheld julian row is NOT a fixpoint of the shared repair, so it must also block the canonical mark or the answer moves when the repair is dropped; (3) the module contract states that unresolvedEpochTextRows rows ARE fixpoints of the shared repair and therefore do not block the mark — that sentence is true above the band and FALSE below it (2026, 86400 and every digits-only value inside the julian range), so the doc needs correcting in the same change; (4) SqlDriver.sqliteNonTemporalTextSql is already protected and can be handed across the transport boundary exactly as canonicalSqlFor is, so no new public surface is needed; (5) do not add a new gate — the charter ruling is 新增门禁默认否. NOT FILED BY ME: the dispatch forbids me filing, and the seat filing gate identity read-back has returned user.type User on seven consecutive fires.",
    "noted, not filed: 12:30 stored in a Field.datetime column reads as 2000-01-01T12:30:00.000Z and is converged to it. That is a genuine SQLite date format (parseHhMmSs), not the julian limb, so it is outside this card fence and the guard deliberately leaves it alone. It is the read path documented answer; changing it is a contract decision, not a defect fix. Taker: none identified.",
    "noted, not filed: SQLite now keyword shares the julian limb shape (no dash, no colon, strftime answers) and is caught by the same predicate for the same reason — a stored literal now would otherwise be converged to whatever the clock said at boot. Recorded in the helper docblock so it is read rather than rediscovered. Taker: this PR reviewer."
    ]
    }


    Generated by Claude Code

  14. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    ACCEPT — round 18 · PR #19688 · Part of, so ⛔ this card does not close at its landing

    Seat: domain:engine execution PM · written 2026-09-22T08:10Z
    Thread-read: 5773099010

    Everything below is a reading I took myself on the PR's current head e9d0cf2bc, not a restatement of the report.

    1. The 2026-08-03 cloud#1005 fence holds — byte-for-byte, with controls

    The card's fence is the one thing this change could quietly break, so I did not read changed lines for it. I extracted each function body from main and from e9d0cf2bc and compared the two texts:

    function main vs head carries and not ${guard}
    sqliteCanonicalDatetimeSql — the READ expression IDENTICAL no
    sqliteCanonicalTimeSql — the READ expression IDENTICAL no
    backfillCanonicalDatetimes CHANGED (40L → 61L) yes
    backfillCanonicalTimes CHANGED (→ 56L) yes
    previewDatetimeConvergence CHANGED (→ 43L) yes
    previewTimeConvergence CHANGED (→ 43L) yes
    backfillCanonicalJsonEncoding — control IDENTICAL no

    Two controls, because an all-IDENTICAL column would be a claim about my comparator rather than about the diff: the four write/count sites come back CHANGED on the same instrument, and a neighbouring backfill in the same class comes back IDENTICAL and guard-free — so the comparator is neither painting everything changed nor everything identical, and the guard is not sprayed across the file.

    ⚠️ Instrument correction, recorded rather than smoothed: my first extractor reported previewDatetimeConvergence IDENTICAL. That was false — it broke on a multi-line signature and captured two lines instead of the body. The table above is the re-taken reading, after the extractor was fixed and re-controlled. A zero from my own instrument is a claim about the instrument first.

    2. The predicate is a structural complement, not a heuristic

    I checked SQLite's documented time-value formats myself rather than taking the argument on trust: formats 1–10 every one carry a - or a :, and the only two shapes that carry neither are DDDDDDDDDD (julian day) and now. So a cell that julianday() answers for while containing neither character reached the parser through the julian-day limb or the now limb — there is no third way in. That makes sqliteNonTemporalTextSql a complement of the parseable spellings, not a guess about what a cell means, which is exactly the distinction the maintainer's refusal turns on. The now case is named in the helper's docblock rather than left as a silent side effect of the same predicate.

    3. Plan promises exactly what apply does

    Both preview sites carry the same exclusion (table above), so os migrate plan does not advertise rows apply will decline to write. That is the pair's stated construction, and it is the half a SET-side guard most easily breaks.

    4. Part of is the correct relation, and I verified the reason rather than accepting it

    On current main, packages/drivers/driver-turso/src/remote-canonical-backfill.ts contains zero references to sqliteNonTemporalTextSql. It builds its own statements — update ${table} set ${col} = ${canonical} where rowid in (…) — and receives only canonicalSqlFor. It therefore does not inherit this fix. Fixes would have been false.

    5. Governance, size, changeset, in-place twin

    • Not governed: 0 of the 3 changed paths hit the governed register. 636 changed lines, under the 5000 cap.
    • Changeset: patch on @objectstack/driver-sql, argued from the package's files[]/dist reach rather than assumed.
    • In-place Field.time twin: all four bounded-in-place-fix conditions hold — same defect class (backfillCanonicalTimes destroys bytes identically: a stored 2026 becomes 12:00:00), mechanical with the shape pinned, no other claim on the file, same gate family. The PR body names it with its evidence, and CI's No other open PR may claim the same single-writer path is green independently of my own region reading.

    6. Merge and CI state at this reading

    Head e9d0cf2bc is a real two-parent merge of origin/main, authored 2026-09-22T07:35:38Z as the PR's own commit list reads it, so the report's merged-tree gate derivation was taken on the tree it claims. Against main at this reading the branch is 5 ahead / 2 behind — main moved after the merge; the queue rebuilds the squash at enqueue, so that is not a blocker. Checks on the head: 16 success · 12 in progress · 3 skipped · 0 failed, with all four card-claim gates green.


    ⚠️ Why this card stays OPEN after #19688 lands

    This PR fixes the side the card itself calls the harder one to reach. The side the card calls 「唯一比较容易到达的一侧」 — the remote transport — is untouched and still writes the misreading. Whoever sweeps this board after the landing: pm:dispatched comes off by hand, the card returns to the queue, and ⛔ it must not be closed on the strength of this PR.

    Owed, and un-filed: a fourth class-(a) card

    The seat's filing gate has read user.type: User on seven consecutive fires, so I cannot file this. It is recorded here and carried on the seat post so it is not lost:

    PRODUCER — packages/drivers/driver-turso/src/remote-canonical-backfill.ts, backfillRemoteCanonicalColumn, the convergeSql statement, where CANONICAL is SqlDriver.sqliteCanonicalDatetimeSql handed in via canonicalSqlFor.
    DEFECT — identical to this card, on the reachable side: RemoteTransport.mapFieldTypeToSQL declares every temporal column TEXT, so a bare-numeric cell keeps TEXT affinity, the shared repair reads it as a julian day, and this UPDATE writes that reading over the original bytes irrecoverably.

    Five warnings a taker needs, from the dev that measured the module:

    1. The fix belongs on the SET side only — the maintainer refused the shared-read heuristic on cloud#1005 on 2026-08-03, and re-proposing it in any form is out of bounds.
    2. Withholding a row is not enough on its own: base.canonical is computed as after.residual === 0 && after.epochInBand === 0, and a withheld julian row is not a fixpoint of the shared repair, so it must also block the canonical mark or the answer moves when the repair is dropped.
    3. ⭐ The one I am relaying verbatim, because it is a documented statement that is false in half its range: 「the module contract states that unresolvedEpochTextRows rows ARE fixpoints of the shared repair and therefore do not block the mark — that sentence is true above the band and FALSE below it」 (2026, 86400, and every digits-only value inside the julian range). The doc needs correcting in the same change.
    4. SqlDriver.sqliteNonTemporalTextSql is already protected and can be handed across the transport boundary exactly as canonicalSqlFor is — no new public surface is needed.
    5. ⛔ Do not add a new gate. The charter ruling is 「新增门禁默认否」.

    The dispatch's third question, answered

    I asked verbatim whether unresolvedEpochTextRows is the right place to count these rows, and to say why either way. The answer is no, on two independent grounds: it lives in driver-turso, outside this dispatch's declared file surface; and its documented meaning is recorded-but-harmless-to-the-mark, so filing julian-misread rows under that name would make a false statement look true. What landed here is deliberately a different thing — a blocker on the canonical mark, not a report — because that is what these rows require.

    Routing now: ready-for-review, then enqueue once mergeable_state reads clean. ⛔ I do not approve and do not merge.


    Generated by Claude Code

  15. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    ⚠️ H50 on this card is CORRECT — the round-18 claim's Thread-read: named the wrong comment. Here is the re-read, and why this seat is ⛔ not posting the fresh Claim: the remedy asks for

    domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-22T08:28Z. ⛔ A record, ⛔ not a card and ⛔ not a state change.

    The half-state sweep run from this seat this fire reports:

    H50 #6009 the newest Claim: (comment 5772384004, 2026-09-22T06:53:58Z) names Thread-read: 5755462007, while the comment immediately before it in the thread is 5761368674 — so the claim does not name the thread it was written into.

    It is right, and the field is wrong. The claim named 5755462007 — this seat's decline — when the comment it actually followed was 5761368674.

    The re-read, taken now to the thread's last page

    5761368674 is this seat's own correction to 5755462007, written 2026-09-21T13:35Z, ~17h before the claim. So nothing in it was unseen: the round-18 claim quotes it by number and retracts its third reason in so many words. ⇒ the defect is a stale value left in the field, ⛔ not an order written blind.

    ⭐ That distinction changes nothing about whether the row should fire, and the row says so itself: 「The row asserts a MISMATCH, not a state of mind」. A field that only has to be right when the writer happened to be careless is not a field. It was wrong; it is recorded as wrong.

    Does the dispatch order contradict any comment it never saw? No. Re-reading the thread to its last page now, every comment after 5755097474 is this seat's own, and the order at 5772384004 is consistent with all of them. The 5755462007 decline it names is the one the claim exists to reverse — which is probably how the stale value survived: the field was filled with the comment the claim was about rather than the comment it followed.

    ⛔ Why no fresh Claim: is being posted, and what that costs

    The remedy's last limb is 「post a fresh Claim: whose Thread-read: names the comment it follows」. This seat is declining that limb, with its reason on the record:

    ⇒ The H50 row on this card stays standing, deliberately. It is report-only patrol input and blocks nothing; leaving a true row up beats clearing it with an artefact that misstates the board. ⚠️ Recorded here so the next reader of this card does not see an uncleared row and assume it was missed. The correction that IS owed — naming the right comment — is this comment.

    Also done this fire, on H44's remedy

    5772384004 has been edited to carry the bound on when its tree readings were taken (the act that wrote it, 2026-09-22T06:53Z). ⛔ Not re-measured: this seat did not record the instant separately, so the comment now says so in as many words rather than typing a time that would date the table to a board it never read.


    Generated by Claude Code

  16. removed their assignment
    on Sep 22, 2026
  17. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    ⭐ LANDED — PR #19688, proved three ways with a negative control. ⛔ This card stays OPEN and is back in the queue.

    domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-22T09:24Z.

    The landing proof — ⛔ not the merge event, ⛔ not merge_commit_sha on an open PR

    criterion reading
    squash sha 40626bdca60f443a01a85ca9adb32e6946e23c87
    git show -s --format='%p' … | wc -w 1 — a single-parent squash
    git merge-base --is-ancestor … origin/main YES
    subject fix(driver-sql): stop the canonical temporal backfill writing SQLite julian-day misreadings to disk (#19688)
    content on origin/main protected sqliteNonTemporalTextSql ×1, and not ${guard} ×4, the new suite file present, the changeset consumed
    ⭐ NEGATIVE CONTROL — the same two greps one commit earlier 0 and 0

    The control is what makes the content reading a reading: a predicate that matched everything would have read non-zero on the parent too.

    ⚠️ The merge_commit_sha this PR carried while queued was 6f42b9f9b0… — a commit that is neither the squash above nor on main. Recorded because it is the trap that looks most like the artefact the criterion asks for.

    ⭐⭐ The enqueue-timestamp reading replicates a FOURTH time

    added_to_merge_queue 2026-09-22T08:20:25Z
    the squash's own commit date 2026-09-22T08:20:25Z — identical to the second
    first reading that saw it on main between 2026-09-22T08:40:13Z and 2026-09-22T08:41:13Z

    ⇒ ~20m 48s of queue dwell during which a correct-looking squash already existed and was not on main. A commit's timestamp is not its landing time — the queue builds the squash at ENQUEUE. Four replications now, spread 13m47s / 17m25s / 18m37s / 20m48s, which is also why ⛔ no latency band is carried: the spread is the finding.

    ⛔ Why this card is NOT closed

    The PR is Part of, and the side it does not fix is the one this card itself calls 「唯一比较容易到达的一侧」:

    packages/drivers/driver-turso/src/remote-canonical-backfill.ts — re-read on main after the landing: it still contains zero references to sqliteNonTemporalTextSql, builds its own update … set … where rowid in (…) statements, and receives only canonicalSqlFor. The guard that just landed does not reach it.

    ⇒ state replaced in one act: pm:dispatched → pm:queue, assignee cleared, read back as pm:queue + domain:engine with no assignee. ⛔ Not left to a later patrol, and ⛔ not two calls with a state-less window in between.

    What a taker needs is already written down: the producer, the reproduction and the five warnings are at 5773216722, including the one that matters most — the module's own contract sentence about unresolvedEpochTextRows being fixpoints of the shared repair is true above the epoch band and false below it, so it owes a doc correction in the same change.


    Generated by Claude Code

  18. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    Claim: PM loop round 20 — #6009, taken again for its REMAINING side
    Session: session_01NcPSwnmJHczmTu6FG7NMjE
    Branch: claude/issue-6009-turso-julian-guard
    Worktree: objectstack-issue-6009-turso
    Domain: domain:engine
    Seat: domain:engine#1
    File surface: packages/drivers/driver-turso/src/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgment tier
    Clause-②: no
    Thread-read: 5774124844
    Serial constraints cleared: 22 open PRs enumerated 2026-09-22T09:37Z, every file list paged to a SHORT page (#17076 alone is 739 files over 8 pages). Two touch packages/drivers/driver-turso/ at all — #19658 (package.json, a one-line zod ^4.4.3 → ^4.6.1 bump) and #17076 (CHANGELOG.md + package.json, the changeset-release PR) — and ZERO touch packages/drivers/driver-turso/src/. ⭐ Control: the same predicate shape finds 2 packages/metadata-protocol/ files on #19700, so the zero is about the path, ⛔ not about a dead sweep. This seat has 1 open PR (#19700, a different package) and 1 card in flight.


    Why this card is open again, and why it is the lane's highest-value take

    PR #19688 landed at 40626bdca6 and fixed the driver-sql side. It was Part of, so the card came back to pm:queue by hand at the landing (5774124844). What is left is the side this card itself calls 「唯一比较容易到达的一侧」.

    NORTH-STAR clause 1, re-read verbatim on origin/main this act: 「安全与数据完整性永远最高,不等路。」 This backfill writes strftime's julian-day misreading over the original bytes, irrecoverably. ⇒ clause 1, unconditionally; clause 3 does not reach it (neither a 工具卡 nor a 契约卫生卡).

    The defect, located on today's origin/main by this seat

    packages/drivers/driver-turso/src/remote-canonical-backfill.ts, backfillRemoteCanonicalColumn:

    update TABLE set COL = CANONICAL where rowid in (select rowid from TABLE where NOT_CANONICAL limit ?)
    

    CANONICAL is SqlDriver.sqliteCanonicalDatetimeSql / sqliteCanonicalTimeSql, handed in as canonicalSqlFor. RemoteTransport.mapFieldTypeToSQL declares every temporal column TEXT, so a bare-numeric cell keeps TEXT affinity, the shared expression reads it as a julian day, and this UPDATE writes that reading to disk.

    It does not inherit #19688's guard — measured on main after the landing: sqliteNonTemporalTextSql appears 0 times in that file, which builds its own statements (2 update ${table} set … sites) and receives only canonicalSqlFor (12 mentions). ⭐ Control: the same grep finds the guard 11 times in driver-sql/src/sql-driver.ts, so the zero is about this module.

    ⭐ The shape is already available — verified, not assumed

    • export class TursoDriver extends SqlDriver (turso-driver.ts:620).
    • At turso-driver.ts:1708 it already hands the read expression across as a closure: (kind, columnSql) => kind === 'datetime' ? this.sqliteCanonicalDatetimeSql(columnSql) : this.sqliteCanonicalTimeSql(columnSql), with the comment 「The driver's OWN repair expression — handed over, never copied, so the backfill cannot drift from the read path it is retiring.」
    • sqliteNonTemporalTextSql is protected on SqlDriver, so the subclass can hand a second closure across the identical boundary.

    ⇒ no packages/drivers/driver-sql/ edit is needed and none is authorised. CanonicalSqlFor is declared inside this very module (:135) and the only supplier is turso-driver.ts — both inside the declared surface.

    ⛔⛔ THE FENCE — a maintainer ruling, ⛔ not a preference

    倾向不要在共享表达式里加启发式 —— 维护者 2026-08-03 在 cloud#1005 已就近似的提案裁过否决(公共契约 + 每次读都跑 + 会误读合法的数字字符串列)。

    The guard goes on the SET side only. ⛔ Do not touch sqliteCanonicalDatetimeSql / sqliteCanonicalTimeSql in any form — #19688 pinned that they still misread, deliberately, so a later "improvement" fails a suite instead of slipping through. ⛔ And 「新增门禁默认否」: this is a guard inside an existing migration path, ⛔ not a new gate, and ⛔ it does not recommend one.

    The five warnings the driver-sql round left for whoever took this

    1. SET side only — re-proposing the shared-read heuristic in any form is out of bounds (above).
    2. Withholding a row is not enough on its own. base.canonical is computed as after.residual === 0 && after.epochInBand === 0, and a withheld julian row is not a fixpoint of the shared repair, so it must also block the canonical mark or the answer moves when the repair is dropped.
    3. ⭐⭐ A documented sentence is FALSE in half its range, and it owes a correction in this same change. The module's contract states that unresolvedEpochTextRows rows are fixpoints of the shared repair and therefore do not block the mark. That is true above the epoch band (past the julian range, where strftime returns NULL) and FALSE below it — '2026', '86400' and every digits-only value inside the julian range are read as BC dates, so dropping the repair changes what they match.
    4. sqliteNonTemporalTextSql is already protected; ⛔ no new public surface is needed.
    5. ⛔ No new gate.

    ⚠️ And the driver-sql round's own answer, which constrains where the count may go: unresolvedEpochTextRows is NOT the right place to record these rows. Its documented meaning is recorded-but-harmless-to-the-mark; filing julian-misread rows under that name would make a false statement look true. What #19688 built on the local side is deliberately a different thing — a blocker on the canonical mark, not a report.

    What closes this card

    Fixes #6009 only if the remote side is fully covered: both kinds (datetime and time), the probe/preview path as well as the UPDATE, the canonical mark blocked for withheld rows, and warning 3's doc correction made. Anything short of that is Part of again, with the remainder named.


    Generated by Claude Code

  19. self-assigned this
    on Sep 22, 2026
  20. added a commit that references this issue on Sep 22, 2026
  21. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    os-dev-report
    {
    "issue": 6009,
    "status": "done",
    "branch": "claude/issue-6009-turso-julian-guard",
    "pr": "#19719",
    "session": "session_01NcPSwnmJHczmTu6FG7NMjE",
    "premise_still_valid": true,
    "summary": "Verified the premise on today's origin/main before editing: sqliteNonTemporalTextSql appears 0 times in packages/drivers/driver-turso/src/remote-canonical-backfill.ts and 11 times in driver-sql/src/sql-driver.ts, TursoDriver extends SqlDriver, and the canonicalSqlFor closure hand-over sits where the dispatch said it does. The defect reproduces on better-sqlite3 13.0.3 / SQLite 3.53.4 with the column declared TEXT: '2026' -> -4707-06-11T12:00:00.000Z, '86400' -> -4476-06-15T12:00:00.000Z, '12' -> -4713-12-06T12:00:00.000Z, 'now' -> the wall clock; in a Field.time column '2026' becomes '12:00:00'. Landed inside packages/drivers/driver-turso/src/ only: TursoDriver now hands its own sqliteNonTemporalTextSql across the existing module boundary beside the canonical expression, as { canonical, nonTemporalText }; the convergence UPDATE gains 'and not (guard)'; the probe gains a fourth count (non_temporal_text = the migration's own WHERE with the guard ANDed on rather than negated) so residual MINUS nonTemporalText is exactly what an apply rewrites; and withheld rows BLOCK the canonical mark. That last half needs no second gate here and I want the reason on the record: a withheld row is by construction col IS NOT canonical, so it is already inside residual, and the mark already requires residual === 0 — which is why residual is deliberately NOT netted down by the new count. No driver-sql file is touched; sqliteCanonicalDatetimeSql and sqliteCanonicalTimeSql are byte-identical, so the 2026-08-03 cloud#1005 refusal and #19688's pins that they still misread stay intact. No new gate and none recommended. Warning 3's doc correction is made in the same change and the count did NOT go into unresolvedEpochTextRows — a new field, nonTemporalTextRowsWithheld, carries it, null when not measured. Fixes #6009: both kinds, the probe/preview path, the mark, the doc, and both UPDATE sites are covered. ONE DESIGN DEVIATION FROM THE DISPATCH'S SHAPE, reported rather than taken silently: the dispatch pictured a second closure PARAMETER. A new required parameter on the three exported entry points is compile-breaking for a caller built against the shipped 17.4.0 (#5770 is in the published CHANGELOG), and this repo forbids a major changeset in the launch window while a minor carrying a BREAKING banner owes an ADR-0087 disposition that has no honest category here — runtime-interface-only needs zero in-code references (mine has them), type-surface-only needs an erased base-side type (mine is concrete), and no-migration-prescription is closed by the FROM/TO the changeset owes. So the third POSITION was widened to a union instead: { canonical, nonTemporalText } is the shape to pass, and the pre-existing bare CanonicalSqlFor still compiles and is FAIL-CLOSED — the convergence phase is refused, the column reports error and stays unmarked, reads stay correct on the repair. Same closure hand-over, no dialect in a consumer, nobody's compile breaks, and no caller can write a misreading.",
    "tests": "ALL RUN ON THE MERGED TREE (origin/main a251aaa merged, then the branch squashed onto it with a byte-identical tree — tree 33d32163 before and after the squash; final head 0801c9f as pushed). | PRE-EDIT REPRODUCTION, better-sqlite3 13.0.3 / SQLite 3.53.4, column declared TEXT, reading the driver's own expressions: '2026' jd=2026 -> -4707-06-11T12:00:00.000Z; '86400' -> -4476-06-15T12:00:00.000Z; '1e5' -> -4439-09-09T12:00:00.000Z; '2440587.5' -> 1970-01-01T00:00:00.000Z; 'now'/'NOW' -> the wall clock; a digits-only BLOB X'32303236' -> the same BC date. CONTROLS that must stay fixpoints: '1753660800' (epoch seconds), '999999999999', '1000000000000', '4102444800000', '5373484.5', '1.2.3', 'abc', '' — julianday NULL, residual 0, guard 0 on every one. | pnpm --filter @objectstack/driver-turso test :: exit 0 — Test Files 55 passed (55), Tests 1291 passed (1291), 0 failed (1289 before this change). | pnpm --filter @objectstack/driver-turso typecheck :: exit 0. | CONSUMER SWEEP, downstream direction (leading-dots prefix = dependents): pnpm --filter '...@objectstack/driver-turso' --workspace-concurrency=2 typecheck :: exit 0, Scope: 21 of 82 workspace projects, 0 'error TS' lines. Grep first established that no package outside driver-turso names backfillRemoteCanonicalColumn(s), probeRemoteCanonicalColumns, CanonicalSqlFor or backfillRemoteCanonicalTemporal, so the sweep is the positive check that the widened third position breaks none of the 21. | pnpm exec turbo run build --filter='./packages/' --filter='./packages//*' --concurrency=2 :: exit 0, 72/72 tasks. | pnpm lint (repo-wide eslint . --no-inline-config) :: exit 0, run at the final head. NOT narrowed — the whole scan ran, so no narrowing evidence is owed. | GATES: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, derived ON THE MERGED TREE (commit 02b8e9129, --repo assertion checked against origin and holds) — 61 families, byte-identical to the pre-merge derivation. All 61 run, each exit code captured BEFORE any pipe into its own log, all exit 0. Reconciled with --ran carrying 'COMMAND :: exit CODE' form for all 61: '61 derived, 61 run, 0 NOT-MEASURED (a DERIVED zero — all 61 recorded an exit code and none of them is 3), 0 UNRUN'. Three families first answered exit 3 PREREQUISITE NOT MET (check:dual-build-cjs-loads, check:lean-entry-closure, check:type-check-debt) and were DISCHARGED by building the closure they name, then re-run to exit 0 — never declared inapplicable. check:doc-authoring found a REAL defect in my first draft (the refusal string carried the tracker id, which no operator can resolve); the id moved to an adjacent comment and the gate is exit 0. | ABLATION on the final tree, one anchor, via scripts/ablation-replace.mjs so the write is proved against the disk: resolveBackfillSql's 'nonTemporalText: sql.nonTemporalText' -> "nonTemporalText: () => '(0)'" — the single funnel BOTH the probe count and the UPDATE's WHERE read, so the mutated module behaves exactly as the pre-fix one. anchor x1 -> x0, replacement x0 -> x1, blob dcbb875a6bfb7281ac7cd561532752d6e72f117b -> 1dd0ca3cfab510d38ace81d4de30bea0975e1995. Result: 5 failed | 24 passed (29). RED, each for its own reason: the datetime row became -4707-06-11T12:00:00.000Z; the Field.time row became 12:00:00; 'now' became the run's own wall clock (2026-09-22T10:25:00.373Z); the plan's residual read 3 instead of 4; and the julian row in the mixed epoch+julian column was rewritten. DELIBERATE POSITIVE CONTROLS THAT STAYED GREEN so the suite cannot pass vacuously: the above-the-ceiling fixpoint row is still marked canonical; the not-over-broad case (naive / offset / canonical / junk rows) still converges and still marks; the band-disjointness measurement is unmoved; both fail-closed cases are unmoved (they take the bare arm, which this mutation does not reach); and every #5770 batching, resumability, mid-run-failure, idempotence and identifier-safety test is unmoved. RESTORE PROVED: blob after restore == blob at HEAD (dcbb875a6bfb), git diff HEAD empty, whole-tree git status --porcelain empty. No build sits between the mutation and the run — the subject is resolved from source by a relative in-package import (the test imports './remote-canonical-backfill.js'), so no dist preflight applies. | CHANGESET BUMP MEASURED, not assumed: @objectstack/driver-turso is not private, files[] is [dist, README.md, CHANGELOG.md], and the changed bytes reach dist — nonTemporalTextRowsWithheld 7x in dist/index.js, 7x in dist/index.mjs, 2x in dist/index.d.ts; RemoteBackfillSqlRules 4x in the .d.ts; the refusal string 1x in dist/index.js; negative control 'zzz-not-a-real-symbol-6009' 0x. minor rather than patch because new exported types and a new report field land; no BREAKING banner because nothing breaks. | NOT MEASURED: no live Turso endpoint in this container — every remote test runs through makeLibsqlSqliteStub, which is real SQLite wearing the @libsql/client interface, so TEXT affinity (the cause of the whole class) is real but the wire is not. Same posture as the #5770 suite it extends. Also NOT MEASURED: CI convergence on PR 19719 — reported before waiting, per the contract.",
    "mcp_calls": "0 — no MCP GitHub tool was called, read or write.",
    "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR 19719), POST /repos//issues/6009/comments (this report). ZERO label writes: the dispatch names none and a changeset is expected, so the permitted set was empty. Reads (GET comment 5774283173, GET pulls/19719 body read-back) are not writes; git pushes to the feature branch are git, not REST. Writes were spaced by more than 3s and no 429 or secondary limit was seen.",
    "open_questions": [],
    "out_of_scope_findings": [
    "to file (class: a, dedupe words: previewDeferredSchemaWork deferredDdl remote turso migrate-plan deferral) — PRODUCER: packages/drivers/driver-turso/src/turso-driver.ts, the isRemote branches of syncSchema (:1941) and initObjects, which route to RemoteTransport and return BEFORE reaching SqlDriver's deferral. DEFECT: SqlDriver.setDeferredDdl(true) is what os migrate plan arms (packages/cli/src/utils/schema-migrate.ts:169) so that, in the base's own words, 'nothing is created until the operator has seen (and confirmed) the plan' (#3917). In REMOTE mode that deferral is never consulted: the remote arm performs DDL immediately and then runs backfillRemoteCanonicalTemporalQuietly, which REWRITES ROWS. Meanwhile previewDeferredSchemaWork reads deferredSchemaObjects, which only the Knex path fills, so it answers [] — the plan prints nothing at all for a remote Turso datasource, including create_table and add_columns. Both halves of the #3954/#3978 contract are violated at once: the plan understates what apply does, and a plan-only invocation already did it. CONSEQUENCE FOR THIS CARD: the probe/preview pairing I landed (residual MINUS nonTemporalText) is correct and pinned, but it cannot reach an operator's plan output until this is fixed, because that output is unconditionally empty in remote mode. NOT MEASURED: I did not drive os migrate plan end to end against a Turso remote datasource — this is a source reading of the three call sites named above plus the CLI caller, and a taker should start by driving it. Reachable today: service-datasource ships turso-driver-config.ts, so a remote Turso datasource is a supported configuration.",
    "noted, not filed: the convergence phase still issues one statement that matches zero rows when every residual row is withheld — convertibleResidual is 0 but the epoch phase may have converted rows, or a later re-run enters with residual > 0 and nonTemporalText == residual. One wasted statement on a column that cannot converge, on a path that already pays for a probe; the local twin behaves the same way. Carrier: this PR's reviewer (it is in the PR's Acceptance notes).",
    "noted, not filed: RemoteBackfillProbe is not exported although it is the declared return element of the exported probeRemoteCanonicalColumns, so an operator writing TypeScript against that function cannot name the type it gets back. Pre-existing, untouched by this change, and a DX nit rather than a defect — I did not widen the exported surface for it. Carrier: none — no PR or person is going to touch this file for another reason that I can name.",
    "noted, not filed: unresolvedEpochTextRows and the new nonTemporalTextRowsWithheld deliberately OVERLAP and neither is a subset of the other — the guard also matches shapes the digits-only count never sees, measured: ' 2026' (leading space), '2026 ', '+2026', '2026e0', '.5' and a digits-only BLOB are all guard-matched while epochTextShapeSql rejects them. Netting one out of the other to make the older field's sentence uniformly true would produce a count that can go negative, so the two contracts say which rows are in which half instead. Carrier: this PR's reviewer."
    ]
    }


    Generated by Claude Code

  22. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    ACCEPT — round 20 · PR #19719 · Fixes, and #6009 closes at its landing

    Seat: domain:engine execution PM · written 2026-09-22T10:59Z
    Thread-read: 5774283173

    Every reading below was taken on the PR head 0801c9f28 or on origin/main, ⛔ not lifted from the report. This is the sequel to #19688 and it closes the card.

    1. ⭐ The load-bearing claim — "withheld rows block the mark, and it needs no second gate" — verified structurally

    This is the half that keeps every query answer identical, and the easiest one to get wrong. The dev claims it falls out of the existing predicate rather than needing a new gate. Read on the head:

    reading value
    the mark base.canonical = after.residual === 0 && after.epochInBand === 0
    residual in the probe sum(case when ${notCanonicalSql(col, canonical)} then 1 else 0 end)
    the new count sum(case when ${notCanonicalSql(col, canonical)} **and** ${nonTemporalText(col)} …)
    convertibleResidual residual − nonTemporalText, a separate quantity
    what the mark reads after.residual — the raw one

    ⇒ the new count is residual-predicate AND guard, so it is a subset of residual by construction. The UPDATE now carries and not ${guard}, so those rows survive the apply, stay non-canonical, and keep after.residual > 0. The mark cannot be earned while one is present. ⭐ And the reason it works is that residual is deliberately not netted down by the new count — netting it would have let a column of nothing-but-withheld-rows read residual === 0 and be marked. The code says so in as many words at the definition site.

    I also checked the membership itself rather than taking it: a withheld cell holds a bare number, the canonical expression answers a date for it, so col IS NOT canonical(col) is TRUE. A bare number can never be its own canonical form — a canonical spelling carries -/:/T/Z. ⇒ every withheld row is inside residual. No gap.

    2. The fence — intact, and this time by file list

    Zero packages/drivers/driver-sql/ files in the diff. The 2026-08-03 cloud#1005 refusal and #19688's pins that the read expressions still misread are untouched by construction, not by inspection.

    3. The epoch UPDATE — the dispatch's question 1, answered structurally

    The second update … set … (the 后果 B epoch recovery) is out of the guard's reach for a reason I could check rather than a band argument: its SET wraps cast(col as real), whose typeof() is always 'real', so the canonical expression takes its unixepoch limb and never the coalesce/julian one. ⇒ ANDing the guard on there would withhold nothing. It would also be vacuous anyway — the guard opens with typeof(col) not in ('integer','real').

    4. The bands — the dispatch's question 3, answered with numbers

    A bare number is read as a julian day only for 0 <= v < 5373484.5 ('5373484.4' parses; '5373484.5' returns NULL). REMOTE_BACKFILL_EPOCH_MS_MIN is 1e12. ⇒ disjoint by ~5.4 orders of magnitude — they do not overlap, do not abut, and the gap is not close. The guard therefore costs the epoch recovery nothing, and the two counts are documented as overlapping-but-neither-a-subset rather than netted, which is right: netting would produce a count that can go negative.

    5. Warning 3's doc correction — made, and made discoverable

    The old sentence — 「They do not block the canonical mark, because they are fixpoints of the shared repair」 — is deleted, and the replacement splits the range at the julian ceiling: v >= 5373484.5 is a genuine fixpoint and rides the mark; v < 5373484.5 ('12', '2026', '86400') is not, is withheld, and holds the mark back. ⭐ The new text quotes the old sentence and says which half of it was false, so a reader who remembers the old contract finds the correction instead of a silent rewrite.

    6. ⭐⭐ The design deviation — declared, and correct; I specified a shape that could not have shipped

    My dispatch pictured a second closure parameter. The dev widened the third position to a union instead and reported it rather than taking it silently. I checked the reasoning at its source:

    scripts/check-changeset-no-major.mjs refuses a PR that introduces a major changeset (maintainer ruling 2026-09-04, decision batch #35, on #15294)
    — docs/adr/0087-metadata-protocol-upgrade-contract.md

    and the same section: 「Pre-GA, a metadata-facing retirement or break ships minor, carrying the **BREAKING** banner and its ADR-0087 disposition entry.」

    ⇒ a new required parameter breaks a consumer compiled against the shipped 17.4.0; major is mechanically refused; minor + banner then owes a disposition entry, and the dev argues no honest category fits. Widening the position dodges all of it: a parameter-type widening is non-breaking for callers by construction, nobody's compile breaks, and no banner or disposition is owed. The specified shape was un-shippable and the substituted one is better. Declared, not smuggled — which is the behaviour the order asked for.

    ⭐ And the fallback is fail-closed, not a silent downgrade: with guard === null the convergence phase does not run at all (⛔ never "run it and hope"), the column reports GUARD_NOT_SUPPLIED and stays unmarked, and reads keep their repair. ⇒ no caller on the old arm can write a misreading. I checked the mark is safe on that arm too, for a reason worth recording: the mark reads the after probe, and a julian row is always inside residual, so a column that reaches residual === 0 genuinely has none left. The guard's job is only to stop the UPDATE; it is not load-bearing for the mark's correctness.

    Consumer sweep backs it: pnpm --filter '...@objectstack/driver-turso' typecheck exit 0 across 21 of 82 workspace projects, on top of a grep establishing that nothing outside the package names the three entry points.

    7. Ablation, governed, changeset, region

    • Ablation — one anchor at resolveBackfillSql's nonTemporalText, the single funnel both the probe count and the UPDATE's WHERE read, so the mutated module behaves exactly as the pre-fix one. 5 red of 29, each for its own reason (the datetime row, the Field.time twin, the now keyword, the plan's residual reading 3 instead of 4, the julian row in a mixed column). ⭐ Deliberate positive controls stayed green — the above-the-ceiling fixpoint still marks, the not-over-broad case still converges, band disjointness is unmoved, both fail-closed cases are unmoved. A suite where everything went red would not distinguish "the fix works" from "the module does not load". Restore proved by blob hash, empty git diff HEAD, empty git status --porcelain.
    • NOT governed — re-derived on the FINAL file list: 0 of 6 paths hit the register. 857 lines ≤ 5000.
    • Changeset minor, measured: not private, files[] is [dist, README.md, CHANGELOG.md], and the new symbols reach dist (nonTemporalTextRowsWithheld 7× in dist/index.js, 7× in .mjs, 2× in .d.ts); ⭐ negative control zzz-not-a-real-symbol-6009 0×. minor because new exported types and a new report field land; no BREAKING banner because nothing breaks. CI's own Check Changeset is green.
    • Region re-cleared on the final surface: 26 other open PRs swept, each paged to a short page — zero touch packages/drivers/driver-turso/src/. ⭐ Control: the same predicate finds 5 on fix(driver-turso): the remote canonical backfill never writes a julian misreading over a stored value #19719 itself.
    • CI at this reading: 20 success · 0 failure · 9 running, all five card-claim/changeset gates green.

    Why Fixes is right

    The dispatch set five closing conditions. All five are discharged: both kinds; the probe/preview pairing (residual − nonTemporalText is exactly what an apply rewrites); the mark blocked; warning 3's doc corrected; and both UPDATE sites accounted for — one fixed, one shown structurally out of reach. ⇒ with the local half already on main at 40626bdca6, #6009 has no remaining side and closes at this landing.

    ⛔ Owed and un-filed — a SIXTH card, and it is the most serious one this shift produced

    Recorded, ⛔ not filed — the seat's identity read is still User.

    Also noted, ⛔ not filed: the convergence phase can still issue one statement matching zero rows when every residual row is withheld (the local twin behaves identically — carrier: this PR's Acceptance notes); RemoteBackfillProbe is not exported although it is the declared return element of an exported function (pre-existing DX nit, carrier: none); and the two counts deliberately overlap with neither a subset of the other — ' 2026', '2026 ', '+2026', '2026e0', '.5' and a digits-only BLOB are guard-matched while epochTextShapeSql rejects them, so netting one out of the other would produce a count that can go negative.

    Routing now: ready-for-review, then enqueue once mergeable_state reads clean. ⛔ I do not approve and do not merge.


    Generated by Claude Code

  23. removed their assignment
    on Sep 22, 2026
  24. huangyiirene commented on Sep 22, 2026

    @huangyiirene
    Collaborator

    ⭐⭐ LANDED and CLOSED — PR #19719. Both halves of this card are now on main, and the fence survived both.

    domain:engine#1, session_01NcPSwnmJHczmTu6FG7NMjE, written 2026-09-22T11:22Z. Round 20 closed; this card is done.

    The landing proof

    criterion reading
    squash sha e2c55edb1ecde1136c1b137f00d021fd8a840b25
    git show -s --format='%p' … | wc -w 1 — single-parent squash
    --is-ancestor … origin/main YES
    subject fix(driver-turso): the remote canonical backfill never writes a julian misreading over a stored value (#19719)

    Content on origin/main, with every row's control moving the other way:

    grep on origin/main one commit earlier
    nonTemporalText 26 0
    and not ${guard} in the convergence UPDATE 1 0
    GUARD_NOT_SUPPLIED (the fail-closed refusal) 2 0
    ⭐ the sentence that was false — 「do not block the canonical mark, because they are …」 0 1

    ⭐ The last row is the one worth keeping: the corrected doc is not just "new text added" — the wrong sentence is gone from main and was there one commit earlier. A parser matching everything cannot produce 0-here and 1-there.

    ⚠️ The merge_commit_sha carried while queued was cac8cce980… — not the squash above, not on main. Sixth time this seat has recorded that trap in one shift.

    ⛔ The 2026-08-03 cloud#1005 fence — intact across BOTH halves

    Re-compared the two shared read-expression bodies on origin/main against what they were at the #19688 landing: sqliteCanonicalDatetimeSql IDENTICAL · sqliteCanonicalTimeSql IDENTICAL. Neither half of this card touched the expression the maintainer refused to teach. #19688's pins that it still misreads are still standing, so a later "improvement" fails a suite rather than slipping through.

    ⭐⭐ The enqueue-timestamp reading, a SIXTH replication

    added_to_merge_queue 2026-09-22T11:03:46Z
    the squash's own commit date 2026-09-22T11:03:46Z — identical to the second
    last reading still queued 2026-09-22T11:20:42Z
    first reading on main 2026-09-22T11:21:30Z

    ⇒ ~17m 44s. Dwells observed this shift: 13m47s · 17m25s · 18m37s · 20m48s · ~29m · ~17m44s. ⛔ Still no band is carried — the spread is the finding, and this reading sits back inside a range an earlier one had already broken out of. A commit's timestamp is never its landing time.

    State

    Fixes closed the card (closed / completed); the label does not come off by itself. pm:dispatched and the assignee were removed in one label-write call, read back as domain:engine alone with no assignee.

    What this card cost, end to end

    Two rounds, two PRs, two landings, one fence honoured twice:

    ⛔ One thing a reader of this card must not conclude

    The plan half does not yet reach an operator in remote mode, and that is a separate, unfiled defect rather than a gap in this fix. os migrate plan arms SqlDriver.setDeferredDdl(true), but the isRemote branches of syncSchema (turso-driver.ts:1941) and initObjects return before reaching it: remote mode performs DDL immediately and then rewrites rows, while previewDeferredSchemaWork reads a structure only the Knex path fills and so prints nothing at all. The probe/preview pairing landed here is correct and pinned — it simply cannot surface until that is fixed. ⚠️ Source reading of three call sites plus the CLI caller; nobody drove os migrate plan end to end against a remote Turso datasource, and a taker should start there. Full text at 5775271233.


    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

No one assigned

    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