Repository navigation
fix(rest)!: an async import job for an unregistered object is refused with 404 OBJECT_NOT_FOUND before any job is created - #27711
Conversation
Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
… 404 before persisting it Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…port-job-unknown-object
…s the exposure gate's does Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
…port-job-unknown-object
Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 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 522b834f03cd83a78f161798f06fd7a4e7b9453f && git checkout 522b834f03cd83a78f161798f06fd7a4e7b9453f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d93d9be598e6b5ba407836e9d53d3a7d37ee6194 004cbb14df2d48a6c6d4adec0d180b7d6f849bf0 && git checkout -B drift-repro d93d9be598e6b5ba407836e9d53d3a7d37ee6194 && git merge --no-ff 004cbb14df2d48a6c6d4adec0d180b7d6f849bf0
node scripts/docs-audit/affected-docs.mjs --json d93d9be598e6b5ba407836e9d53d3a7d37ee6194
|
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract-review-claim: session_01L6bhgUCizoYWQVJWjhdR6F · head bb1e1d2 · 2026-10-29T21:03Z |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract reviewServed-tier: Read at 2026-10-29T21:15Z against the net diff ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentpackages/rest/src/data-action-routes.ts — holds against the code it cites. The reader is byte-for-byte the gate's
packages/rest/src/import-job-integration.test.ts — the negative pin asserts status, nested-or-flat
.changeset/27501-import-job-unknown-object.md — frontmatter, both declaration lines, one ADR-0087 marker, BREAKING banner, Before / Now / Unchanged / Who sees it / Remedy: the shape the gates and the CHANGELOG reader need. Sentences checked against the diff: all true.
No must-fix. The narrowing takes away only an accept that imported nothing, and makes the data door's refusal real at this door. Implemented-by: VERDICT: PASS |
…f the import job door Claude-Session: https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F Contract reviewServed-tier: Delta record on the head that followed the reviewed head ① Derived judgments
② Semver levelUnchanged and still correct: ③ Boundary flags
④ Design judgment.changeset/27501-import-job-unknown-object.md — the added bullet holds against the cited code for both refusals (status and code as read above), names the before-state truthfully, and gives each its remedy. Placed under "Who sees it" and before "The remedy", so the CHANGELOG reader meets the two extra refusals beside the typo case. No must-fix, no should-fix. packages/rest/src/data-action-routes.ts, packages/rest/src/import-job-integration.test.ts — unchanged by oid; 6508524284's ④ stands, its should-fixes 1 and 3 now carried by #27729 and not owed on this PR. Implemented-by: VERDICT: PASS |
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F 维护者速读(席位终稿)
席位意见: ACCEPT。按分诊只做 (a),与数据入口同一工厂、同一应答;(b) 任务终态在决策卡 #27676 等裁决。同步导入入口的同类缺口和两条复核建议另开卡。 席位核对ACCEPT · head
|
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Requested by Sales · project thread
Fixes #27501
Scope: ask (a) only, per triage 6498786930. Ask (b), "a job whose every row failed does not read
succeeded", was split to the decision card #27676 and is not built here: the job's terminal status is unchanged.Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none (no published skill names the async import job door's object handling)
维护者速读
POST /data/OBJ/import/jobs在对象未注册时直接返回 404OBJECT_NOT_FOUND,与POST /data/OBJ的应答信封完全一致;不再建sys_import_job,也不启动后台任务。succeeded,只有逐行错误里写着找不到对象;自动化导入方会把这当成成功。data-action-routes.ts的一处调用。packages/rest/src/data-action-routes.ts的assertImportObjectExists及其调用点;packages/rest/src/import-job-integration.test.ts末尾新增的 describe。pnpm --filter @objectstack/rest exec vitest run src/import-job-integration.test.ts,预期 15/15 通过。What changes
POST /data/:object/import/jobsnow reads the object by name, the same read the API-exposure gate makes (the protocol's execution face where it has one, elsegetMetaItem), right after that gate and before the body is parsed. If no object is behind the name, it throwsobjectNotFoundError(objectName)from@objectstack/core. That is the one factory the protocol's existence gate and the engine's verbs throw, and the route'shandleRouteErrorclassifies it. The answer is 404OBJECT_NOT_FOUNDwith the same bodyPOST /data/:objectgives the same name. No new code is minted.sys_import_jobrow is written and no worker is scheduled for a refused request.OBJECT_PACKAGE_DISABLEDpropagates (the protocol's by-name read documents that refusal; this was not measured here). Any other thrown read fails open and logs atwarn, as the exposure gate'sloadObjectItemdoes: such a read says nothing about existence, and each row's data call still answers for the object. Two existing real-protocol harnesses whose metadata store is not provisioned (import-row-schema-drift-20701,import-row-dropped-fields-20701) need this; with the fault propagated they answered 503.@objectstack/restpatch, with a BREAKING note and the ADR-0087not-required (no-migration-prescription)marker.Tests
Two cases in
packages/rest/src/import-job-integration.test.ts. They use the realRestServerroute, the realObjectQLengine withObjectStackProtocolImplementation, and SQLite:memory:.OBJECT_NOT_FOUND. The body istoEqualthe data door's (POST /api/v1/data/:object) body for the same name.sys_import_jobholds 0 rows, counted at the engine before and after.{object:'task', status:'pending', total:1}, and exactly onesys_import_jobrow once the job finishes.Acceptance notes
POST /data/:object/importhas the same gap. It runs no existence check, answers 200, and reportsOBJECT_NOT_FOUNDper row (read from the handler; the card's reproduction step 3 measured it). Triage did not name it, so it is not changed here. It is reported to the seat.RestServer, not an HTTP transport. This is the harness the file's existing cases use.Verification
Final head
bb1e1d2f(merged withorigin/main).Red before the fix (commit fcde8d5, test only)
Ablation on the final head (scripts/ablation-replace.mjs)
The ablation removed the
await assertImportObjectExists(...)call. The suite imports./rest-serverfrom source, so no dist rebuild was involved.Package suites and gates
Read at head
bb1e1d2f, except where a line names another sha.pnpm --filter @objectstack/rest test: exit 0, 340/340 files, 6668 passed, 326 skipped. Run at 06dd5e1; the commit after it changes only the new test's row-count helper, and that file re-ran 15/15 at bb1e1d2.pnpm --filter @objectstack/rest typecheck: exit 0 (06dd5e1, after a declaration build of the@objectstack/rest...closure).eslint packages/rest --no-inline-config: exit 0, 0 errors. The 2 warnings are in an untouched file.dispatch-gates --commandsderived 59 families. 57 exited 0.check:query-options-erasurewas red at first (the new test'sas anyoptions cast grew the test surface 236 → 237); it was fixed in bb1e1d2 and re-ran green at 236.check:dual-build-cjs-loadsexited 3, PREREQUISITE NOT MET: it reads every package's dist and needs a fullpnpm build, so it is NOT MEASURED locally and the CILint & Repo Gatesjob carries it.--ran: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.(narrowing)arm) at bb1e1d2: os-verify-lock VERDICT command-exit 0 (held 36m11s), closure build exit 0, 352 files passed and 3 skipped, 2844 tests passed and 20 skipped, no failures (os-dev-report 6508232970 on import: POST /data/OBJ/import/jobs accepts an unregistered object with 201 and the job then reads succeeded, where the data door answers 404 #27501).data-action-routes.tsorimport-job-integration.test.ts(REST read of all 29 open PRs' files).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01L6bhgUCizoYWQVJWjhdR6F