Repository navigation
docs(adr-0120): the per-object batch upsert by id joins the create doors; pin the edit-without-read class - #28797
Conversation
…oors; pin the edit-without-read class The ADR-0120 2026-11-01 amendment gains a dated note: `POST /data/:object/batch` with `operation: 'upsert'` is a create door for a row naming an id the caller cannot read, its collision and refusal answers, and the driver-memory persisted-duplicate residual. The dogfood pin adds the edit-without-read principal class (with and without create) on an open-write object to its HIDDEN table, and the fork comment names the old fork's third outcome. Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 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 c784678cc703b7692ea30d6b2a786390ee608f78 && git checkout c784678cc703b7692ea30d6b2a786390ee608f78
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0aaad18fc052326bc78378ead5f7e789f64e072d 1e7ced88b8d69d66d21991e95e82bdef048fa153 && git checkout -B drift-repro 0aaad18fc052326bc78378ead5f7e789f64e072d && git merge --no-ff 1e7ced88b8d69d66d21991e95e82bdef048fa153
node scripts/docs-audit/affected-docs.mjs --json 0aaad18fc052326bc78378ead5f7e789f64e072d |
|
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk Contract reviewServed-tier: Read-only shape held: the diff at this head ( Disclosure check (ruling 2026-10-29): the ADR note, the create-door clause, the fork-comment clause, the test header, the test titles and the PR body were read for any sentence describing how an earlier path could be abused. None found. The ADR note states contracts (match key, arm, answer) and names a commit; the test header names the principal class, the object and the grants each holds, and why the object was chosen (the floor's relief on ① Derived judgmentsNo runtime code moves: the accept set and every public face of
② Semver level
③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Must-fixes: none. Should-fixes: (1) ADR note, driver-memory bullet — name the public option: "its opt-in Reading time 2026-11-01T20:40Z. |
…dable row enters Two wording corrections to the batch-upsert note from the contract review: the driver-memory bullet names the driver's public `persistence` option (a custom adapter included) instead of a private field, and the facts-carry sentence says the create arm is one no readable row enters. Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
|
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk Contract reviewServed-tier: Delta record by the same reviewer, on the full-review record 6563439682 (head ① Derived judgmentsThe delta is two prose edits inside the 2026-11-01 note of the ADR, both applying should-fixes 1 and 2 of 6563439682; no code, test, comment, link or Status-line change; no accept set or public face moves.
② Semver levelUnchanged: ③ Boundary flags
④ Design judgment
Implemented-by: VERDICT: PASS Must-fixes: none. Should-fixes: none new; 6563439682's (3) remains for the PM seat at landing. Tier H: the maintainer's approval of the governed text is still owed; this record is the at-tier PASS on the current head. Reading time 2026-11-01T20:50Z. |
|
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk ACCEPT → head 维护者速读
席位意见:收。本 PR 补齐 #28721 达档复核 6561724481 的 should-fix 2–4。 席位核对
Generated by Claude Code |
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk
维护者速读
id的POST /data/:object/batchupsert,对调用方读不到的行就是一扇新建门——已被占用但读不到的id只得到撞键(409 UNIQUE_VIOLATION)而不是匹配,没有新建权则得到新建的拒绝;同时记下 driver-memory 开启持久化时重复行会写入开发库这一已接受的残余。dogfood 用例的HIDDEN表新增「有编辑、无读取」这一类主体(无新建权、有新建权各一);protocol-batch-data.ts的分叉注释补一句。零运行时改动。docs/adr/0120-unique-scope-vocabulary-and-null-safe-tenant-uniqueness.md(风险 hunk:修订注记末尾新增的日期注记,以及清单第一条的补句,核对它与 2026-11-01 修订的裁决范围一致)→packages/qa/dogfood/test/upsert-by-id-caller-read.dogfood.test.ts(HIDDEN表新增的两行,及「不存在的 id」用例里无新建权的分支)→packages/metadata-protocol/src/protocol-batch-data.ts(分叉注释第一条理由的补句)。pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=1 test/upsert-by-id-caller-read.dogfood.test.ts,预期 1 个文件、36 个用例全绿(原有 28 个加新增 8 个)。docs/adr/**(Tier H),请审阅新增注记的措辞后亲手合并或入队。What changes
A docs + pin + comment PR. No runtime change.
ADR-0120, the 2026-11-01 amendment. A dated note is appended to the amendment in the record's own amendment form, and the amendment's first fact (the create-door list) gains one clause pointing at it. The note records that
POST /data/:object/batchwithoperation: 'upsert'is a create door for a row naming anidthe caller cannot read: the door's match key is theid, read as the caller; a readable row takes the update arm, an unreadable one takes the create arm with the create's answer. Contracts it states:idthe caller cannot read meets the collision, never a match:409 UNIQUE_VIOLATIONon a store that enforces the primary key, the same row error a batch create naming thatidcarries, identical for a row hidden inside the caller's organization and one held by another; importupsertmatches on the mapping's declared key fields, this door on theiditself;idand for one that names no row;driver-memorythe create arm lands a second row with thatid, and with that driver's persistence configured the duplicate is written to the development store; recorded as the same accepted residual (the development and test driver declares no primary key).Provenance in the note: ruling record
5985885287, applied to this door by commit7b62f0a77. The status line is unchanged; its existing 2026-11-01 entry already points at the amendment this note extends.Dogfood pin,
upsert-by-id-caller-read.dogfood.test.ts(stock showcase): theHIDDENtable gains the edit-without-read principal class onshowcase_semantic_zoo(public_read_write, so the platform'supdateownership floor does not bind it, and no baseline set grants anything on it), once without and once withallowCreate. Each table row now carries the create's expected answer (403 PERMISSION_DENIEDwithout the create grant,409 UNIQUE_VIOLATIONwith it); the held-id case asserts the upsert's body equals a batch create naming that id and the stored row is byte-identical afterwards; the unknown-id case answers as the create does (created with the grant; without it, the create's refusal, nothing stored, and the same answer a held id gets). The guard case reads each new principal's grants through/security/explain(object_crudverdictgrantson update,denieson read), its by-id read (403) and its create (201/403).Fork comment,
packages/metadata-protocol/src/protocol-batch-data.ts: one clause in the first reason of the reversal paragraph naming the earlier fork's third outcome, stated as the rule. Comment only.Tests
All at head
751c15af6, exit codes captured before any pipe.--maxWorkers=1:Test Files 1 passed (1),Tests 36 passed (36), lockVERDICT command-exit 0. 28 before plus 8 new (two classes, atomic and not, held id and unknown id).@objectstack/metadata-protocolunit tests that drivebatchData(9 files:protocol.upsert-existence,protocol.batch-atomic,protocol.batch-row-driver-text,protocol.batch-row-http-status,protocol.batch-row-sandbox-refusal,protocol.bulk-record-not-found,protocol.readonly-insert,protocol.write-response-internal-fields.tripwire,protocol.bracketed-refusal-opener-absence), under the verify lock:Test Files 9 passed (9),Tests 150 passed (150), lockVERDICT command-exit 0.tsc --noEmit --listFilesover the dogfood package's owntsconfig.json(what itstypecheckscript runs), under the verify lock: exit 0, 0 errors; the edited test is in the program (2261 files listed, 435 of them underpackages/qa/dogfood/test/).One-shot ablation of the new pin (reverted fork, measured, restored with proof)
Driven by
scripts/ablation-replace.mjsin wrap mode (trap-armed restore, absolute path), the whole sequence under the verify lock (VERDICT command-exit 0).1 -> 0, replacement0 -> 1, blobf26864308to8037e64df.pnpm --filter @objectstack/metadata-protocol buildexit 0.ablation-dist-preflightexit 0: marker present in 2 built files.Tests 16 failed | 20 passed (36). The six new cases are among them: the held-id case for both new classes, atomic and not (4), and the unknown-id case of the no-create class, atomic and not (2). The other ten are the existing hidden-row cases. The with-create class's unknown-id case stays green, as it should: an unknown id takes the create arm under either fork.f26864308,git diff HEADempty for the path, whole-treegit status --porcelainempty. Rebuilt (exit 0);ablation-dist-preflight --absentexit 0: marker absent from all 24 built files, tree clean.Nothing of the ablation is in the tree or the branch.
Acceptance notes
iditself here. That is what makes this door's answer to a held-but-unreadableidthe collision rather than a match, and the note says it that way so the sentence holds for both doors.test:repo(this diff edits none of its tests and the derivation names none of it;Test Coreruns it), and the repo-wide scans CI owns.pnpm check:dual-build-cjs-loadsfirst answeredPREREQUISITE NOT MET(exit 3: seven packages outside the dogfood closure had nodist/). Built those seven with turbo (all cache hits) and re-ran it: exit 0. Both readings are recorded here; the list below carries the measured one.docs/adr/**, the private@objectstack/dogfoodpackage, and one code comment inpackages/metadata-protocol/src. Measured: the comment's text has 0 hits in the builtdist/(the package'sfiles[]shipsdist), against the positive controlcallerMayReadObjectwith 2 hits.skip-changesetis declared at creation;pnpm check:empty-changesetexit 0.Verification
Head
1e7ced88b. It adds one commit on top of751c15af6, from contract review 6563439682: two wording corrections in the ADR-0120 note, prose only and in that one file. The driver-memory bullet now names the driver's publicpersistenceoption ("a custom adapter included") in place of a private field name. The facts-carry sentence now reads "holds for that arm, which no readable row enters". Re-run at1e7ced88b, exit codes captured before any pipe:node scripts/check-adr-links.mjs:: exit 0 (967 relative link destinations resolve)pnpm check:adr-index:: exit 0 (docs/adr/README.md is current)pnpm check:doc-anchors:: exit 0 (857 internal fragment links resolve)pnpm check:doc-authoring:: exit 0The dogfood run, the unit tests, the type check, the ablation and the derived gate families below were taken at
751c15af6. The commit after it touches neither test file nor any code path.At
751c15af6:node scripts/check-adr-links.mjs:: exit 0 (967 relative link destinations resolve)pnpm check:adr-index:: exit 0 (docs/adr/README.md is current)pnpm check:doc-anchors:: exit 0 (857 internal fragment links resolve)pnpm check:doc-authoring:: exit 0pnpm check:nul-bytes:: exit 0, and a control-byte scan of the three changed files: no hitsnode scripts/pm/dispatch-gates.mjs --ran:: exit 0,63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN, every line carrying its exit codeDerived gate families (dispatch-gates --commands, reconciled with --ran)
Each line is
command :: exit code, captured before any pipe, at751c15af6.node scripts/check-adr-links.mjs:: exit 0node scripts/check-adr-links.mjs --self-test:: exit 0node scripts/check-closing-keyword-parity.mjs:: exit 0node scripts/check-closing-keyword-parity.mjs --self-test:: exit 0node scripts/check-comment-mask-adoption.mjs:: exit 0node scripts/check-comment-mask-adoption.mjs --self-test:: exit 0node scripts/check-comment-mask-corpus.mjs:: exit 0node scripts/check-dts-emitted.mjs --self-test:: exit 0node scripts/check-issue-citations.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs:: exit 0node scripts/check-plugin-teardown-shape.mjs --self-test:: exit 0node scripts/ci/assert-turbo-remote-cache.mjs --self-test:: exit 0node scripts/ci/plan-test-core-shards.mjs --self-test:: exit 0node scripts/ci/prune-turbo-cache.mjs --self-test:: exit 0node scripts/docs-audit/check-affected-docs.mjs:: exit 0node scripts/docs-audit/check-drift-comment.mjs:: exit 0pnpm --filter @objectstack/lint run check:doc-formula-expressions:: exit 0pnpm --filter @objectstack/spec run check:duration-unit-keys:: exit 0pnpm --filter @objectstack/spec run check:empty-state:: exit 0pnpm --filter @objectstack/spec run check:liveness:: exit 0pnpm --filter @objectstack/spec run check:strictness-ledger:: exit 0pnpm --filter @objectstack/spec run check:variant-docs:: exit 0pnpm check:adr-anchors:: exit 0pnpm check:adr-index:: exit 0pnpm check:adr-symbol-anchors:: exit 0pnpm check:ci-filter-parity:: exit 0pnpm check:closed-ceremony-text:: exit 0pnpm check:cross-package-test-inputs:: exit 0pnpm check:dispatcher-error-vocabulary:: exit 0pnpm check:doc-authoring:: exit 0pnpm check:driver-memory-census:: exit 0pnpm check:dts-closure:: exit 0pnpm check:dual-build-cjs-loads:: exit 0pnpm check:durability-log-level:: exit 0pnpm check:engine-double-contract:: exit 0pnpm check:error-status-conformance:: exit 0pnpm check:gitlink-declared:: exit 0pnpm check:issue-citations:: exit 0pnpm check:keyed-text-bounds:: exit 0pnpm check:lean-entry-closure:: exit 0pnpm check:logger-receiver-detach:: exit 0pnpm check:nul-bytes:: exit 0pnpm check:objectql-double-limit:: exit 0pnpm check:org-identifier:: exit 0pnpm check:page-declaration-shape:: exit 0pnpm check:platform-object-tenancy-census:: exit 0pnpm check:pm-governed-merges:: exit 0pnpm check:pm-prior-rulings:: exit 0pnpm check:published-files:: exit 0pnpm check:query-options-erasure:: exit 0pnpm check:refd-timer-probe:: exit 0pnpm check:registry-log-declared:: exit 0pnpm check:rest-log-spy-declared:: exit 0pnpm check:slot-lookup:: exit 0pnpm check:sourcemap-no-sources-content:: exit 0pnpm check:system-context-census:: exit 0pnpm check:test-source-alias:: exit 0pnpm check:tier-file-adoption:: exit 0pnpm check:type-check-coverage:: exit 0pnpm check:type-check-debt:: exit 0pnpm check:undeclared-dep-imports:: exit 0pnpm check:watch-hint-literal:: exit 0pnpm check:where-matcher:: exit 0pnpm check:doc-anchors:: exit 0pnpm check:empty-changeset:: exit 0Generated by Claude Code