Repository navigation
fix(rest)!: both import doors answer for the object as the data door does (#27728, #27729) - #27897
Conversation
…does The synchronous import door (POST /data/:object/import) now asks the same up-front existence read as the async job door, right after the API-exposure gate and before the body is parsed: an unregistered object answers the data door's 404 OBJECT_NOT_FOUND and an object of a disabled package its 404 OBJECT_PACKAGE_DISABLED, instead of a 200 with the refusal repeated in every row. The existence read and the exposure gate both take the item the by-name read serves, under whichever name it carries, so a legacy ns__short object addressed by its short name is the object behind that name at every door, as registry.getObject resolves it for the data door. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxENxbA8ZpRgwafjvs
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract-review-claim: The lane seat runs the |
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
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 4371c1709770486de3f9a8f2eb11fe41979903c8 && git checkout 4371c1709770486de3f9a8f2eb11fe41979903c8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e65536193a5cd493d1cbf6ad7ac78ae34909e37a 274ccc20b44dcf2009f8edcc2827932b859af9a4 && git checkout -B drift-repro e65536193a5cd493d1cbf6ad7ac78ae34909e37a && git merge --no-ff 274ccc20b44dcf2009f8edcc2827932b859af9a4
node scripts/docs-audit/affected-docs.mjs --json e65536193a5cd493d1cbf6ad7ac78ae34909e37a
|
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: Read at 2026-10-30 against the net diff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentpackages/rest/src/data-action-routes.ts — holds against the code it cites and the scenario it is accepted against. The call sits where the job door's does and where the data door's existence refusal sits relative to the gate; dry run and an unparseable CSV body are refused before parsing (pinned). The helper's docblock now states the alias rule and cites packages/rest/src/meta-item-read-gate.ts — holds against ADR-0049's rule (a non-enforcing packages/rest/src/import-door-object-existence.test.ts — the pins are the review checklist's shape (status, code, whole body against the reference door, engine-counted rows); the short-name premise is asserted before it is used; .changeset/27728-27729-import-doors-object-existence.md — frontmatter, both declaration lines, one ADR-0087 marker, BREAKING banner, Before / Now / One read / Who sees it / Remedy; every sentence checked against the diff is true. Should-fix (③): name the gate-side class under "Who sees it" and the remedy. No must-fix. The narrowing takes away only answers that were wrong (a 200 that imported nothing; a short-name pass-through of a declared Implemented-by: VERDICT: PASS |
Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WV4cxENxbA8ZpRgwafjvs
|
domain:server#3 · os-elon-musk · session_016WV4cxENxbA8ZpRgwafjvs Changeset should-fix pushed (text only): head Dogfood battery reds match main's signatures (battery at
None of the three reads an import door, the exposure gate or a Generated by Claude Code |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Contract reviewServed-tier: Delta read at 2026-10-30 of ① Derived judgments
② Semver levelUnchanged and still correct: frontmatter ③ Boundary flags
④ Design judgment.changeset/27728-27729-import-doors-object-existence.md — the one should-fix is closed in exactly the four places the record named, with the remedy sharpened rather than copied; declarations intact and line-initial; rule-level prose throughout. No must-fix, no residual should-fix. Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia Seat ACCEPT · PR #27897 · head
|
Requested by Elon · project thread
Fixes #27728
Fixes #27729
domain:server#3 · os-elon-musk · session_016WV4cxENxbA8ZpRgwafjvs
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no hand-written docs page or published skill states what either import door answers for an unregistered object, a disabled package's object, or a short name (
git grepofcontent/docsoutsidereferences//releases/and ofskills/for the import routes withOBJECT_NOT_FOUND,OBJECT_PACKAGE_DISABLED, 404 or "not found": 0 hits;wire-format.mdxnames the sync route only as the bulk door,import-mappings.mdxdocuments per-row outcomes).维护者速读
POST /data/OBJ/import现在先问对象在不在:没注册的对象回 404OBJECT_NOT_FOUND,停用包里的对象回 404OBJECT_PACKAGE_DISABLED,和数据门POST /data/OBJ一字不差(原来回 200,错误藏在每行结果里)。两个导入门与 API 暴露闸都改用同一种读法:旧式ns__short对象用短名访问时,认的就是数据门认的那个对象。POST /data/:object/import(synchronous import) on an unregistered object answers 200 with a per-rowOBJECT_NOT_FOUND, wherePOST /data/:objectanswers 404 #27728)与 6509225810(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):只看状态码的导入客户端,拼错对象名时会以为成功;作业门对短名比数据门更严;暴露闸对短名放行,apiEnabled: false的ns__short对象用短名能被读写(实测)。__的旧对象名,已发运的对象都不带__。回滚即删data-action-routes.ts里同步门那一行调用,再把两处item.name === name比较加回去。packages/rest/src/data-action-routes.ts(同步门新增的assertImportObjectExists调用与函数里去掉的名字比较);packages/rest/src/meta-item-read-gate.ts的loadObjectItem;新测试packages/rest/src/import-door-object-existence.test.ts。pnpm --filter @objectstack/rest exec vitest run src/import-door-object-existence.test.ts,预期 11/11 通过。What changes
POST /data/:object/import(synchronous import) on an unregistered object answers 200 with a per-rowOBJECT_NOT_FOUND, wherePOST /data/:objectanswers 404 #27728, the synchronous import door.POST /data/:object/importnow callsassertImportObjectExistsright after the API-exposure gate and before the control opt-out gate and the body parse, the same place the async job door asks it (fix(rest)!: an async import job for an unregistered object is refused with 404 OBJECT_NOT_FOUND before any job is created #27711). An unregistered object answers the data door's 404OBJECT_NOT_FOUND, byte for byte; an object of a disabled package answers its 404OBJECT_PACKAGE_DISABLED. Nothing is parsed or written, dry run included. Placed after the exposure gate, so a hidden object keeps its 404 and the door is no existence oracle.ns__shortname as the data door does #27729 item 1, pinned. The async job door's disabled-package refusal (propagated since fix(rest)!: an async import job for an unregistered object is refused with 404 OBJECT_NOT_FOUND before any job is created #27711, read from the code) is now measured: status and bodytoEqualthe data door's answer for the same name, zerosys_import_jobrows, zero object rows. It was already green on the base, as fix(rest)!: an async import job for an unregistered object is refused with 404 OBJECT_NOT_FOUND before any job is created #27711 said.ns__shortname as the data door does #27729 item 2, one read.assertImportObjectExistsand the exposure gate'sloadObjectItem(meta-item-read-gate.ts) both droppeditem.name === name: whatever item the by-name read serves counts. That read (getMetaItemForExecution, elsegetMetaItem) resolves a legacyns__shortobject from its short name exactly as the data door'sregistry.getObjectdoes (measured:widget→exl__widgeton both). Results:OBJECT_NOT_FOUND;enable. Before, a short name was "no object here" to the gate and passed through, so anns__shortobject withenable.apiEnabled: falsewas served under its short name (measured on the base:POST /data/vault201, sync import 200, whilePOST /data/exl__vaultanswered 404OBJECT_API_DISABLED). Now all three doors answer 404OBJECT_API_DISABLEDfor the short name too.@objectstack/restpatch, BREAKING note, ADR-0087not-required (no-migration-prescription)marker.Tests
packages/rest/src/import-door-object-existence.test.ts(new), realRestServerroutes on a realObjectQLengine +ObjectStackProtocolImplementation, SQLite:memory:, realsys_import_job, realdisablePackage:POST /data/:object/import(synchronous import) on an unregistered object answers 200 with a per-rowOBJECT_NOT_FOUND, wherePOST /data/:objectanswers 404 #27728: unregistered object → sync door 404 equal to the data door's body; dry run and an unparseable CSV body get the same answer; control: a registered object still imports (200, 1 row).ns__shortname as the data door does #27729 item 1: disabled package → data door 404OBJECT_PACKAGE_DISABLED; sync and job doors equal it (status + body), zero job rows, zero object rows.ns__shortname as the data door does #27729 item 2: premise (registry.getObject('widget')→exl__widget, data door 201); job door 201 + one job row for the short name; sync door imports under it; an unregistered short name is stillOBJECT_NOT_FOUNDat both import doors, zero job rows;apiEnabled: falseobject hidden under its short name at the data, sync and job doors, zero rows.Ablation (both source files reverted to
main, test kept): 6 of 11 red (the two #27728 cases, the sync disabled-package case, and three short-name cases); the 5 that stay green are the controls, the data-door reference reading, and the job-door disabled-package pin (already true on the base). Restored: 11/11.Acceptance notes
POST /batchper-op exposure gate (batch-routes.ts) keys the object LIST byname, so it keeps the old short-name pass-through. This harness's runtime answers 501 for a transactional batch, so it could not be measured here. Noted for the seat; not filed.Verification
Head
c0b19922, basemain30f8fdd3(#27795 and #27711 already in the base).pnpm --filter @objectstack/rest exec vitest run(whole package): Test Files 353 passed (353), Tests 7031 passed, 350 skipped.pnpm --filter @objectstack/rest run typecheck: exit 0.pnpm lint: exit 0.pnpm check:dual-build-cjs-loadsexits 3 (needs every package's dist; CI builds it).check-plugin-teardown-shape --self-testfirst exited 3 in the shallow clone and passed (0) after fetching its pinned commit.--ranreconciliation: "60 derived famil(ies) accounted for — 59 run, 1 NOT-MEASURED".Dogfood battery (narrowing arm), full, under the verify lock, at c0b1992
pnpm --filter @objectstack/dogfood testafter the@objectstack/dogfood^...closure build.os-verify-lock: VERDICT command-exit 1. Test Files 3 failed, 353 passed, 3 skipped (359); Tests 4 failed, 2867 passed, 20 skipped (2891). None is this diff's (none reads an import door, the exposure gate or a__name):error-sentences-live.dogfood.test.ts, acceptance[1], bothPOST /data/OBJECTand/createMany; andobject-hook-lifecycle-scratch.dogfood.test.ts, acceptance[6] negative[0]: theHOOK_ABORTEDmain red 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 (fix in PR test(dogfood): flip the three hook-abort pins to the ruled HOOK_ABORTED wire #27878).platform-app-object-entry-views.test.ts, "every object an entry names is declared by the composition or by the identity barrel":expected [ 'sys_exchange_rate' ] to deeply equal []. A main-side red from194e66c0(sys_exchange_ratenamed by a Setup entry the dogfood composition does not register), tracked as the second red of 4-hourly full run: red on main (CI) #27813. Named, not fixed.The dogfood shards do not run on the PR or in the merge queue for this diff (their
if:excludes both events), so the reading above is the only one before the merge.Human Minimum
packages/specholds no runtime logic. (Not touched.)pnpm lintpasses.main, one pull request. (Two cards carried together on the seat's order: one file, one read.)packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_016WV4cxENxbA8ZpRgwafjvs