Repository navigation
fix(rest)!: clone refuses a body that carries overrides beside other top-level keys - #27934
Conversation
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czA6HpNWK4YvhLTw3x
…op-level keys Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4czA6HpNWK4YvhLTw3x
|
os-dev-report · #27669 · session_016WV4czA6HpNWK4YvhLTw3x {
"card": 27669,
"pr": 27934,
"head": "9d859e3a",
"branch": "claude/project-thread-1eeldu",
"session": "session_016WV4czA6HpNWK4YvhLTw3x",
"outcome": "done",
"premise": "holds — pinned red on main b13a1ed3: 3 cases answered 201 where 400 was expected",
"files_changed": [
"packages/rest/src/data-action-routes.ts",
"packages/rest/src/clone-overrides-refusal.test.ts",
".changeset/27669-clone-overrides-stray-keys.md"
],
"line_budget": "+68 / -1 vs merge base, 3 files; human-merge threshold 3000: under",
"tests": "clone-overrides-refusal.test.ts 19/19 at e2d1eb18; ablation (strays.length > 0 -> < 0) 3 failed/16 passed, restored blob == HEAD; @objectstack/rest test at 9d859e3a: 348/348 files, 6754 passed, 332 skipped; typecheck exit 0",
"gates": "59 derived at e2d1eb18: 58 exit 0, pnpm check:dual-build-cjs-loads exit 3 (NOT MEASURED, needs whole-tree dist); --ran reconciliation 59 accounted; pnpm lint exit 0",
"dogfood_battery": "9d859e3a · exit 0 (no os-verify-lock; single worktree) · Test Files 357/357 (3 skipped) · Tests 2875 passed, 20 skipped · no failures. Earlier run at e2d1eb18 before the merge: 4 failures, 3 = stale hook-abort pins of #27806 (fixed on main by #27878), 1 = platform-app-object-entry-views sys_exchange_rate; all pass at 9d859e3a",
"deviations": [
"Skills-docs: none — content/docs/api/data-api.mdx describes only the two accepted shapes, which are unchanged (same reading as #27659)",
"dual-build-cjs-loads not measured locally"
],
"declarations": "Clause-②: no (narrowing); Metadata-protocol: none; BREAKING banner + adr-0087 not-required marker in the changeset; Fixes #27669",
"mcp_calls": "5 — reads only: issue_read x3 (get, get_comments, get), pull_request_read x1, search_issues x1; no write tool",
"api_writes": "3 via relay — pr_create, pr_request_reviewers, comment",
"open_questions": [],
"out_of_scope_findings": []
}Generated by Claude Code |
📓 Docs Drift CheckThis PR changes 1 package(s): 17 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 5 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 19 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 19711f82afbe218484c8c380c7ecf0c7f59323f3 && git checkout 19711f82afbe218484c8c380c7ecf0c7f59323f3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ca54eacaa13befaea8142274a4db20b10fce2ab8 9d859e3afe40b913d629631afa3a1ec39dab6dbd && git checkout -B drift-repro ca54eacaa13befaea8142274a4db20b10fce2ab8 && git merge --no-ff 9d859e3afe40b913d629631afa3a1ec39dab6dbd
node scripts/docs-audit/affected-docs.mjs --json ca54eacaa13befaea8142274a4db20b10fce2ab8
|
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat runs the |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentNarrowing judged against the published spec on Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Seat ACCEPT · PR #27934 · head
|
Requested by Elon · project thread
Fixes #27669
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no skill teaches the clone door's body shapes, and
content/docs/api/data-api.mdxdescribes only the two accepted shapes, which are unchanged维护者速读
POST /data/OBJ/ID/clone的请求体若同时带overrides和其它顶层键(例如{"overrides":{"name":"Copy"},"status":"draft"}),现在返回 400,不再只读overrides、悄悄丢掉status并返回 201。overridesthat is not an object (a string) and answers 201 with the overrides silently ignored #27489 修过的「静默丢弃」同一类。{"overrides":{...}}、不带overrides键的裸字段表、{}、无 body 行为不变。回滚即删掉克隆处理器里的这一段守卫。packages/rest/src/data-action-routes.ts(克隆处理器的strays守卫)、packages/rest/src/clone-overrides-refusal.test.ts(新的[#27669]一组)。pnpm --filter @objectstack/rest exec vitest run src/clone-overrides-refusal.test.ts,预期 19 条全部通过。What changes
packages/rest/src/data-action-routes.ts, when the body is a field map that carriesoverrides(the nested shape), any other top-level key is a stray. One or more strays answer400through the same nested-envelope writer fix(rest)!: export refuses an unserved format and clone refuses a non-object overrides #27659 uses (sendEnvelopeError,error.code: 'VALIDATION_ERROR'), beforecloneDatais called. The message names each stray key, names both accepted shapes ({ "overrides": {...} }or a bare field map), and tells a caller whose record has a real field namedoverridesto nest it:{ "overrides": { "overrides": ... } }. That is the triage ruling; no new shape is added.overridesthat is not an object (a string) and answers 201 with the overrides silently ignored #27489 non-object check, so{"overrides":"x","status":"draft"}is refused for its stray key first; both are400 VALIDATION_ERRORbeforecloneData.overrideskey,{}, a barenullbody and no body clone exactly as before. The SDK'sdata.clonesends only the nested shape.@objectstack/restpatch, BREAKING banner, the ADR-0087 dispositionnot-required (no-migration-prescription)and the remedy, modelled on fix(rest)!: export refuses an unserved format and clone refuses a non-object overrides #27659's.Tests
packages/rest/src/clone-overrides-refusal.test.ts(the #27659 harness, realHonoHttpServer) gains a[#27669]block:overrideswith a stray key. Each asserts400,VALIDATION_ERROR, every stray key named, both shapes named, the nested-overridesprescription, and thatcloneDatawas never called.{"overrides":{"overrides":"x","name":"Copy"}}answers201andcloneDatareceives{ overrides: 'x', name: 'Copy' }.overridesthat is not an object (a string) and answers 201 with the overrides silently ignored #27489 cases are untouched and green.Verification
Pin on main, then the fix, then an ablation
[#27669]block was committed alone on top ofmainb13a1ed3(commitb276331d).vitest run src/clone-overrides-refusal.test.ts: 3 failed, 16 passed, eachexpected 201 to be 400.e2d1eb18): 19 passed (19).node scripts/ablation-replace.mjs, anchorif (strays.length > 0) {replaced byif (strays.length < 0) {: anchor 1 → 0, blob327a925b243e→71a31169d8a4; result 3 failed, 16 passed. Restored: blob after restore327a925b243e== HEAD,git diff HEADempty. The test imports./rest-serverfromsrc/, so nodist/leg applies.Package tests and typecheck: @objectstack/rest green at the final head
9d859e3a(after one merge oforigin/main, install and closure rebuild):pnpm --filter @objectstack/rest testexit 0, Test Files 348 passed (348), Tests 6754 passed and 332 skipped.pnpm --filter @objectstack/rest typecheckate2d1eb18: exit 0,check:test-typecheckOK with 0 files / 0 errors in the debt ledger.Derived gate families: 59 derived, 58 exit 0, 1 NOT MEASURED; pnpm lint exit 0
node scripts/pm/dispatch-gates.mjs --commandsderived 59 commands ate2d1eb18, each run with its exit code captured before any pipe. 58 exited 0 (check-plugin-teardown-shape --self-testafter fetching its pinned commit into the shallow clone).pnpm check:dual-build-cjs-loadsexited 3, PREREQUISITE NOT MET (it needs every package'sdist/): NOT MEASURED locally; CI'sBuild Corejob runs it.--ranreconciliation: 59 derived famil(ies) accounted for.pnpm lint: exit 0.origin/mainchanged none of this PR's three files' other regions;data-action-routes.tsauto-merged.Dogfood battery (owed by the narrowing arm): green at the final head
9d859e3a:pnpm turbo run build --filter='@objectstack/dogfood^...'thenpnpm --filter @objectstack/dogfood test, exit 0. Test Files 357 passed, 3 skipped (360). Tests 2875 passed, 20 skipped (2895). Not run underos-verify-lock(one worktree on this box).e2d1eb18(before the merge) had 4 failures, none on the clone door: three were the stale hook-abort pins tracked as main red (Dogfood Regression Gate 1/3) since 21c4bdd8 (#27667): two dogfood pins still expect a code-less hook refusal after HOOK_ABORTED landed #27806 (fixed onmainby test(dogfood): flip the three hook-abort pins to the ruled HOOK_ABORTED wire #27878), and one wasplatform-app-object-entry-views.test.tsnamingsys_exchange_rate. All four pass at9d859e3a.Reviewer notes
POST /data/:object/import(synchronous import) on an unregistered object answers 200 with a per-rowOBJECT_NOT_FOUND, wherePOST /data/:objectanswers 404 #27728/rest: pin the async import job door's disabled-package refusal, and decide whether it should resolve a legacyns__shortname as the data door does #27729 work edit other regions of the same file; this diff stays inside the clone handler.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4czA6HpNWK4YvhLTw3x