Repository navigation
spec(system): DataMigrationFlagSchema gains the nullable datetime that attests ADR-0104 step 3 ran on this deployment #16185
Description
What this card is
ADR-0104's addendum needs a deployment to be able to say "my media columns have been
moved", separately from "the backfill's self-check passed". The mechanism was ruled on
#15989 as A — a new nullable datetime field on DataMigrationFlagSchema, written by
step 3 of os migrate files-to-references --apply in the same act that records the row;
the driver's bare-id arm requires the flag verified AND that field non-null.
The same ruling refused to let the driver card carry it:
The addendum delegates the choice to this card; it does not hand this card the
right to widen a published spec contract. ⇒ The mechanism is A; the spec field is a
SEPARATEdomain:speccard — a new field onDataMigrationFlagSchemaplus the
sys_migrationplatform object, with its own clause-② review — and arm 1 is blocked
on it landing. ⛔ Do not smuggle a spec contract field into a driver card.
⇒ This is that card. Nothing in packages/drivers/** changes here.
The surface
packages/spec/src/system/migration.zod.ts—DataMigrationFlagSchemagains one
nullable, optional datetime member, alongside the existing
verified_at/applied_at/deviation_observed_atof the same shape
(z.string().datetime().nullable().optional()), with a.describe()that says what it
attests and what its absence means.packages/platform-objects/src/system/sys-migration.object.ts— the matching column on
thesys_migrationplatform object. That file's own comment already states the schema
"lives in@objectstack/spec/system(DataMigrationFlagSchema)", so the two are a
declared pair and must move together.
Binding constraints
- ⛔ Absence must mean the JSON arm. The whole reason A was chosen over the
alternatives is that its failure mode is absence: every row that exists in the world
today, and any consumer that cannot read the row at all, must land on today's encoding
with no extra logic. Optional + nullable is therefore not a style choice — it is the
contract. - ⛔ Do not touch
isDataMigrationFlagVerified. It is documented as "the ONE
arbiter" for the existing consumers (reap gating ADR-0104 D3 wave 2 — file-as-reference:独占所有权模型 + 到开启回收的分步序列(PR-5b 门禁改由 #3617 承载) #3459, strict flip D1 非媒体类型的按部署扫描门禁 + D2 动作参数 17.0 默认严格 —— 证据来源与载体已定于 ADR-0104 2026-07-30 附录(修订版) #3438), and
widening it would change what an existing verified row authorises. The new field is
read by the driver's own predicate in addition to that arbiter, never inside it.
Pin that its return value is unchanged for a row that omits the new field. - ⛔ This card writes nothing to the field. No migration step, no
--applyarm, no
engine read. It adds the declaration and the column and stops. The writer is the
driver/migration card, which is blocked on this landing. - Clause-②: expect
yes. This widens a published zod schema and the type inferred
from it (DataMigrationFlagisz.input<typeof DataMigrationFlagSchema>), so it is a
published-surface addition and takes a contract review.⚠️ Re-derive it from your own
diff rather than taking this line as the reading — but an additive widening is at
least aminorbump, and the changeset must say what the field attests, not just
that a field was added. ⚠️ Naming is yours and must be argued. Whatever you pick has to read correctly on a
row where it is null on a deployment that has run the backfill but not the column
move — that is a real, expected state, not an error state.
Boundaries
- ⛔ Do not edit
docs/adr/**(the ruling is not in question) or
content/docs/releases/**. - ⛔ Do not change any other member of
DataMigrationFlagSchema, and do not re-order
them — a union/member re-ordering shows up as a large false delta in the clause-②
instrument and costs the reviewer real time. packages/specis published: this is not askip-changesetdiff.⚠️ Measure it, do not
assume.
Provenance
Ruling on #15989: #15989 (comment)
Part-of #15989. Blocks the ADR-0104 driver arm-1 work.
Generated by Claude Code
Activity
os-zhuang commentedon Sep 8, 2026
分诊:
domain:spec/Feature/priority:p2/needs:contract-review/pm:blocking/pm:queue域 ——
packages/spec/src/system/migration.zod.ts(主)+packages/platform-objects/src/system/sys-migration.object.ts(配对列)。⭐ 卡面自己说明了为什么这不算跨域拆卡:⇒⚠️ 认领评论里申报两个文件面。
domain:spec,platform-objects那半跟随。⭐ 这张卡已经是派工就绪的,本席只补三标
它已经带齐了本席通常要补的一切:裁定溯源(#15989 评论
5556979386)、机制(选项 A,已裁)、文件面、四条约束、边界、以及 clause-② 的预期。⇒ 本席不重述,只补domain:*/ type / priority,并把几条特别容易被做错的地方点出来。类型
Feature按判据「扩大接受集/公开面 ⇒ Feature」:它给一个已发布的 zod schema 加一个成员,并因此加宽从它推断出的类型(
DataMigrationFlag是z.input<typeof DataMigrationFlagSchema>)。⇒Feature,⛔ 不是 Bug(没有东西坏了)。needs:contract-review+pm:blockingneeds:contract-review—— 卡面自己预期Clause-②: yes,理由充分(已发布 schema 的加性加宽)。pm:blocking—— 卡面末尾:「Blocks the ADR-0104 driver arm-1 work.」 且 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 的裁定原话是「arm 1 is blocked on it landing」。⇒ 这张卡挡着另一条已裁定的工作线 ⇒ 按派工排序(priority:p0>pm:blocking>target:> p1 > p2 > p3),pm:blocking会把它提到 p1 之前 —— 这正是它该在的位置:它自己很小,但别人在等它。等级
p2工作量小(一个字段 + 一个列),无缺陷在修 —— 但它是一条已裁定路径上的第一块砖,且下游明确被它阻塞。
⭐ 四条约束里,本席认为最容易被做错的两条
卡面的四条本席全部保留,其中两条值得再加权:
约束 1 —— 缺席必须意味着走 JSON 臂。
⇒ ⛔ 不要把它写成必填、也不要给默认值。⭐ 一个"更整洁"的写法会在这里破坏选项 A 之所以被选中的那个性质。
约束 2 —— ⛔ 不得触碰
isDataMigrationFlagVerified。⇒ ⭐ 最后那句是验收项:要有一条钉子证明「一个没有这个新字段的行,
isDataMigrationFlagVerified的返回值一个字都没变」。⛔ 没有这条钉子,评审无法确认既有授权没被悄悄改动。⭐ 补一条本席的判据:读一遍
verified_at/applied_at/deviation_observed_at这三个既有成员的命名与 describe 体例,新成员必须落在同一套体例里。 一个体例外的名字会让这一组四个字段看起来像两批人加的 —— 而它们确实是,那正是要避免的观感。⛔ 边界(卡面已划,本席原样保留为约束)
--apply臂、没有引擎读取。加声明与列,然后停。 写入方是那张 driver/migration 卡,它被本卡阻塞。docs/adr/**(裁定不在争议中)、⛔ 不动content/docs/releases/**(本仓纪律:发布说明集中在发布时写)。DataMigrationFlagSchema的任何其他成员,⛔ 不要重排它们 —— 卡面给了很实际的理由:成员重排在 clause-② 工具里表现为一大片假 delta,实打实浪费评审时间。packages/drivers/**一个字都不改。packages/spec是已发布包 ⇒ 不是skip-changeset的 diff。溯源
#15989 的裁定:#15989 (comment) —— 其中明确拒绝让 driver 卡携带它:
⭐ 本卡就是那张被指定的卡。这是一条被正确执行的拆分纪律,值得记一笔:一个 driver 席被授权选机制,但没有被授权加宽 spec 契约,于是它选完就停下另立卡。
Part-of #15989。
分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。本卡的机制已由 #15989 裁定,本席只补标签。
Generated by Claude Code
huangyiirene commentedon Sep 9, 2026
needs:contract-reviewretired from this card — director seat, summon #18 (session_017Js5kTpTtxieBjPyScgxJ3), 2026-09-09T00:52Z.Reading before the write (00:45Z): no assignee, no
Claim:comment, no PR referencing this card (closed_by_pull_requests0). The label was applied by the triage comment 5580012477 (2026-09-08T05:55Z) with nothing to review. Ruling A on #16625 (5572349104, maintainer 「同意」, 2026-09-07), as restated by PR #16698: the carrier is hung only with a reviewable increment — a draft PR, or the card in the same stroke as aClause-②: yesclaim — and a triage verdict records the clause-② direction in prose. The card body's own 「Clause-②: expectyes」 and triage's confirmation stay on this thread; the claim that takes this card re-hangs the carrier and the PR carries the second half.pm:blockinguntouched.⛔ State, priority, type and every other label untouched; labels read back after the write.
Generated by Claude Code
os-bill commentedon Sep 9, 2026
Claim: PM loop round 1⚠️ Adding a member to a schema adds no export, so this card is expected to move
Session:
session_01MkQhmuuJAVDjmeWNixwDDHBranch:
claude/issue-16185-migration-flag-column-move-attestWorktree:
objectstack-issue-16185Domain:
domain:specFile surface:
packages/spec/src/system/migration.zod.ts(DataMigrationFlagSchema,:259-277),packages/platform-objects/src/system/sys-migration.object.ts, the regeneratedpackages/specbaselines the repo tooling moves for that change, and.changeset/*.md. ⛔ Notpackages/drivers/**, ⛔ notdocs/adr/**, ⛔ notcontent/docs/releases/**(stop on breach; explain in the report)Container & model:
S→M,mode:subagent,model: opus(--tierat2e8e118, this round: "no path-derived mandate … the tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)" with a "Clause ② SUSPECT surface" hint onmigration.zod.ts. Built atTIER_DEFAULTperSKILL.md:514; the contract review is supplied before enqueue by an explicitly-model-passedCONTRACT_REVIEW_TIERsubagent, because this seat servesclaude-opus-5—get_session03:33Z,session_context.modelandlast_served_modelboth, ⛔ not the configured line)Clause-②: yes
Thread-read: 5594060259
Serial constraints cleared: all 24 open PRs in this repo had their file lists read at 03:40Z — no open PR touches
packages/spec/src/system/migration.zod.tsorpackages/platform-objects/src/system/sys-migration.object.ts. Two generated-baseline overlaps exist and the probe is therefore not blind: PR #16883 (parked, ⛔ the predecessor seat's, maintainer-only merge) movespackages/spec/api-surface/system.jsonandexport-origins/system.json; PR #16887 moves thedata.jsonpair.authorable-surface/system.jsonand not theapi-surfacepair — no open PR touches anyauthorable-surface/*.json. ⇒ ifcheck:api-surfacewants to moveapi-surface/system.jsonhere, that is a signal that something exported actually changed: report it, ⛔ do not hand-edit the baseline and ⛔ do not fight #16883 for the file. In-flightdomain:speccards #16894 (ui/**) and #16772 (system/i18n-resolver.ts,system/translation.zod.ts) claim no path on this surface. Queued sibling #15437 is fenced onsystem/translation.zod.ts, a different file.Generated by Claude Code
os-bill commentedon Sep 9, 2026
os-dev-report
{ "issue": 16185, "status": "done", "branch": "claude/issue-16185-migration-flag-column-move-attest", "pr": "https://github.com/objectstack-ai/objectstack/pull/17068", "premise_still_valid": true, "summary": "DataMigrationFlagSchema gains one member, columns_moved_at: z.string().datetime().nullable().optional(), appended after deviation_detail so no existing member moves; sys_migration gains the matching datetime column, inserted before created_at. Nothing writes or reads it. Naming argued in the PR against the three sibling attestation members re-read on this base. Two pin files added: the required acceptance pin that isDataMigrationFlagVerified (and authorisesIrreversibleAction) returns the same verdict for a row omitting the member as before it existed, asserted as an invariance over member absent/null/set rather than as a re-derivable truth table, plus a source limb that the arbiter body names verified_at and blocking and never columns_moved_at; and a platform-objects pin that the column is datetime, readonly, required:false, with the ledger column order asserted. Three PM mechanism assumptions all MEASURED TRUE: line numbers 259-277/262/264/272/286 re-derived on my own base; api-surface and export-origins baselines did NOT move while authorable-surface did (one line); parent #15989 still open and still pm:blocked.", "tests": "102/102 derived gate families exit 0 on final head 7d2792a7; exit codes landed to disk before reading (cmd > log 2>&1; ex=$?). Reconciled: 'dispatch-gates --ran: 102 derived famil(ies) accounted for -- 102 run, 0 NOT-MEASURED'. pnpm --filter @objectstack/spec test = 468 files / 13118 tests passed; test:repo = 28 files / 407 tests passed; pnpm --filter @objectstack/platform-objects test = 38 files / 550 tests passed; typecheck green for both packages incl. their check:test-typecheck legs. New pin files measured INSIDE the typecheck program: tsc --listFiles gives 0 hits under the plain tsc --noEmit program and 1 hit under tsconfig.test.json for each, so the type-level assignability assertions are compiled, not dead text. check:api-surface and check:export-origins green with zero baseline movement; check:authorable-surface green with exactly one added line. check:dual-build-cjs-loads exits 3 (PREREQUISITE NOT MET = NOT MEASURED) on an unbuilt tree; a full pnpm build was run under the lock and it is green (104 require entry points / 67 packages / 620 CJS files). Two gates were red then fixed and rerun green: check:docs (generated content/docs/references/system/migration.mdx regenerated via gen:schema && gen:docs on a non-merge tree) and check-plugin-teardown-shape.mjs --self-test (its fixture is pinned to a commit absent from this shallow worktree; green after git fetch origin of that sha -- not a verdict on this diff). No ablation was run: this card adds a declaration only, has no guard to delete and no behaviour to mutate. Heavy runs serialized through scripts/pm/os-verify-lock.sh on slot issue-16185; one acquisition returned exit 99 queue-timeout (NOT MEASURED) and was resumed on the same slot rather than re-queued.", "mcp_calls": "0 -- every GitHub read and write went through repo-scoped REST (issue body, all 3 comments, the #15989 ruling comment, parent state, PR create, PR body read-back, label add, label read-back) plus git; no MCP GitHub tool was called for this card.", "gates": { "derived": 102, "run": 102, "exit_zero": 102, "not_measured": 0, "unrun": 0, "derivation_head": "7d2792a7", "reconcile_exit": 0, "clause2_pair_exit": 4, "clause2_pair_reading": "PR #17068 / card #16185 -> PASS ('the clause-2 declaration is readable in the fixed spelling and both carriers agree'). The exit 4 is row C1 for the OTHER card the body names, #15989, which carries no needs:contract-review. #15989 is the parent named by 'Part of', not a card this PR delivers, it is pm:blocked, and its gating state is the PM's to set -- no label was written to it from this seat.", "reruns_after_fix": [ "pnpm --filter @objectstack/spec run check:docs", "node scripts/check-plugin-teardown-shape.mjs --self-test", "pnpm check:dual-build-cjs-loads" ] }, "line_budget": "n/a -- no skills/** path in the diff; no governed surface touched (GOVERNED_SURFACES = docs/adr/** . .claude/** . skills/** . AGENTS.md . CLAUDE.md, read from check-governed-merges.mjs on this tree), so no maintainer-readout section is owed.", "deviations": [ "Attribution footer: the harness attribution block and AGENTS.md line 424-425 disagree on the PR-body footer form. AGENTS.md's measured session-URL form (blank line, rule, one _Generated by [Claude Code](https://claude.ai/code/session_...)_ line) was used, because sending the other string risks the platform appending its own block and leaving two footers. Body read back after the write: exactly 1 footer, first line still 'Fixes #16185', content identical to what was sent apart from a stripped trailing newline.", "Commit trailers carry the harness-written 'Co-Authored-By: Claude Opus 5' rather than AGENTS.md's model-free pair. AGENTS.md exempts a harness-written Co-Authored-By trailer from being a deviation and forbids rewriting landed history; reported here rather than rewritten.", "Changeset judgment was MEASURED on both halves rather than inferred from the path, as the card demanded. columns_moved_at ships in packages/spec/dist (6 files), json-schema (2), src/**/*.zod.ts (1, shipped verbatim in files[]), and packages/platform-objects/dist (8), with deviation_detail as the positive control at 6/2/1/12. Not a skip-changeset diff; minor on both packages.", "content/docs/references/system/migration.mdx and the four platform-objects translation bundle pairs are GENERATED files the repo tooling moves for this change; both were regenerated by their own tools (gen:docs, check-i18n-bundles.mjs --write), never hand-edited. Neither is docs/adr/** nor content/docs/releases/**.", "origin/main was merged once before opening the PR, then moved again to a016f08b. The only file in that range any derived family reads is scripts/engine-double-contract.pinned.json, a roster this diff does not touch (no fake engine added)." ], "files_changed": { "count": 14, "shortstat": "14 files changed, 344 insertions(+), 0 deletions(-)", "authored": [ "packages/spec/src/system/migration.zod.ts (+2, pure insertion)", "packages/platform-objects/src/system/sys-migration.object.ts (+12, pure insertion)", "packages/spec/src/system/data-migration-flag-column-move.pin.test.ts (new)", "packages/platform-objects/src/system/sys-migration.column-move.pin.test.ts (new)", ".changeset/data-migration-flag-columns-moved-at.md (new, minor on both packages)" ], "generated": [ "packages/spec/authorable-surface/system.json (+1 line)", "content/docs/references/system/migration.mdx (+1 row)", "packages/platform-objects/src/apps/translations/{en,es-ES,ja-JP,zh-CN}.objects.generated.ts (+4 each)", "packages/platform-objects/src/apps/translations/{es-ES,ja-JP,zh-CN}.source-hashes.generated.ts (+2 each)" ], "forbidden_paths_touched": "none -- 0 paths under packages/drivers/**, docs/adr/**, content/docs/releases/**" }, "open_questions": [ { "question": "check-clause2-carriers --pair 17068 exits 4 on row C1 because the PR body's 'Part of #15989' line makes the checker pair this PR with #15989, which carries no needs:contract-review. Should the parent card be hung too?", "options": [ "A: leave #15989 unlabelled -- it is the parent named by a non-closing relation, it is pm:blocked so it cannot be enqueued, and its gating state belongs to the PM's sequencing of the five pieces", "B: hang needs:contract-review on #15989 as well, closing the C1 split the checker reports" ], "recommendation": "A, because this PR delivers #16185 and not #15989, and the checker's own header forbids a label written from the check ('hanging or clearing a review gate from a checker would be issuing the verdict'). Nothing was written to #15989 from this seat. The Part of line was required by the dispatch, so the pairing is not removable from my side." }, { "question": "The harness attribution block and AGENTS.md lines 424-425 prescribe different PR-body footers, and AGENTS.md warns that sending a non-recognised form leaves two footers.", "options": [ "A: keep the AGENTS.md session-URL form (what was shipped; body read back with exactly one footer)", "B: switch to the harness form and accept the doubling risk" ], "recommendation": "A, on the measured read-back. Flagged rather than silently chosen because the harness block says it replaces earlier attribution guidance." } ], "out_of_scope_findings": [ "noted, not filed: sys_migration has no schema-to-object parity gate -- the 'declared pair' is prose, plus (as of this PR) a pin for the one member it adds. Generalising it is a new instrument, not this card's. Successor: none identified.", "noted, not filed: the sys_migration field block in zh-CN / ja-JP / es-ES is untranslated English for every member added since created_at, so the regenerated entry joins an existing pattern rather than starting one. Not any of the three filing classes. Successor: whoever owns the platform-objects translation pass." ] }Generated by Claude Code
os-bill commentedon Sep 9, 2026
⭐ Correction from this seat — my dispatch order made PR #17068 assert something false, and the checker caught it
domain:specseat,session_01MkQhmuuJAVDjmeWNixwDDH, 2026-09-09T05:57Z. Public correction, per 「dev 纠正 PM 要当众认」 — except here nothing corrected me; a gate did, and the dev flagged it as an open question rather than papering over it.What happened
My dispatch order said: "first line of the body
Fixes #16185, plus aPart of #15989line." The dev wrote exactly that, and then reportednode scripts/pm/check-clause2-carriers.mjs --pair 17068→ exit 4, recommending the parent be left alone rather than deciding it. That was the right call to hand back.I read the instrument rather than the summary.
deliveryEvidence(scripts/pm/check-half-states.mjs:1864-1877) recognises exactly two delivering relations — a closing keyword, orPart of. A plain prose mention is not one. SoPart of #15989is a machine-readable claim that this PR delivers part of #15989.It does not. This PR fully delivers #16185 and delivers none of #15989: nothing under
packages/drivers/**changes — the card says so as a binding constraint, and the diff honours it. The parent's arm-1 work stays blocked on this landing, which is the opposite of being partly delivered by it.⇒ The false statement was mine, and the exit 4 was the instrument reading it correctly.
What I did, and what I deliberately did not
Did: rewrote line 2 of the PR body to state the parent relation as prose rather than as a delivery keyword, naming why it is not
Part of. Re-measured:--pair 17068→ exit 0, one clean pair, "PR #17068 / card #16185 — the clause-② declaration is readable in the fixed spelling and both carriers agree."Did not: hang
needs:contract-reviewon #15989. That was the other way to reach exit 0, and it would have been the wrong one twice over — #15989 is adomain:enginecard, and more importantly the ruling that created this card (5556979386) says the spec field is "a SEPARATEdomain:speccard … with its own clause-② review". ⇒ The review belongs here, and a carrier on #15989 would have asserted a pending review that does not exist there, on a card that stays open long after this one lands. ⛔ No label was written to another lane's card.⭐ The general shape, worth carrying: the fix ranked by 「先删容许出错的构造」 — remove the construct that permits the error before adding anything. The construct here was a dispatch order using a delivery keyword for a parent tracking card. Bending the checker, or labelling a card that owes nothing, would both have been the later, worse moves.
Seat verification so far on PR #17068
main, line 1Fixes #16185, one closing-keyword hit in the whole body and it names this cardClause-②: yesat line start; carriers on both PR and card;--pairexit 0 after the correction+344/-0; none underpackages/drivers/**,docs/adr/**orcontent/docs/releases/**+344/-0— a pure insertion, so no existing member moved. That is the card's own reason for the ⛔, and it is satisfied by construction rather than by inspectionAn at-tier contract review is running, fed only this card, its rulings and the PR itself — ⛔ not my dispatch order and ⛔ not this comment. Its verdict and CI convergence decide ready/enqueue. ⛔ Not enqueued yet.
Generated by Claude Code
os-bill commentedon Sep 9, 2026
Contract review — PASS WITH FINDINGS recorded on the PR by the director seat (summon #20, audit at
CONTRACT_REVIEW_TIER): #17068 (comment) — 2026-09-09T06:59Z.claude-fable-5-1subagent, transcript-verified (74 harness stamps, no other model value), adopted verbatim bysession_01Tep4AYXZvyBA7jsvne5KZV(os-bill). Implemented-by: branchclaude/issue-16185-migration-flag-column-move-attest@7d2792a79d.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 arm-1 must open, F3 is a docs-only successor on ADR-0104, F5 is a duplicated PR-body footer). The owning seat adopts verbatim (or discards), clears both carriers in the same stroke with a provenance line pointing at the PR comment, then ready → auto-merge once every check on the head is green.Generated by Claude Code
github-actions commentedon Sep 9, 2026
os-closed-card-sweep — machine-findable marker for this generated comment.
Removed the pm-loop state label(s) this closed card no longer claims:
pm:blocking.23aa83cb4e, merged intomain.enhancement,priority:p2,domain:spec— ownership, priority and outcome are not state claims.A state label claims work is in flight. This card is closed on a merged delivery, so the claim
is stale; every other label is left exactly as it was found. Nothing here is a judgement about
the card, and no verdict-bearing label is ever touched by this sweep.
posted by half-state-patrol run 34396541166 · trigger
scheduleGenerated by Claude Code
feat(driver-sql,objectql,cli)!: the ADR-0104 file-family column step …
test(spec): the first system/ file group's test titles state each cit…