You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding: cloneData is the one create face whose response contract carries no droppedFields — a clone that copies or overrides a readonly column is stripped and warned, but not reported on the wire #15703
Found while patching PR #15395 (the create-side static-readonly strip moving into engine.insert, card #14147); filed unassigned and out of that PR's scope because closing it changes a published response contract.
What is true today
After the 2026-09-03 ruling (option C on #14147), every DataProtocol create face hands the caller's payload to engine.insert whole and the engine strips a non-system caller's static-readonly keys. Five of the six create-shaped faces surface the engine's onFieldsDropped as their response droppedFields — createData, createManyData, insertManyData, and batchData's create rows and both arms of upsert that create. cloneData does not. It passes no listener, and its response contract, CloneDataResponseSchema (packages/spec/src/api/protocol.zod.ts, #11924, declared AS PRODUCED by maintainer ruling 2026-08-25), is exactly { object, id, sourceId, record }; packages/metadata-protocol/src/search-clone-schema-conformance.test.ts asserts in so many words that the body carries no droppedFields. PR #15395 pins that absence with its reason (protocol.readonly-insert.test.ts, the firing-control block) and narrows its changeset sentence to the faces that report.
Why it is worth a card
A clone IS a create, and it is the one create shape that can carry a read-only column without the caller typing it: cloneData copies the source row (approval_status: 'approved' included) and applies overrides on top, then inserts. The engine strips the copied or overridden value and logs the warn line — but the 201 body says nothing, so a caller that cloned an approved record and reads record.approval_status: 'draft' back has no field in the response telling it why. Every other create face now answers that question on the wire; the clone answers it only in server logs.
Suggested shape (a decision, not a patch)
Either add an optional droppedFields member to CloneDataResponseSchema (same shape as CreateDataResponseSchema's), wire the listener in cloneData exactly as createData does, and flip the two conformance pins and the firing-control pin — a @objectstack/spec surface widening, minor under the bump-level rule — or record that a clone deliberately does not report drops and say why on the schema. Both are legitimate; neither is a delegation detail of #14147.
Related: PR #15395, card #14147, the contract review at comment 5548671173 on #14147 (row A12).
Comment 5572013366 (2026-09-07T14:22Z) declared 「pm:queue → needs-user-decision」 and carried the full 四棱 + 维护者速读. The comment landed; the label write did not. The card has been sitting in pm:queue since — i.e. visible to this lane's candidate selection as dispatchable work, while its own newest comment says every route needs a maintainer ruling.
⇒ ⛔ This is not a re-adjudication and ⛔ not a new judgment. It is 补齐成对 on a state change that went out half-written: one label write, pm:queue → needs-user-decision, read back (enhancement, priority:p2, needs-user-decision, domain:spec, finding; assignee empty). The analysis, options and recommendation of 5572013366 stand unchanged and untouched.
How it was caught. Taking over this seat, the outgoing brief (5577721567) listed five cards as 决策盒待裁. Reading each against its own thread rather than against the brief showed the list was stale in both directions: #14791 (batch #1) and #15543 (batch #2) were ruled on 2026-09-07 and correctly converted to pm:queue, and #16685 was ruled 2026-09-08T00:17Z (batch #80) — three dispatchable cards, not pending ones. This card is the only one of the five that is genuinely still awaiting a ruling, and it was the one card wearing the wrong label.
⚠️ Still open for the maintainer, unchanged: 选 1(给 CloneDataResponseSchema 加可选 droppedFields)还是 2(写明 clone 刻意不报告,理由落进 schema 注释)? The four facets are not aligned on this card — three point to 1, 「创业阶段不扩散」 points to 2 — which is precisely why it is here and not auto-adjudicated.
⛔ Not dispatched. ⛔ No grade, type or domain:* touched.
Ruling recorded — option 1: cloneData reports droppedFields like every other create face (director seat, decision batch #83, 2026-09-08)
Maintainer's reply, verbatim (live PM chat, 2026-09-08T03:4xZ, answering the batch #83 presentation where this card was item 1 with recommendation 1):
其他同意
⇒ CloneDataResponseSchema gains an optional droppedFields member of the same shape as CreateDataResponseSchema's; cloneData passes the onFieldsDropped listener exactly as createData does; the two conformance pins (search-clone-schema-conformance.test.ts) and PR #15395's firing-control pin in protocol.readonly-insert.test.ts flip in the same PR. @objectstack/spec changeset minor under the bump-level rule.
Sequencing: the schema member and the producer land together ("declared AS PRODUCED" — never a declared member the route does not fill). The PR is Clause-②: yes (a published response contract widens) and goes through the contract-review tier.
Label: needs-user-decision → pm:queue. ⛔ Not dispatched by this seat.
Dispatching the ruled work to an os-dev subagent. The dev inherits this claim: it does not write the assignee and does not post a second Claim:.
Ruling being executed — comment 5578819560 (director seat, decision batch #83, 2026-09-08), maintainer verbatim 「其他同意」, option 1: CloneDataResponseSchema gains an optional droppedFields member of the same shape as CreateDataResponseSchema's; cloneData passes the onFieldsDropped listener exactly as createData does; the two conformance pins and PR #15395's firing-control pin flip in the same PR. ⛔ Not re-adjudicated.
Clause-②: yes — this widens a published response contract, so the dispatch runs at CONTRACT_REVIEW_TIER, not the default tier, and ⛔ the PR cannot be enqueued on a below-tier verdict.
File-face declared (for cross-lane intersection checks): packages/spec/src/api/protocol.zod.ts, packages/metadata-protocol/src/search-clone-schema-conformance.test.ts, the cloneData implementation and its protocol.readonly-insert.test.ts firing-control block, plus a .changeset/ entry. ⚠️ This intersects packages/spec with no other in-flight card in this lane at claim time.
Sequencing precondition verified before dispatch, not assumed: PR #15395 is MERGED (merged_at: 2026-09-05T12:24:23Z), so the firing-control pin it added is on main and can be flipped without fighting it — this was the condition triage attached to route 1.
{
"issue": 15703,
"status": "done",
"branch": "claude/issue-15703-clonedata-dropped-fields",
"pr": "https://github.com/objectstack-ai/objectstack/pull/16823",
"premise_still_valid": true,
"summary": "Option 1 implemented as ruled (comment 5578819560): CloneDataResponseSchema gains an optional droppedFields member with the same Zod shape as CreateDataResponseSchema's (z.array(DroppedFieldsEventSchema).optional()); cloneData wires the onFieldsDropped listener byte-for-byte as createData does and spreads droppedFields onto its return only when non-empty; the two conformance pins in search-clone-schema-conformance.test.ts and PR #15395's firing-control pin in protocol.readonly-insert.test.ts flip in the same PR; changeset @objectstack/spec minor (plus @objectstack/metadata-protocol minor and @objectstack/client minor). AS PRODUCED honoured: the conformance suite now measures the produced member on the real producer over an engine double that strips and fires the event. All six ZONE 2 premises held, with one nuance for the reviewer: the card's 'in so many words' forbid (conformance test line 227, expect(body).not.toHaveProperty('droppedFields')) sat on a fixture engine that never fired onFieldsDropped, so it was vacuous with respect to the listener — the teeth against wiring were protocol.readonly-insert.test.ts:266 and packages/objectql/src/protocol-data.test.ts:654 (the latter unnamed on the card; it asserted insert was called with exactly { context }), both flipped. Beyond the three ruled files, two edits flagged in the PR body: packages/client/src/index.ts CloneDataResult (the declared mirror) gains the same optional member because its published docblock stated 'No droppedFields' — a sentence this change falsifies in a shipped .d.ts; and content/docs/api/data-api.mdx (coordinator's docs-drift reading) stated the 201 body as exactly { object, id, sourceId, record } — changed; data-engine.mdx and http-protocol.mdx measured still true and left; neither release-owned page is falsified (no docs-only card needed); none of the five pages names CloneDataResult at all (0 hits each, control page 4 hits of CloneDataResponse). The CreateDataResponseSchema describe sentence about the X-ObjectStack-Dropped-Fields header was NOT copied: measured, the clone route relays verbatim and never calls applyDroppedFieldsHeader, so the new describe says body-only. Process facts: claim verified (5580290466 names this branch; assignee was already set by the PM, untouched); origin/main merged before opening the PR via scripts/pm/os-regen-merge.sh after dispatch-gates reported a STALE TREE (8 gate scripts moved upstream), hand-written delta byte-identical across the merge, check:generated green post-merge with nothing to regenerate; a second STALE TREE at the final head names only scripts/check-regen-pending.mjs and scripts/git-merge-regen.mjs (merge-driver tooling, not a family my paths derive a verdict from) — not merged again, CI runs current copies. Label writes, both additive and read back: needs:contract-review on PR #16823, and — after check-clause2-carriers --pair 16823 answered exit 4 (C1: PR carries it, card does not) — the same label on card #15703, which the triage seat had explicitly left to the dispatch side; the predicate then answers exit 0. The run was killed by a 429 mid-task and resumed from re-established state (two commits already pushed); nothing was redone. Worktree removed after the PR opened. PR left a draft; not readied, enqueued or armed.",
"tests": "All build/test/typecheck through scripts/pm/os-verify-lock.sh (slot issue-15703), exit codes captured before any pipe; VERDICT lines quoted. Heads: b617ce886 (pre-merge), 68c74383f (after merging origin/main), d11785752 (final, docs-only commit on top; hand-written code delta identical across all three by diff-of-diffs cmp). spec build: VERDICT command-exit 0 (204s at b617ce886; 209s at 68c74383f); rebuilt dist/api/index.d.ts carries droppedFields inside CloneDataResponseSchema (positive control 15 hits in the file). spec check:generated: pre-merge 1 of 15 stale (content/docs/references → gen:docs) regenerated via --fix and committed (authorable-surface/api.json written by the build's gen:schema; authorable-surface.base.json untouched); post-merge '✓ All 15 generated artifacts are up to date.' Closure: turbo build objectql^... + metadata-protocol (14/14) — metadata-protocol/dist/index.js carries the spread droppedFields at the clone return. metadata-protocol pnpm test @b617ce886: 'Test Files 169 passed | 2 skipped (171)', 'Tests 2434 passed | 10 skipped (2444)'; typecheck (tsc --noEmit) 0 errors. The two flipped files explicitly @68c74383f: 'Test Files 2 passed (2)', 'Tests 19 passed (19)'. objectql src/protocol-data.test.ts + src/protocol-clone-real-engine.test.ts @b617ce886 and @68c74383f: 'Test Files 2 passed (2)', 'Tests 121 passed (121)'; objectql check:test-typecheck OK (44 files / 242 errors / 69 pinned — the ledgered debt, unchanged). client tsc --noEmit (src): exit 0, empty, both heads. spec pnpm test @b617ce886 and @68c74383f: 'Test Files 465 passed (465)', 'Tests 12955 passed (12955)'; spec typecheck exit 0 incl. check:test-typecheck OK (54 files / 261 errors / 145 pinned, unchanged). ABLATION (under the lock, VERDICT command-exit 0 · held 58s, trap-restored, absolute paths): cloneData's opts listener replaced by a marker comment — on-disk proof grep -c marker=1, neighbouring opts.context line=1, the 3 identical listener spellings in other faces untouched; leg A (src-resolved) vitest run of the two metadata-protocol files: exit 1, 'Tests 4 failed | 15 passed (19)' — the clone delegation case (expected undefined to deeply equal the event), the 7-face enumeration ('expected undefined to be function'), the named clone presence pin, and the conformance 'declared AND produced' case; leg B (dist-resolved: objectql imports @objectstack/metadata-protocol unaliased) rebuild exit 0 → ablation-dist-preflight marker present exit 0 → protocol-data.test.ts exit 1, 'Tests 1 failed | 116 passed (117)' at 'forwards context to findOne and insert'; restore: git checkout HEAD -- ABS_PATH, git diff HEAD 0 lines, hash-object equals HEAD blob 67bcd138cc046aaf053bbe61ad23956b70e80a52, marker 0, status clean; rebuild exit 0, preflight --absent exit 0 ('marker absent from all 24 built files'), both suites green again (19/19, 117/117). Direction observed: red, no reversal. GATES: dispatch-gates --commands --repo objectstack-ai/objectstack at the merged head derived 103 families (first derivation at b617ce886 was 80: the committed docs regen added 23 docs/prose families and a STALE TREE named 8 moved gate scripts — hence the merge); all 103 run, --ran reconciles '103 derived, 103 run, 0 UNRUN' at 68c74383f and again at d11785752; 101 exit 0 incl. check:nul-bytes, check:empty-changeset, check:adr-0087-registration, check:changeset-no-major, check:pm-widening-tells, check:engine-double-contract, check:corpus-claim-drift, check:doc-anchors, spec check:api-surface / check:authorable-surface / check:docs / check:liveness / check:skill-examples (the last only after client + client-react were built; its first run was a build-prerequisite refusal). After the docs-only commit the 25 docs-reading families were re-run on that exact tree: 21 exit 0 immediately, 4 answered exit 3 because the re-created worktree had no dist (check:doc-formula-expressions, check:doc-security-posture, check:docs, check:docs-transcript-drift) — spec + lint rebuilt under the lock (VERDICT command-exit 0 · held 159s) and all 4 then exit 0 before the commit was made. NOT MEASURED, declared to CI: @objectstack/client full pnpm test and its check:test-typecheck (unit tests import @objectstack/runtime / objectql / plugin-hono-server unaliased, outside the built closure; the client change is a type-only widening); pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt (both PREREQUISITE NOT MET, exit 3, 57 packages without dist); check:react-declaration-parity (EXTERNAL_INPUT_REQUIRED, as always). check-clause2-carriers --pair 16823: exit 4 before the card label (C1 split), exit 0 after. PR body read back twice: create stored byte-identical minus one trailing newline; the PATCH (sent without a footer) stored byte-identical with the platform's bare footer block appended; 0 angle brackets, no HTML comment. CI status at report time: in_progress (not waited on).",
"mcp_calls": "0 — every GitHub read and write went over repo-scoped REST (issue + comments + PR #15395 reads; branch-push write probe; POST pulls draft=true; POST issues/16823/labels; POST issues/15703/labels; PATCH pulls/16823; GET read-backs after each write; POST issues/15703/comments for this report). No search_issues; dup check not needed (this is a ruled card, no new issue filed).",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: after this PR the clone 201 body carries droppedFields but the route sets no X-ObjectStack-Dropped-Fields header, while POST /data/:object and PATCH /data/:object/:id set both (bulk routes set neither) — incompleteness, not a defect class, and no contract text promises the clone route a header; deliberately not added (a wire change the ruling did not name); the schema describe and data-api.mdx state body-only. 承接者: this PR's contract reviewer if they want it; otherwise none.",
"noted, not filed: .changeset/tidy-cups-smile.md (PR #15395, unreleased) still says cloneData 'forwards whole but reports nothing on the wire … adding that key is a spec change' — accurate history of that change, left untouched; this PR's changeset states the new fact. 承接者: whoever compiles the release notes.",
"noted, not filed: docs/qa/platform-checklist/areas/records-forms.json 'Clone contract edges' item describes the body as {object, id, sourceId, record} (still true for a no-drop clone); its readonly-forge step could now also assert droppedFields. 承接者: the next checklist-author sweep.",
"noted, not filed: content/docs/kernel/contracts/data-engine.mdx layering callout's hedge 'wherever their contract declares one' is now vacuous (all six create faces declare it) — still true, polish only. 承接者: none.",
"noted, not filed: content/docs/releases/implementation-status.mdx and content/docs/releases/v17.mdx (release-owned, read-only) are NOT falsified by this change — implementation-status lists the clone route as implemented, v17's write-observability note claims droppedFields for PATCH/POST /data, bulk, batch and the SDK and never claimed or denied the clone; no docs-only card needed.",
"noted, not filed: dispatch-gates STALE TREE at the final head names scripts/check-regen-pending.mjs and scripts/git-merge-regen.mjs (2 upstream commits after the merge) — merge-driver/pre-commit tooling, not a family whose verdict my paths move; CI runs the current copies. 承接者: none."
]
}
Activity
os-zhuang commentedon Sep 5, 2026
分诊 ·
pm:queue/domain:spec/priority:p2/enhancement/finding⛔ 本席位只分诊:不认领、不派单、不写码、不合并、不裁决 decision-box 卡。
为什么锚定
domain:spec无论走哪条路,被改的契约字节都在
packages/spec/src/api/protocol.zod.ts的CloneDataResponseSchema上(加一个可选droppedFields成员,或在 schema 上写明「clone 刻意不报告」)。按车道表:packages/spec整包唯一契约、schema 形状与 describe 散文一律domain:spec。cloneData上像createData那样接上 listener,并翻转两条一致性 pin 与 PR #15395 的 firing-control pin。⇒ 那些是同一次契约变更的必要随行,本席位按不可切分处理,⛔ 不拆卡。priority:p2的理由要害是卡片点出的那条不对称性,而不是「少了一个字段」:
⇒ 引擎剥掉了那个值并记了
warn,但 201 的响应体一个字都没说。一个克隆了「已批准」记录、读回record.approval_status: 'draft'的调用方,在响应里找不到任何字段告诉它为什么。而其余五个 create 面(createData、createManyData、insertManyData、batchData的create行与upsert的两条创建臂)都已经在 wire 上回答了这个问题。不给 p1:没有数据被写错——剥离是 2026-09-03 option C 裁决要求的正确行为,且 warn 已落到服务端日志。丢的是可观测性的一致性。
不给 p3:6 个面里 5 个报告、1 个不报告,且不报告的恰是最容易在调用方无意识下触发的那一个。
⛔ 两条路线都合法 —— 本席位给排序,⛔ 不裁
droppedFields(与CreateDataResponseSchema同形),在cloneData里接上 listener,翻转两条一致性 pin 与那条 firing-control pin。⇒@objectstack/spec表面加宽,按 bump-level 规则取 minor。本席位倾向 1,判据只有一条:「五个面回答、一个面沉默」这件事本身就是下一个人要重新发现的东西。若裁定 2,⛔ 请务必把理由写进 schema 的 describe/docblock,而不是只留在卡上——否则第三次发现它的人还会再开一张卡(本轮已经出现三张「解释缺失或过期导致重复发现」的卡:#15640、#15669、#15688)。
needs:contract-review)。⛔ 本席位不代贴该标签、不代定档位。⛔ 三条边界
readonlystrip is a protocol-boundary guard only —engine.insertapplies none of it, andcreate_record'sonFieldsDroppedchannel can never fire for a readonly drop #14147 的委派细节。 卡片明说,且 PR fix(objectql,metadata-protocol): a staticreadonlyfield is stripped from a non-system INSERT insideengine.insert, and the boundary copy is deleted #15395 刻意把它留在范围外并用protocol.readonly-insert.test.ts的 firing-control 块钉住了这处缺席及其理由、把 changeset 措辞收窄到「会报告的那些面」。⇒ 本卡不得被读成 finding: the insert-pathreadonlystrip is a protocol-boundary guard only —engine.insertapplies none of it, andcreate_record'sonFieldsDroppedchannel can never fire for a readonly drop #14147 的收尾。CloneDataResponseSchema的「AS PRODUCED」性质。 该 schema 按 2026-08-25 维护者裁决是按实际产出声明的(Four client SDK routes answer a shape no published contract declares —automation.create/automation.update/search/data.clone#11924)⇒ 加成员必须与cloneData的真实产出同批变更,⛔ 不能先声明后实现。packages/metadata-protocol/src/search-clone-schema-conformance.test.ts逐字断言该 body 不带droppedFields。⇒ 走路线 1 就得翻转它;⛔ 但翻转前请确认 PR fix(objectql,metadata-protocol): a staticreadonlyfield is stripped from a non-system INSERT insideengine.insert, and the boundary copy is deleted #15395 已落地,否则会与它的 firing-control 块打架。Refs:PR #15395 · #14147(2026-09-03 option C 裁决)· #11924(
CloneDataResponseSchema的 AS PRODUCED 裁决)· #14147 上的契约复审5548671173(row A12)。分诊席位 ·
claude-opus-5· 本轮 R+156Generated by Claude Code
zhuangjianguo commentedon Sep 8, 2026
Half-state repaired — the label write the 14:22Z conversion declared but never landed
domain:specexecution seat, sessionsession_016N6xmWt5hYm94ffVEwGH8x, R1, 2026-09-08T02:38Z.Comment
5572013366(2026-09-07T14:22Z) declared 「pm:queue→needs-user-decision」 and carried the full 四棱 + 维护者速读. The comment landed; the label write did not. The card has been sitting inpm:queuesince — i.e. visible to this lane's candidate selection as dispatchable work, while its own newest comment says every route needs a maintainer ruling.⇒ ⛔ This is not a re-adjudication and ⛔ not a new judgment. It is 补齐成对 on a state change that went out half-written: one label write,
pm:queue→needs-user-decision, read back (enhancement,priority:p2,needs-user-decision,domain:spec,finding; assignee empty). The analysis, options and recommendation of5572013366stand unchanged and untouched.How it was caught. Taking over this seat, the outgoing brief (
5577721567) listed five cards as 决策盒待裁. Reading each against its own thread rather than against the brief showed the list was stale in both directions: #14791 (batch #1) and #15543 (batch #2) were ruled on 2026-09-07 and correctly converted topm:queue, and #16685 was ruled 2026-09-08T00:17Z (batch #80) — three dispatchable cards, not pending ones. This card is the only one of the five that is genuinely still awaiting a ruling, and it was the one card wearing the wrong label.CloneDataResponseSchema加可选droppedFields)还是 2(写明 clone 刻意不报告,理由落进 schema 注释)? The four facets are not aligned on this card — three point to 1, 「创业阶段不扩散」 points to 2 — which is precisely why it is here and not auto-adjudicated.⛔ Not dispatched. ⛔ No grade, type or
domain:*touched.Generated by Claude Code
os-zhuang commentedon Sep 8, 2026
Ruling recorded — option 1:
cloneDatareportsdroppedFieldslike every other create face (director seat, decision batch #83, 2026-09-08)Maintainer's reply, verbatim (live PM chat, 2026-09-08T03:4xZ, answering the batch #83 presentation where this card was item 1 with recommendation 1):
⇒
CloneDataResponseSchemagains an optionaldroppedFieldsmember of the same shape asCreateDataResponseSchema's;cloneDatapasses theonFieldsDroppedlistener exactly ascreateDatadoes; the two conformance pins (search-clone-schema-conformance.test.ts) and PR #15395's firing-control pin inprotocol.readonly-insert.test.tsflip in the same PR.@objectstack/specchangesetminorunder the bump-level rule.Sequencing: the schema member and the producer land together ("declared AS PRODUCED" — never a declared member the route does not fill). The PR is
Clause-②: yes(a published response contract widens) and goes through the contract-review tier.Label:
needs-user-decision→pm:queue. ⛔ Not dispatched by this seat.Generated by Claude Code
zhuangjianguo commentedon Sep 8, 2026
Claim: session
session_016N6xmWt5hYm94ffVEwGH8x· branchclaude/issue-15703-clonedata-dropped-fields·domain:specexecution seat · 2026-09-08T06:22ZDispatching the ruled work to an
os-devsubagent. The dev inherits this claim: it does not write the assignee and does not post a secondClaim:.Ruling being executed — comment
5578819560(director seat, decision batch #83, 2026-09-08), maintainer verbatim 「其他同意」, option 1:CloneDataResponseSchemagains an optionaldroppedFieldsmember of the same shape asCreateDataResponseSchema's;cloneDatapasses theonFieldsDroppedlistener exactly ascreateDatadoes; the two conformance pins and PR #15395's firing-control pin flip in the same PR. ⛔ Not re-adjudicated.Clause-②: yes— this widens a published response contract, so the dispatch runs atCONTRACT_REVIEW_TIER, not the default tier, and ⛔ the PR cannot be enqueued on a below-tier verdict.File-face declared (for cross-lane intersection checks):⚠️ This intersects
packages/spec/src/api/protocol.zod.ts,packages/metadata-protocol/src/search-clone-schema-conformance.test.ts, thecloneDataimplementation and itsprotocol.readonly-insert.test.tsfiring-control block, plus a.changeset/entry.packages/specwith no other in-flight card in this lane at claim time.Sequencing precondition verified before dispatch, not assumed: PR #15395 is MERGED (
merged_at: 2026-09-05T12:24:23Z), so the firing-control pin it added is onmainand can be flipped without fighting it — this was the condition triage attached to route 1.https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Generated by Claude Code
claude commentedon Sep 8, 2026
os-dev-report
{ "issue": 15703, "status": "done", "branch": "claude/issue-15703-clonedata-dropped-fields", "pr": "https://github.com/objectstack-ai/objectstack/pull/16823", "premise_still_valid": true, "summary": "Option 1 implemented as ruled (comment 5578819560): CloneDataResponseSchema gains an optional droppedFields member with the same Zod shape as CreateDataResponseSchema's (z.array(DroppedFieldsEventSchema).optional()); cloneData wires the onFieldsDropped listener byte-for-byte as createData does and spreads droppedFields onto its return only when non-empty; the two conformance pins in search-clone-schema-conformance.test.ts and PR #15395's firing-control pin in protocol.readonly-insert.test.ts flip in the same PR; changeset @objectstack/spec minor (plus @objectstack/metadata-protocol minor and @objectstack/client minor). AS PRODUCED honoured: the conformance suite now measures the produced member on the real producer over an engine double that strips and fires the event. All six ZONE 2 premises held, with one nuance for the reviewer: the card's 'in so many words' forbid (conformance test line 227, expect(body).not.toHaveProperty('droppedFields')) sat on a fixture engine that never fired onFieldsDropped, so it was vacuous with respect to the listener — the teeth against wiring were protocol.readonly-insert.test.ts:266 and packages/objectql/src/protocol-data.test.ts:654 (the latter unnamed on the card; it asserted insert was called with exactly { context }), both flipped. Beyond the three ruled files, two edits flagged in the PR body: packages/client/src/index.ts CloneDataResult (the declared mirror) gains the same optional member because its published docblock stated 'No droppedFields' — a sentence this change falsifies in a shipped .d.ts; and content/docs/api/data-api.mdx (coordinator's docs-drift reading) stated the 201 body as exactly { object, id, sourceId, record } — changed; data-engine.mdx and http-protocol.mdx measured still true and left; neither release-owned page is falsified (no docs-only card needed); none of the five pages names CloneDataResult at all (0 hits each, control page 4 hits of CloneDataResponse). The CreateDataResponseSchema describe sentence about the X-ObjectStack-Dropped-Fields header was NOT copied: measured, the clone route relays verbatim and never calls applyDroppedFieldsHeader, so the new describe says body-only. Process facts: claim verified (5580290466 names this branch; assignee was already set by the PM, untouched); origin/main merged before opening the PR via scripts/pm/os-regen-merge.sh after dispatch-gates reported a STALE TREE (8 gate scripts moved upstream), hand-written delta byte-identical across the merge, check:generated green post-merge with nothing to regenerate; a second STALE TREE at the final head names only scripts/check-regen-pending.mjs and scripts/git-merge-regen.mjs (merge-driver tooling, not a family my paths derive a verdict from) — not merged again, CI runs current copies. Label writes, both additive and read back: needs:contract-review on PR #16823, and — after check-clause2-carriers --pair 16823 answered exit 4 (C1: PR carries it, card does not) — the same label on card #15703, which the triage seat had explicitly left to the dispatch side; the predicate then answers exit 0. The run was killed by a 429 mid-task and resumed from re-established state (two commits already pushed); nothing was redone. Worktree removed after the PR opened. PR left a draft; not readied, enqueued or armed.", "tests": "All build/test/typecheck through scripts/pm/os-verify-lock.sh (slot issue-15703), exit codes captured before any pipe; VERDICT lines quoted. Heads: b617ce886 (pre-merge), 68c74383f (after merging origin/main), d11785752 (final, docs-only commit on top; hand-written code delta identical across all three by diff-of-diffs cmp). spec build: VERDICT command-exit 0 (204s at b617ce886; 209s at 68c74383f); rebuilt dist/api/index.d.ts carries droppedFields inside CloneDataResponseSchema (positive control 15 hits in the file). spec check:generated: pre-merge 1 of 15 stale (content/docs/references → gen:docs) regenerated via --fix and committed (authorable-surface/api.json written by the build's gen:schema; authorable-surface.base.json untouched); post-merge '✓ All 15 generated artifacts are up to date.' Closure: turbo build objectql^... + metadata-protocol (14/14) — metadata-protocol/dist/index.js carries the spread droppedFields at the clone return. metadata-protocol pnpm test @b617ce886: 'Test Files 169 passed | 2 skipped (171)', 'Tests 2434 passed | 10 skipped (2444)'; typecheck (tsc --noEmit) 0 errors. The two flipped files explicitly @68c74383f: 'Test Files 2 passed (2)', 'Tests 19 passed (19)'. objectql src/protocol-data.test.ts + src/protocol-clone-real-engine.test.ts @b617ce886 and @68c74383f: 'Test Files 2 passed (2)', 'Tests 121 passed (121)'; objectql check:test-typecheck OK (44 files / 242 errors / 69 pinned — the ledgered debt, unchanged). client tsc --noEmit (src): exit 0, empty, both heads. spec pnpm test @b617ce886 and @68c74383f: 'Test Files 465 passed (465)', 'Tests 12955 passed (12955)'; spec typecheck exit 0 incl. check:test-typecheck OK (54 files / 261 errors / 145 pinned, unchanged). ABLATION (under the lock, VERDICT command-exit 0 · held 58s, trap-restored, absolute paths): cloneData's opts listener replaced by a marker comment — on-disk proof grep -c marker=1, neighbouring opts.context line=1, the 3 identical listener spellings in other faces untouched; leg A (src-resolved) vitest run of the two metadata-protocol files: exit 1, 'Tests 4 failed | 15 passed (19)' — the clone delegation case (expected undefined to deeply equal the event), the 7-face enumeration ('expected undefined to be function'), the named clone presence pin, and the conformance 'declared AND produced' case; leg B (dist-resolved: objectql imports @objectstack/metadata-protocol unaliased) rebuild exit 0 → ablation-dist-preflight marker present exit 0 → protocol-data.test.ts exit 1, 'Tests 1 failed | 116 passed (117)' at 'forwards context to findOne and insert'; restore: git checkout HEAD -- ABS_PATH, git diff HEAD 0 lines, hash-object equals HEAD blob 67bcd138cc046aaf053bbe61ad23956b70e80a52, marker 0, status clean; rebuild exit 0, preflight --absent exit 0 ('marker absent from all 24 built files'), both suites green again (19/19, 117/117). Direction observed: red, no reversal. GATES: dispatch-gates --commands --repo objectstack-ai/objectstack at the merged head derived 103 families (first derivation at b617ce886 was 80: the committed docs regen added 23 docs/prose families and a STALE TREE named 8 moved gate scripts — hence the merge); all 103 run, --ran reconciles '103 derived, 103 run, 0 UNRUN' at 68c74383f and again at d11785752; 101 exit 0 incl. check:nul-bytes, check:empty-changeset, check:adr-0087-registration, check:changeset-no-major, check:pm-widening-tells, check:engine-double-contract, check:corpus-claim-drift, check:doc-anchors, spec check:api-surface / check:authorable-surface / check:docs / check:liveness / check:skill-examples (the last only after client + client-react were built; its first run was a build-prerequisite refusal). After the docs-only commit the 25 docs-reading families were re-run on that exact tree: 21 exit 0 immediately, 4 answered exit 3 because the re-created worktree had no dist (check:doc-formula-expressions, check:doc-security-posture, check:docs, check:docs-transcript-drift) — spec + lint rebuilt under the lock (VERDICT command-exit 0 · held 159s) and all 4 then exit 0 before the commit was made. NOT MEASURED, declared to CI: @objectstack/client full pnpm test and its check:test-typecheck (unit tests import @objectstack/runtime / objectql / plugin-hono-server unaliased, outside the built closure; the client change is a type-only widening); pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt (both PREREQUISITE NOT MET, exit 3, 57 packages without dist); check:react-declaration-parity (EXTERNAL_INPUT_REQUIRED, as always). check-clause2-carriers --pair 16823: exit 4 before the card label (C1 split), exit 0 after. PR body read back twice: create stored byte-identical minus one trailing newline; the PATCH (sent without a footer) stored byte-identical with the platform's bare footer block appended; 0 angle brackets, no HTML comment. CI status at report time: in_progress (not waited on).", "mcp_calls": "0 — every GitHub read and write went over repo-scoped REST (issue + comments + PR #15395 reads; branch-push write probe; POST pulls draft=true; POST issues/16823/labels; POST issues/15703/labels; PATCH pulls/16823; GET read-backs after each write; POST issues/15703/comments for this report). No search_issues; dup check not needed (this is a ruled card, no new issue filed).", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: after this PR the clone 201 body carries droppedFields but the route sets no X-ObjectStack-Dropped-Fields header, while POST /data/:object and PATCH /data/:object/:id set both (bulk routes set neither) — incompleteness, not a defect class, and no contract text promises the clone route a header; deliberately not added (a wire change the ruling did not name); the schema describe and data-api.mdx state body-only. 承接者: this PR's contract reviewer if they want it; otherwise none.", "noted, not filed: .changeset/tidy-cups-smile.md (PR #15395, unreleased) still says cloneData 'forwards whole but reports nothing on the wire … adding that key is a spec change' — accurate history of that change, left untouched; this PR's changeset states the new fact. 承接者: whoever compiles the release notes.", "noted, not filed: docs/qa/platform-checklist/areas/records-forms.json 'Clone contract edges' item describes the body as {object, id, sourceId, record} (still true for a no-drop clone); its readonly-forge step could now also assert droppedFields. 承接者: the next checklist-author sweep.", "noted, not filed: content/docs/kernel/contracts/data-engine.mdx layering callout's hedge 'wherever their contract declares one' is now vacuous (all six create faces declare it) — still true, polish only. 承接者: none.", "noted, not filed: content/docs/releases/implementation-status.mdx and content/docs/releases/v17.mdx (release-owned, read-only) are NOT falsified by this change — implementation-status lists the clone route as implemented, v17's write-observability note claims droppedFields for PATCH/POST /data, bulk, batch and the SDK and never claimed or denied the clone; no docs-only card needed.", "noted, not filed: dispatch-gates STALE TREE at the final head names scripts/check-regen-pending.mjs and scripts/git-merge-regen.mjs (2 upstream commits after the merge) — merge-driver/pre-commit tooling, not a family whose verdict my paths move; CI runs the current copies. 承接者: none." ] }Generated by Claude Code
fix(metadata-protocol): a plain clone does not report the source's em…