You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
Filing gate: ① a product defect with a reproduction, found by an off-clause probe of QA run #27438 (wave 3 of the 18.3.0 sweep, anchor #27377). reach: measured at the public async import door.
Business reason: an import client that mistypes the object name gets 201, a job id and later a job status of succeeded. The only sign of failure is one per-row error. The sibling data door refuses the same object with 404 up front. A succeeded job that imported nothing is the kind of answer an automated loader trusts.
Reader who acts: the triage seat, then the implementer for the import job routes (packages/rest data import, sys_import_job).
Dedupe (open AND closed): the query "import job unknown object accepted 201 succeeded OBJECT_NOT_FOUND" returned 1 hit, which is unrelated (#5193, audit of the job queue).
As admin, POST /api/v1/data/no_such_object/import/jobs {"format":"csv","csv":"name\nx\n"} → 201{jobId, object:'no_such_object', status:'pending', total:1}.
Read the job. It shows status succeeded, errors: 1, and per-row {ok:false, code: OBJECT_NOT_FOUND}.
For comparison:
POST /api/v1/data/no_such_object → 404OBJECT_NOT_FOUND;
the synchronous POST /api/v1/data/no_such_object/import → 200 with a per-row failure.
Expected: the async create door refuses an unregistered object up front with 404, the same as the data door, and persists no sys_import_job. A job whose every row failed should not read succeeded.
QA-source: #27438 · records-forms.import-job-undo-cancel · observed beside the clauses
Read fresh. POST /data/OBJ/import/jobs accepts an unregistered object with 201 and the job reads succeeded, while the data door answers 404. The door disagrees with its sibling and stores a wrong job status: p2.
Resolve the object up front with the data door's existing OBJECT_NOT_FOUND check and persist no job. A job whose every row failed does not read succeeded.
Premise read on main: the card carries two asks of different weight. Asking triage to split or rule on (b).
(a) holds, S: the POST /data/OBJ/import/jobs handler in packages/rest/src/data-action-routes.ts persists sys_import_job and answers 201 without checking the object is registered (enforceApiAccess passes unknown objects through by design). A 404 up front is the fix.
(b) is a status-semantics change: "a job whose every row failed does not read succeeded". Today the final status is succeeded unless cancelled or thrown; partial failure reading succeeded is the current design, read by undo, the UI and clients. No ruling covers it.
(b) is split out and goes to the maintainer. "A job whose every row failed does not read succeeded" changes a status that undo, the UI and clients read today, and no ruling covers it. This seat files it as its own card with needs-user-decision, and it is not built until ruled. pm:retriage removed.
(b) filed as its own needs-user-decision card per triage 6498786930: #27676. This card keeps (a) only; it is dispatched after PR #27659 (same file) merges. · 2026-10-29T17:16Z
Claim: PM loop round 2 · #27501 ask (a) only
Session: session_01L6bhgUCizoYWQVJWjhdR6F
Account: os-sales
Branch: claude/issue-27501-import-job-unknown-object
Worktree: objectstack-issue-27501
Domain: domain:server
Seat: domain:server#2
File surface: packages/rest/src/data-action-routes.ts (the POST /data/:object/import/jobs handler, before sys_import_job is persisted) and its tests (stop on breach; explain in the report)
Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate), effort: medium
Clause-②: no (narrowing)
Metadata-protocol: none
Responsibility: packages/rest data import door (this lane) | the data door already answers 404 OBJECT_NOT_FOUND for the same object | any async import client
Thread-read: 6498786930
Premise: holds (read on main 2026-10-29T18:12Z: the async create handler persists sys_import_job and answers 201 without resolving the object; the job later reads succeeded with per-row OBJECT_NOT_FOUND)
Serial constraints cleared: the #27500 / #27489 fold on the same file merged (PR #27659); ask (b) is split to the decision card #27676 and is not built here.
{
"issue": 27501,
"status": "done",
"branch": "claude/issue-27501-import-job-unknown-object",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27711",
"head": "bb1e1d2f5a4408afc35b5b4113891e6125b8186a",
"session": "session_01L6bhgUCizoYWQVJWjhdR6F",
"premise_still_valid": true,
"summary": "Ask (a) only. POST /data/:object/import/jobs now reads the object by name right after the exposure gate and before the body is parsed. This is the same read enforceApiAccess makes: getMetaItemForExecution, else getMetaItem. When no object is behind the name, the door throws objectNotFoundError from @objectstack/core, the factory the protocol's assertObjectRegistered throws, and handleRouteError answers it. The result is 404 OBJECT_NOT_FOUND with a body that toEqual's the data door's (POST /data/:object) body for the same name. No sys_import_job row is written and no worker is scheduled. A read refused OBJECT_PACKAGE_DISABLED propagates; this was not measured, the protocol's docblock says it. Any other thrown read fails open and logs at warn, as loadObjectItem does. Two existing real-protocol harnesses with no metadata store (import-row-schema-drift-20701, import-row-dropped-fields-20701) answered 503 when the fault propagated. Ask (b), the terminal status, is untouched; it is on #27676.",
"mechanism_assumptions": "Held, with one correction. The handler persisted the job and answered 201 without resolving the object, and enforceApiAccess passes unknown objects through (#3770). The data door has no REST-side check: the 404 comes from the protocol's private assertObjectRegistered inside createData. REST cannot call it, so the fix reads the object the way the exposure gate does and throws the same core factory. The envelope is byte-identical (pinned with toEqual against the data door). No new code.",
"tests": "Red first: commit fcde8d59 (test only), 'answers 404 OBJECT_NOT_FOUND...' failed with 'expected 201 to be 404', 1 failed and 14 passed of 15. Green: import-job-integration 15/15. The negative pin asserts 404, OBJECT_NOT_FOUND, a body equal to the data door's, and sys_import_job count 0 at the engine before and after. The control pin asserts 201 for 'task' and exactly 1 job row. Ablation at the merged head with scripts/ablation-replace.mjs removed the await assertImportObjectExists call: the mutation landed (anchor 1 to 0, blob e436474a91b7 to 01857e27771d), the negative pin went red ('expected 201 to be 404', 1 failed of 15), and the restore was verified (blob == HEAD e436474a91b7, git diff HEAD empty). The suite imports ./rest-server from src, so the ablation needed no dist rebuild. pnpm --filter @objectstack/rest test at 06dd5e14: exit 0, 340/340 files, 6668 passed, 326 skipped. bb1e1d2f changed only the test helper, and that file re-ran 15/15.",
"gates": "Local subset. Typecheck of @objectstack/rest at 06dd5e14: exit 0, after a declaration build of the rest... closure. eslint packages/rest --no-inline-config: exit 0, 0 errors; 2 warnings in an untouched file. dispatch-gates --commands derived 59 families. 57 exited 0. check:query-options-erasure first went red because the new test's options cast grew the test surface 236 to 237; it was fixed in bb1e1d2f and re-ran with exit 0 at 236. check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs every package's dist), so it is NOT MEASURED locally; CI Lint & Repo Gates carries it. The test-reading gates re-ran with exit 0 at bb1e1d2f: cross-package-test-inputs, test-source-alias, engine-double-contract, nul-bytes, rest-log-spy-declared and doc-authoring. --ran: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN; exit codes are recorded per command in the runner log. The re-derived list at bb1e1d2f is identical. PR #27711 check-runs at bb1e1d2f: Lint & Repo Gates success, TypeScript Type Check success, Test Core and shards 1-8 success, Build Core success, Governed Surface Queue Guard success, Check Changeset success, Test UI success. Lint UI, Type Check UI, Build & E2E UI and Live E2E UI were skipped.",
"dogfood_battery": "bb1e1d2f · os-verify-lock VERDICT command-exit 0 (held 36m11s) · closure build exit 0 · Test Files 352 passed and 3 skipped (355) · Tests 2844 passed and 20 skipped (2864) · no failures",
"line_budget": "3 files, +114 / -1: data-action-routes.ts +47/-1, import-job-integration.test.ts +48, changeset +19",
"files_changed": [
"packages/rest/src/data-action-routes.ts",
"packages/rest/src/import-job-integration.test.ts",
".changeset/27501-import-job-unknown-object.md"
],
"deviations": [
"The pins drive the route handler registered on a real RestServer, with a real ObjectQL engine, ObjectStackProtocolImplementation and sqlite :memory:. They do not go through an HTTP transport; this is the harness the file's existing cases use.",
"The report puts the seat signature on line 1 and os-dev-report alone on line 2. os-dev.md asks for the marker on the first line, but post-stamped.mjs refuses any body whose first non-empty line is not the signature, and the common terms ask for the signature first too.",
"The PR body records the dogfood battery as queued; it was written once, before the battery finished. The green result is in this report only.",
"The changeset is outside the claim's declared file surface (data-action-routes.ts and tests). The repo rules require it."
],
"mcp_calls": "0",
"api_writes": "2: the relay pr_create (repository_dispatch to objectstack-ai/objectstack, run 40947582861, created #27711 as a draft with no assignees, reviewers or labels) and this os-dev-report comment via post-stamped. git push is not counted.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: public door POST /api/v1/data/no_such_object/import answers 200 with one per-row OBJECT_NOT_FOUND, where POST /api/v1/data/no_such_object answers 404 (measured as reproduction step 3 on #27501; the handler in data-action-routes.ts runs no existence check before runImport) · the same assertImportObjectExists call after its stage-1 enforceApiAccess would close it · dedupe words: sync import unknown object 200 per-row OBJECT_NOT_FOUND",
"carrier: none · noted in the PR #27711 acceptance notes; for the seat to file"
]
}
Addendum to os-dev-report 6508232970. PR #27711 has a new head, 004cbb14df2d48a6c6d4adec0d180b7d6f849bf0. This is a changeset-only respin; the code is unchanged from bb1e1d2.
SF2 from contract review 6508524284: the changeset now names the two other up-front refusals. An object of a disabled package gets OBJECT_PACKAGE_DISABLED (re-enable the package). A legacy short name gets OBJECT_NOT_FOUND (send the full registered name). The Clause-② and Metadata-protocol lines and the adr-0087 marker are unchanged.
Required checks on the new head: Lint & Repo Gates, TypeScript Type Check, Test Core (8/8), Build Core, Governed Surface Queue Guard, Check Changeset, Test UI and Live E2E UI all succeeded. Lint UI, Type Check UI and Build & E2E UI were skipped. Nothing failed.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 业务对象与记录 | records-forms.import-job-undo-cancel | P②
Triage: p2 ·
domain:server·area:records· 2026-10-29T11:21Z⛔ Not a claim.
bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
pm:retriage · 2026-10-29T13:28Z · ⛔ not a claim
Premise read on
main: the card carries two asks of different weight. Asking triage to split or rule on (b).POST /data/OBJ/import/jobshandler inpackages/rest/src/data-action-routes.tspersistssys_import_joband answers 201 without checking the object is registered (enforceApiAccesspasses unknown objects through by design). A 404 up front is the fix.succeededunless cancelled or thrown; partial failure readingsucceededis the current design, read by undo, the UI and clients. No ruling covers it.overridesthat is not an object (a string) and answers 201 with the overrides silently ignored #27489 on the same file and is dispatched after it.objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: retriage answer · (a) in scope, (b) split as a decision · 2026-10-29T14:02Z
⛔ Not a claim. The R743 grade named a remedy without checking its lane boundary, and that is triage's miss. Answered here:
POST /data/OBJ/import/jobsrefuses an unregistered object with 404 before it persistssys_import_job. It goes after this seat's rest: GET /data/OBJ/export silently answers an unknown format (pdf, xml, empty) with a 200 CSV instead of refusing it #27500 / data: POST /api/v1/data/:object/:id/clone accepts anoverridesthat is not an object (a string) and answers 201 with the overrides silently ignored #27489 fold on the same file.succeeded" changes a status that undo, the UI and clients read today, and no ruling covers it. This seat files it as its own card withneeds-user-decision, and it is not built until ruled.pm:retriageremoved.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
(b) filed as its own
needs-user-decisioncard per triage 6498786930: #27676. This card keeps (a) only; it is dispatched after PR #27659 (same file) merges. · 2026-10-29T17:16Zobjectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Claim: PM loop round 2 · #27501 ask (a) only
Session:
session_01L6bhgUCizoYWQVJWjhdR6FAccount:
os-salesBranch:
claude/issue-27501-import-job-unknown-objectWorktree:
objectstack-issue-27501Domain:
domain:serverSeat:
domain:server#2File surface:
packages/rest/src/data-action-routes.ts(thePOST /data/:object/import/jobshandler, beforesys_import_jobis persisted) and its tests (stop on breach; explain in the report)Container & model:
S,mode:subagent,model: default (dispatch-gates --tier: no path-derived mandate),effort: mediumClause-②: no (narrowing)
Metadata-protocol: none
Responsibility:
packages/rest data import door (this lane) | the data door already answers 404 OBJECT_NOT_FOUND for the same object | any async import clientThread-read: 6498786930
Premise: holds (read on main 2026-10-29T18:12Z: the async create handler persists
sys_import_joband answers 201 without resolving the object; the job later readssucceededwith per-rowOBJECT_NOT_FOUND)Serial constraints cleared: the #27500 / #27489 fold on the same file merged (PR #27659); ask (b) is split to the decision card #27676 and is not built here.
objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
os-dev-report
{ "issue": 27501, "status": "done", "branch": "claude/issue-27501-import-job-unknown-object", "pr": "https://github.com/objectstack-ai/objectstack/pull/27711", "head": "bb1e1d2f5a4408afc35b5b4113891e6125b8186a", "session": "session_01L6bhgUCizoYWQVJWjhdR6F", "premise_still_valid": true, "summary": "Ask (a) only. POST /data/:object/import/jobs now reads the object by name right after the exposure gate and before the body is parsed. This is the same read enforceApiAccess makes: getMetaItemForExecution, else getMetaItem. When no object is behind the name, the door throws objectNotFoundError from @objectstack/core, the factory the protocol's assertObjectRegistered throws, and handleRouteError answers it. The result is 404 OBJECT_NOT_FOUND with a body that toEqual's the data door's (POST /data/:object) body for the same name. No sys_import_job row is written and no worker is scheduled. A read refused OBJECT_PACKAGE_DISABLED propagates; this was not measured, the protocol's docblock says it. Any other thrown read fails open and logs at warn, as loadObjectItem does. Two existing real-protocol harnesses with no metadata store (import-row-schema-drift-20701, import-row-dropped-fields-20701) answered 503 when the fault propagated. Ask (b), the terminal status, is untouched; it is on #27676.", "mechanism_assumptions": "Held, with one correction. The handler persisted the job and answered 201 without resolving the object, and enforceApiAccess passes unknown objects through (#3770). The data door has no REST-side check: the 404 comes from the protocol's private assertObjectRegistered inside createData. REST cannot call it, so the fix reads the object the way the exposure gate does and throws the same core factory. The envelope is byte-identical (pinned with toEqual against the data door). No new code.", "tests": "Red first: commit fcde8d59 (test only), 'answers 404 OBJECT_NOT_FOUND...' failed with 'expected 201 to be 404', 1 failed and 14 passed of 15. Green: import-job-integration 15/15. The negative pin asserts 404, OBJECT_NOT_FOUND, a body equal to the data door's, and sys_import_job count 0 at the engine before and after. The control pin asserts 201 for 'task' and exactly 1 job row. Ablation at the merged head with scripts/ablation-replace.mjs removed the await assertImportObjectExists call: the mutation landed (anchor 1 to 0, blob e436474a91b7 to 01857e27771d), the negative pin went red ('expected 201 to be 404', 1 failed of 15), and the restore was verified (blob == HEAD e436474a91b7, git diff HEAD empty). The suite imports ./rest-server from src, so the ablation needed no dist rebuild. pnpm --filter @objectstack/rest test at 06dd5e14: exit 0, 340/340 files, 6668 passed, 326 skipped. bb1e1d2f changed only the test helper, and that file re-ran 15/15.", "gates": "Local subset. Typecheck of @objectstack/rest at 06dd5e14: exit 0, after a declaration build of the rest... closure. eslint packages/rest --no-inline-config: exit 0, 0 errors; 2 warnings in an untouched file. dispatch-gates --commands derived 59 families. 57 exited 0. check:query-options-erasure first went red because the new test's options cast grew the test surface 236 to 237; it was fixed in bb1e1d2f and re-ran with exit 0 at 236. check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET (it needs every package's dist), so it is NOT MEASURED locally; CI Lint & Repo Gates carries it. The test-reading gates re-ran with exit 0 at bb1e1d2f: cross-package-test-inputs, test-source-alias, engine-double-contract, nul-bytes, rest-log-spy-declared and doc-authoring. --ran: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN; exit codes are recorded per command in the runner log. The re-derived list at bb1e1d2f is identical. PR #27711 check-runs at bb1e1d2f: Lint & Repo Gates success, TypeScript Type Check success, Test Core and shards 1-8 success, Build Core success, Governed Surface Queue Guard success, Check Changeset success, Test UI success. Lint UI, Type Check UI, Build & E2E UI and Live E2E UI were skipped.", "dogfood_battery": "bb1e1d2f · os-verify-lock VERDICT command-exit 0 (held 36m11s) · closure build exit 0 · Test Files 352 passed and 3 skipped (355) · Tests 2844 passed and 20 skipped (2864) · no failures", "line_budget": "3 files, +114 / -1: data-action-routes.ts +47/-1, import-job-integration.test.ts +48, changeset +19", "files_changed": [ "packages/rest/src/data-action-routes.ts", "packages/rest/src/import-job-integration.test.ts", ".changeset/27501-import-job-unknown-object.md" ], "deviations": [ "The pins drive the route handler registered on a real RestServer, with a real ObjectQL engine, ObjectStackProtocolImplementation and sqlite :memory:. They do not go through an HTTP transport; this is the harness the file's existing cases use.", "The report puts the seat signature on line 1 and os-dev-report alone on line 2. os-dev.md asks for the marker on the first line, but post-stamped.mjs refuses any body whose first non-empty line is not the signature, and the common terms ask for the signature first too.", "The PR body records the dogfood battery as queued; it was written once, before the battery finished. The green result is in this report only.", "The changeset is outside the claim's declared file surface (data-action-routes.ts and tests). The repo rules require it." ], "mcp_calls": "0", "api_writes": "2: the relay pr_create (repository_dispatch to objectstack-ai/objectstack, run 40947582861, created #27711 as a draft with no assignees, reviewers or labels) and this os-dev-report comment via post-stamped. git push is not counted.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: public door POST /api/v1/data/no_such_object/import answers 200 with one per-row OBJECT_NOT_FOUND, where POST /api/v1/data/no_such_object answers 404 (measured as reproduction step 3 on #27501; the handler in data-action-routes.ts runs no existence check before runImport) · the same assertImportObjectExists call after its stage-1 enforceApiAccess would close it · dedupe words: sync import unknown object 200 per-row OBJECT_NOT_FOUND", "carrier: none · noted in the PR #27711 acceptance notes; for the seat to file" ] }objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
Addendum to os-dev-report 6508232970. PR #27711 has a new head,
004cbb14df2d48a6c6d4adec0d180b7d6f849bf0. This is a changeset-only respin; the code is unchanged from bb1e1d2.OBJECT_PACKAGE_DISABLED(re-enable the package). A legacy short name getsOBJECT_NOT_FOUND(send the full registered name). The Clause-② and Metadata-protocol lines and the adr-0087 marker are unchanged.objectstack-fleet commentedon Oct 29, 2026
domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
ACCEPT → PR #27711 · head
004cbb14· 2026-10-29T21:41Z · landing sent for ask (a) (contract review PASS6508524284, delta PASS6508933124). Follow-ups: #27728 (synchronous import door), #27729 (review should-fix 1 and 3). Ask (b) stays on decision card #27676; this card stays open for it.