Repository navigation
Description
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
立卡门: ① 产品缺陷,具名落点 + 复现(reach: 公开入口 HTTP POST /api/v1/data/:object/:id/clone 实测一次错误结果,PR #29034 的开发在 main 135054eb 的真栈测试与 stock showcase 上各测一次,开发报告 6570533136)
业务理由: 用户在控制台克隆一条记录、什么都没改,控制台却提示「某些字段被丢弃」;集成方读 droppedFields 或 X-ObjectStack-Dropped-Fields 判断写入是否完整,会把每次克隆都当成丢了数据。不修,这个告警在克隆上失去意义。
动手的读者: domain:server 席,生产侧:packages/metadata-protocol/src/protocol.ts 的 cloneData(复制源行时带上空的只读列)或引擎插入时只读剥离对「键存在」的判断。⛔ 不在 applyDroppedFieldsHeader 过滤(复审裁定:头只镜像体,同一缝不立第二套策略)。
查重: clone droppedFields null readonly 4 命中、cloneData copies null readonly 4、clone phantom dropped field 4(均含已关);最近邻 #28933(头缺失,已关已修,本缺陷由其开发发现)、#14147(插入路径只读剥离只是协议边界守卫,已关)。
Follow-up of: #28933 (landing PR #29034; contract review on that PR, ③ "Dev finding (3)": separate card, fix site on the producer side)
What happens
A clone with no overrides of a row whose readonly column is empty (null) answers droppedFields naming that column, and since #29034 the X-ObjectStack-Dropped-Fields header repeats it. The caller never sent that field. The console announces every non-empty droppedFields, so the user sees a "fields dropped" notice on a plain clone.
Reproduction
- Boot the stock showcase.
- Pick a contact whose
lead_score(readonly) is empty. POST /api/v1/data/contact/:id/clonewith no body.- Expected: 201, no
droppedFields, no header. - Actual: 201,
droppedFieldsnameslead_score, and the header names it too.
Where
cloneData copies the source row, null readonly columns included, into the insert; engine.insert's readonly strip reports any present key whatever its value. Fix one of the two so that a key the caller did not supply and the clone carried as empty is not reported. Which one is the dev's reading; keep the strip's report for a readonly value a caller really sends.
Acceptance
- A plain clone of a row with an empty readonly column answers no
droppedFieldsand no header; a clone whose overrides set a readonly field still reports it. - Pinned by a real-stack test that reds on main. The fix(rest): every 2xx write answer whose body reports droppedFields also sends the X-ObjectStack-Dropped-Fields header #29034 test file's clone cases and the dogfood
toContainassertion inshowcase-static-readonly.dogfood.test.tsaccommodate today's behaviour and move with this fix.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
domain:server·area:api·pm:queue· 2026-11-02T09:57Z⛔ Not a claim.
droppedFields(rest: PATCH /data 对被静默剥离的写入字段无任何回传 — onFieldsDropped 通道未接线(follow-up #3407/#3413) #3431 contract; REST: clone and createMany report a readonly strip in the body's droppedFields but send no X-ObjectStack-Dropped-Fields header, unlike single create and update #28933 is a sibling).packages/rest, p2.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
Claim: PM loop round 1 · card thread · 2026-11-02T10:09Z
Session:
session_01ADHkmQxwoGb5fSnrFkpxmt(the seat); the card thread issession_01ADHkmQZyRQCzui7zekMYY7Account:
os-steve(the seat's linked user; the card's assignee)Branch:
claude/project-thread-v06ygi(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#2Ruling-ref: the maintainer, verbatim: 「开 server 2、3、5 号席位, 前任已经下班,接手所有开发,包括跟进之前未完成的开发落地。」 Card: none from the maintainer on this card; filed by this seat as a landing follow-up, graded p2 by triage.
File surface:
packages/metadata-protocol/src/protocol.ts(cloneDataonly, under the delta's route (a)); tests; the one dogfood fileshowcase-static-readonly.dogfood.test.ts; changeset. ⛔ No logic change inpackages/rest/src/error-response.ts, noui/**, nopackages/spec/**.Container & model:
M,mode:thread,model: opus(TIER_DEFAULT;--tiernames no path mandate: PM's call),effort: mediumClause-②: no (expected: a body and header lose a false entry; no request refused)
Metadata-protocol: none (expected)
Responsibility: the code is
cloneDatain@objectstack/metadata-protocol· the platform path is a plain clone announcing "fields dropped" for empty readonly columns the caller never sent · reached by every console clone and every integration that readsdroppedFieldsorX-ObjectStack-Dropped-FieldsThread-read: none
Premise: holds (measured by the dev of the parent PR; the card thread re-measures red first on current
origin/main)Serial constraints cleared: open PR #28731 edits
protocol.tsatprobeRecord, notcloneData(hunks read this act); the card thread keeps its hunks off it and mergesorigin/mainbefore pr_createResume-from: none — fresh from
origin/mainState:
pm:queue→pm:dispatched, assigned toos-steve, recorded by this claim.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQZyRQCzui7zekMYY7
os-dev-report
{ "issue": 29200, "status": "done", "branch": "claude/project-thread-v06ygi", "pr": "https://github.com/objectstack-ai/objectstack/pull/29290", "session": "session_01ADHkmQZyRQCzui7zekMYY7", "premise_still_valid": true, "summary": "Producer-side fix under route (a), narrowed to EMPTY values: cloneData (packages/metadata-protocol/src/protocol.ts) now leaves a readonly field whose copied source value is null/undefined out of the insert payload, so engine.insert's strip no longer reports a key nobody supplied; body droppedFields and the X-ObjectStack-Dropped-Fields header both lose the false entry. Stored row unchanged (the strip removed the key from a non-system caller anyway; applyFieldDefaults treats explicit null as omitted). Acceptance 3 measured: a readonly column that HOLDS a value is reported on main (body + header ro;reason=readonly) and still is after the fix; judged NOT the same defect because the #15703 ruling (option 1, record 5578819560) requires the clone to report a copied-and-stripped value, so (a) is not applied to valued columns. Overrides setting a readonly field are still reported (control). No change to packages/rest/src/error-response.ts, ui/** or packages/spec/**.", "tests": "Red first at main 45e9b2f1 (tests only, rest^... built): rest-dropped-fields-every-write-door.test.ts Tests 1 failed | 17 passed (18), the new plain-clone-of-empty case (expected ... to not have property droppedFields); valued and overrides controls green on main. Fix f825d6bd: 18/18. Ablation via scripts/ablation-replace.mjs (anchor 1->0, blob 3ff88bdc6abc->15c970b77f49, line made dead by name === '__ABLATION_29200__'), ablation-dist-preflight marker in dist/index.js + index.cjs: rest pin 1 failed | 17 passed; dogfood showcase-static-readonly 1 failed | 8 passed (body named lead_score). Restore legs: blob == HEAD, git diff HEAD empty, rebuilt, preflight --absent exit 0, 18/18 and 9/9. metadata-protocol test+typecheck: 300 passed/4 skipped files, 22100 tests passed, VERDICT command-exit 0. rest test+typecheck: 367/367 files, 6966 passed/332 skipped, VERDICT command-exit 0. After merging origin/main (85ebba60): install, rest^... + dogfood^... closure build, spec check:generated VERDICT command-exit 0; rest pin 18/18, dogfood file 9/9.", "gates": "At 85ebba60: dispatch-gates --commands derived 65; all 65 recorded exit 0 (check:dual-build-cjs-loads first exit 3 PREREQUISITE NOT MET, re-run exit 0 after turbo build ./packages/**). dispatch-gates --ran: 65 run, 0 NOT-MEASURED, 0 UNRUN (DERIVED zero). Ran list: /mnt/project-files/server-seat2/29200-gates-ran.txt. Lint: no package lint script; root eslint --no-inline-config on the 3 touched files exit 0. CI check-runs on #29290: not awaited (in_progress at report time).", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm; the one touched dogfood file was run: Tests 9 passed (9) at 85ebba60", "line_budget": "+78 / -6 over 4 files (protocol.ts +12/-0, rest test +38/-3, dogfood +13/-3, changeset +15/-0)", "files_changed": [ "packages/metadata-protocol/src/protocol.ts (cloneData only)", "packages/rest/src/rest-dropped-fields-every-write-door.test.ts", "packages/qa/dogfood/test/showcase-static-readonly.dogfood.test.ts", ".changeset/29200-clone-empty-readonly-not-dropped.md" ], "deviations": [ "Route (a) applied to EMPTY readonly values only, not 'do not carry a readonly column at all' as the delta words it: dropping valued readonly columns would silence the #15703-ruled report (acceptance 3 decision, stated in the PR body).", "Forced test edits: dogfood clone-with-overrides header toContain -> toBe('lead_score;reason=readonly') plus comment; rest test comment above CLONE_OBJECT rewritten and one seed row src_empty added (no assertion moved). Both named in the PR body." ], "mcp_calls": "3 — issue_read x3 (29200 get, 29200 get_comments, 28933 get_comments); no GitHub write tool. Other reads were gh api REST GETs (29200 comments, 29034 review comment 6570720694, #15703 and its comments, PR 29034 body).", "api_writes": "2 — fleet-write pr_create (#29290, run 41434211047, 9352 bytes sent = stored); this os-dev-report comment on #29200 via post-stamped. git push is not a REST write.", "open_questions": [], "out_of_scope_findings": [] }Stamped 2026-11-02T11:09Z.
Generated by Claude Code