Skip to content

rest: an async import job whose every row failed ends failed, not succeeded (ruled B; split from #27501 (b)) #27676

Closed

Description

@objectstack-fleet

domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

Blocked-by: #27711

Ruled B by the maintainer (record 6509036166): execution card now. All rows failed ⇒ failed; the predicate, the five pins and the split spec docblock (#27734) are in the record. Built after #27711 merges (same file).

Filing gate: ② a product decision only the maintainer takes. Split out of #27501 ask (b) by triage answer 6498786930 ("changes a status that undo, the UI and clients read today, and no ruling covers it"), after this seat's retriage 6497991662 asked for the split. ⛔ Not a claim; (b) is not built until ruled. #27501 (a) — refuse an unregistered object with 404 before any job row exists — stays in scope there and does not wait on this card.
Business reason: an automated loader (a script, an agent, a migration) that polls a background import to its terminal state and branches on status === 'succeeded' is told "succeeded" today even when not one row was written. Who benefits from a ruling: every client of POST /api/v1/data/:object/import/jobs; without one, the (a) fix removes one producer of the case (a mistyped object) and leaves the ordinary one (every row refused by validation or mapping).
Reader who acts: the maintainer rules; then the domain:server seat (A/B/D) or the domain:spec seat (C) executes per "After the ruling".
Dedupe: the board's search endpoint is closed to this session, so the board was not searched; read instead: the parent thread (#27501, three records) and the tree corpus (check-prior-rulings.mjs --terms, 4 ADR hits, none on the job's terminal status). The triage answer states no ruling covers it.

维护者速读

  • 改了什么:不改代码,问你一个口径。后台导入作业(POST /data/OBJ/import/jobs)里每一行都失败、一条也没写进去时,作业状态该不该还叫 succeeded。
  • 为什么改:今天只要 worker 没抛错、没被取消,作业就记 succeeded,哪怕 0 行落库;唯一的失败迹象是行级错误计数。QA 探针(QA run · release-18.3.0-w3-tier1-b (89/89) · c4c515ad · 2026-10-29 · 79 PASS / 6 PARTIAL / 4 FAIL / 0 BLOCKED / 0 NOT-RUN #27438)把对象名写错,拿到 201 和一个 succeeded 的作业。自动化加载器只看状态就会信。
  • 风险与回滚:A/D 零风险;B 只改「全部失败」这一种终态,部分失败照旧 succeeded,撤销、界面、现有 pin 都量过不受影响,回滚即还原一条规则;C 要加协议枚举值,老版 Console 的轮询是闭集、会等不到终态,客户端的 switch 也会漏。
  • 先看:下面「Options」表和「Readers of the status, measured」表。
  • 两分钟验证:git show origin/main:packages/rest/src/data-action-routes.ts | sed -n 675,677p(终态只分 cancelled / succeeded);git show origin/main:packages/spec/src/api/export.zod.ts | sed -n 483,489p(枚举注释写明 succeeded 允许行级错误)。
  • 需要你做的:选 A / B / C / D(推荐 B)。

Background

The async import door persists a sys_import_job, answers 201, and a background worker runs runImport (per-row; a failing row never stops the batch, as content/docs/data-modeling/import-mappings.mdx:268-283 documents). When the worker returns, the terminal status is computed in one line, packages/rest/src/data-action-routes.ts:675 on origin/main 85663a5:

let finalStatus = summary.cancelled ? 'cancelled' : 'succeeded';

failed is written only by the catch at :696, i.e. when the worker threw. The per-row counts (error_count from summary.errors, :689) are written beside the status either way. The synchronous door (POST /data/:object/import) has no status at all: its response is counts (ok, errors, created, …; ImportResponseSchema, packages/spec/src/api/export.zod.ts:454-465).

The contract declares today's behaviour on purpose. ImportJobStatus, packages/spec/src/api/export.zod.ts:483-489:

'succeeded',  // Finished (rows may still have per-row errors)
'failed',     // Aborted on a fatal error

The same five values are the sys_import_job.status select (packages/platform-objects/src/audit/sys-import-job.object.ts:48), and ImportJobProgressSchema.error is described as "Fatal error message (when status = failed)".

Governing text:

  • packages/spec/src/api/export.zod.ts:486 (above): the declaration says succeeded admits per-row errors. Under 〈升级与决策〉"协议为基准", the current runtime matches its declaration, so this is a request to change the contract's meaning, not a defect.
  • AGENTS.md › Route & surface ownership §4: "Machine-readable surfaces must not lie … a wrong answer here propagates into everything built on top of it." Prime Directive chore: version packages #10: never advertise what the runtime does not deliver.
  • AGENTS.md › Degradation log levels, the "third legal answer": "a batch whose contract IS a per-item outcome report, does not look normal from the outside — the requester was told." This is the text that defends today's design: the per-row report is the delivery.
  • ADR-0119 D4 (nearest principle, not on point): "A response claiming success: true for a row that was rolled back is the bug, not merely a missing transaction."
  • git grep -n -iE 'import job|ImportJobStatus|sys_import_job' origin/main -- AGENTS.md docs/adr packages/spec/src → only ADR-0103 D5 (who may write the row) and the spec docblocks above; no ADR rules on the terminal status.

Protocol statement: A, B, D: Clause-②: no, Metadata-protocol: none (B changes when an already-declared value is emitted; no shape moves). C: Clause-②: yes (widening) on a published schema (ImportJobStatus, ListImportJobsRequestSchema.status, the platform object's select), Metadata-protocol: none (a DTO, not an authorable key); under the protocol-class rule the card re-grades to p1 + the in-development major's target: and moves to the spec lane.

Premises (each with its re-check)

  1. The terminal status is succeeded unless cancelled or thrown, with counts written beside it. Re-check: git show origin/main:packages/rest/src/data-action-routes.ts | sed -n 675,700p.
  2. The declaration says so. Re-check: git show origin/main:packages/spec/src/api/export.zod.ts | sed -n 483,489p.
  3. Undo accepts only succeeded or cancelled jobs with a non-empty undo log. Re-check: git show origin/main:packages/rest/src/rest-server.ts | sed -n 977,983p.
  4. The Console already derives a four-way outcome (success | partial | failed | cancelled) from the counts plus jobStatus === 'failed', and keeps a closed terminal set. Re-check: git show origin/main:ui/packages/plugin-grid/src/ImportWizard.tsx | sed -n 494,504p and | sed -n 1127p.
  5. Three in-repo pollers treat succeeded as "it worked". Re-check: git grep -n "status === 'succeeded'\|toBe('succeeded')" origin/main -- packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts packages/rest/src/import-undo-controls.test.ts packages/rest/src/import-control-opt-out-gate.test.ts.

The question

When a customer's background import finishes and not a single row was imported, should the job's one-word status still say it succeeded?

Options

What it does What the client sees Effort (lane · PRs · precedent)
A Keep succeeded; make the outcome loud without a status change Sharpen the succeeded / failed / error docblocks and the import docs to say "status is the worker's lifecycle; the verdict is ok / errors / results" Unchanged wire. A one-field reader is still told succeeded on zero rows spec seat (docblock is a packages/spec touch; gen:docs) · 1 docs-only PR · no precedent needed
B All rows failed → failed; partial stays succeeded At :675, a job with total_rows > 0 and every row ok:false ends failed, with error set to a sentence that says every row failed and points at results (no tracker number); cancel still wins; partial failure keeps today's succeeded A zero-effect job reads failed with per-row errors attached; a partly-successful job reads as today domain:server · 1 PR after #27501 (a) lands (same file) · nearest landed precedent on this family's semantics #24431 (+584/−48, 17 files, which also carried spec docblocks + UI + docs); B needs no UI change (reading 4)
C Add a third terminal, e.g. partial, for "some rows failed"; all-failed → failed Widen ImportJobStatus, the list filter, the platform-object select; emit it at :675; admit it in importJobUndoable, the wizard's terminal set / badge map / 10 locale files / the satisfies pin, the dogfood poll, tests, docs Three honest terminals on the wire; every closed-set reader built before the widening breaks (table below) domain:spec lead + server + ui in ONE PR (Type Check UI reds the spec widening until the UI member lands) · contract review CONTRACT_REVIEW_TIER · no measured enum-widening precedent in this clone (shallow; the history question is refused here)
D Close not planned Nothing; #27501 (a) removes the producer that was observed As today zero

Business meaning: A is a courier that reports "the run is finished" and staples the list of undelivered parcels to it; B reports the run "failed" only when not one parcel was delivered; C puts three stamps on the run — delivered / partly delivered / nothing delivered; D leaves the courier as is.

Readers of the status, measured on origin/main 85663a5

Reader What it does with succeeded vs a failure status A B C
Undo route, packages/rest/src/rest-server.ts:977-983 (importJobUndoable), data-action-routes.ts:770 (422 NOT_UNDOABLE) Undoable only when succeeded or cancelled AND the undo log has entries — — : an all-failed job wrote nothing, its log is empty, it is already not undoable breaks unless partial is added here: every partly-successful job would lose Undo
Cancel route, data-action-routes.ts:730-740 Acts only on pending / running — — —
List filter ?status=, data-action-routes.ts:898-902; ListImportJobsRequestSchema.status enum, export.zod.ts:564 Passes the string through — — enum widens too
Console Import Wizard result screen, ui/packages/plugin-grid/src/ImportWizard.tsx:494-504 Derives its own outcome from counts: zero imported + errors → headline "Import failed" (ui/packages/i18n/src/locales/en.ts:839); some imported + errors → "Import finished with errors" (:838); jobStatus === 'failed' → failed — — : it already says "failed" for this case; :2696 adds a synthetic row only when failed arrives with no per-row errors, which B does not produce the derivation stays (it also serves the sync door)
Console history list, ImportWizard.tsx:2059-2065 (IMPORT_JOB_STATUS_VARIANT: Record<ImportJobStatus,…>), badge label grid.import.jobStatus.* (en.ts:905-911 and 9 more locales), error count already drawn in red beside it Badge colour and word per status — the badge turns red for an all-failed job Type Check UI reds (missing record key) until the UI member lands; 10 locale files
Console poll loop, ImportWizard.tsx:1127 (IMPORT_JOB_TERMINAL_STATUSES, closed set), :1159-1170 (isSettledJobRead), :2742-2770 (for (;;) until settled) Any status outside the closed set is "not finished" — — an older Console polls a partial job every 800 ms forever and never shows a result
UI type pin, ui/packages/types/src/__tests__/spec-derived-unions.test.ts:171-172 Pins the five members satisfies ImportJobStatus — — must be rewritten in the same PR
Client SDK, packages/client/src/index.ts:7203-7256, :8076-8112; objectui adapter ui/packages/data-objectstack/src/index.ts:4936-4971 Typed pass-through, no branch on status — — type only
Tests: packages/rest/src/import-job-integration.test.ts:176 pins succeeded with errors: 1 (partial); :147, import-undo-controls.test.ts:197-198, import-control-opt-out-gate.test.ts:166 poll the closed set and assert succeeded Partial = succeeded is pinned — — : partial unchanged; one new pin for all-failed :176 flips; the three pollers need the member
Dogfood, packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts:84-88 Returns results only on succeeded; asserts every other read is pending / running — — in practice (its fixtures import cleanly); the assertion would fire on an all-failed fixture fires on any partial job
QA checklist records-forms.import-job-undo-cancel (docs/qa/platform-checklist/areas/records-forms.json) acceptance[0] "reaches a terminal state", unnamed; acceptance[2] already says "the job reports the cancelled/partial state" for the cancel case — — wording already fits
Docs / skills: content/docs/data-modeling/import-mappings.mdx:268-283, :316; generated content/docs/references/api/export.mdx:242; skills/objectui/guides/data-integration.md:69 No hand-written page states the status vocabulary; the generated reference carries the enum one sentence one sentence regenerate + one sentence
Outside this repo (cloud, hotcrm, customer loaders, older Console builds in packages/console/dist) not measurable here — a loader that treated zero-row succeeded as success now sees failed — the intended change closed-set clients break silently

四维分析

os-decision-facets

  • ① 项目长远合理性:两年后的样子是「导入是逐行作业(文档已定:一行失败不停整批),作业的一字状态是一个字段就能信的整体裁定,行级细节留在计数与 results」。参照:Salesforce Bulk API 2.0 的作业有失败记录仍到 JobComplete,Failed 只给作业级失败;BigQuery load 的坏行超过 maxBadRecords 容忍即整作业 FAILED。B 只在「一行都没落」这一个一字段读者真会被骗的点改口径,不增枚举、不增键,特例最小;C 把界面自造的四态搬进协议,是契约增生,且界面的推导还得留着(同步门没有状态),所以 C 不消灭任何特例;A 把「成功」留给生命周期、把裁定推给读计数的人,与仓内三处读者的实际读法相反。
  • ② 实际业务拉动:今天撞上的是 QA 探针 QA run · release-18.3.0-w3-tier1-b (89/89) · c4c515ad · 2026-10-29 · 79 PASS / 6 PARTIAL / 4 FAIL / 0 BLOCKED / 0 NOT-RUN #27438(对象名写错 → 每行 OBJECT_NOT_FOUND → succeeded),(a) 会关掉这个生产者;常态生产者是映射错列、必填全空这类「每行都被拒」的导入。板上无具名客户拉动(本会话搜不了看板,父线程无);仓内实测三处轮询者把 succeeded 读作「成功」(dogfood :86、undo-controls :198、opt-out gate :166)。零具名客户拉动按规则默认 D;拉动来自 ③。
  • ③ 防 AI 犯错:AI 加载器的典型写法就是轮询到终态、status === 'succeeded' 即继续。B 让「零效果」响亮(failed + 一句指向 results 的 error);A 是消费端宽容形,出错时谁也看不到;C 最响亮,但新枚举值本身对每个 switch 闭集的生成客户端是陷阱(老 Console 永远轮询);D 保留静默形。
  • ④ 创业阶段不扩散:B 一条规则、一个文件、一个 pin、一句 docblock,无新键、无新门禁;C 的枚举扩张是永久义务(spec、平台对象、server、UI 十个 locale、文档、测试)且破坏闭集读者;A 只动文档;D 最省。不荐新增门禁。
    Prior rulings read: import job,importjobstatus,sys_import_job,succeeded,per-row → 4 hits; ADR-0027 Decision §13, ADR-0077 Decision §2, ADR-0103 D5, ADR-0119 D4 (none rules on the job's terminal status; ADR-0119 D4 is the nearest principle); thread: 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 (6495291748, 6497991662, 6498786930); repo: objectstack-ai/objectstack — board search unresolved (endpoint closed to this session).

Recommendation

B — recommendation only; the ruling is the maintainer's. One line: it is the single point where a one-field reader is actually misled (nothing landed), and it costs no shape change and breaks no measured reader — partial jobs keep succeeded, so the :176 pin, Undo, the wizard and the dogfood poll all stand.
Not A: it leaves the silent shape, and the three in-repo pollers show readers do treat succeeded as "it worked". Not C: a widening that breaks three measured closed-set readers and ships a permanent member for a state every reader can already derive from counts (the Console does). Not D: (a) removes one producer of the case, not the case.

Confidence gaps, stated: (1) the board could not be searched from this session for a prior ruling; the parent thread and the tree corpus were read. (2) External clients (cloud, hotcrm, customer loaders) are not measurable here. (3) Dry-run jobs: under B a validate-only job whose every row is invalid would also read failed (dryRun: true stays visible on the DTO); recommend the same rule, to be ruled in the same ruling. (4) The predicate: "every row failed" = total_rows > 0 and created + updated + skipped === 0 (equivalently errors === total, since the counts partition the rows); the PR names it.

Self-check: 只看①选 B;②③④ 是否翻转:否(② 零具名客户拉动指向 D,但 ③ 的三处仓内读者实测把 succeeded 读作成功,维持 B;④ 不新增门禁)。

After the ruling

Related

#27501 (parent; (a) in scope there) · triage 6495291748 · retriage 6497991662 · answer 6498786930 · QA run #27438 (anchor #27377) · #27500 / #27489 (same-file fold ahead of (a)) · #24431 (precedent: last semantics change on the import-job family) · ADR-0103 D5 · ADR-0119 D4.


Generated by Claude Code

Activity

  1. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 29, 2026
  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    Independent review (fable)

    Read at origin/main 33ab543; the card's pinned 85663a5 is reachable in this clone and every cited file is byte-identical between the two. Chinese review file: /mnt/project-files/issue-27676/director-review-27676.md. Recorded 2026-10-29T17:33Z. ⛔ Not a ruling; a check and a recommendation.

    Verified readings

    • Premises 1–5 hold: terminal line data-action-routes.ts:675, cancel wins :676-682, counts :689, catch → failed :694-698; enum comments export.zod.ts:483-489, error describe :533, list filter enum :564; platform select sys-import-job.object.ts:47-48; importJobUndoable rest-server.ts:977-983; the 422 text at data-action-routes.ts:771 already names "nothing was written".
    • Readers table holds: wizard derivation ImportWizard.tsx:494-504 (:490-491: all rows deliberately skipped = success), closed set :1127, isSettledJobRead :1159-1170, for (;;) :2742 at 800 ms (:525) with only a consecutive-error cap, so an unknown non-terminal status does poll forever; :2696 synthetic row; badge map :2059-2065; satisfies pin spec-derived-unions.test.ts:171-172; three in-repo pollers read succeeded as success (dogfood :84-88, undo-controls :197-198, opt-out gate :166); :176 pins partial = succeeded; SDK and objectui adapter pass the string through. packages/console/dist is gitignored, so "older Console builds" means the published npm package: not measurable here, implied by the source loop.
    • B's predicate is right, with one trap the PR must dodge: every row gets exactly one of created / updated / skipped / failed and, when not cancelled, the four sum to total (import-runner.ts:1142-1147), so total > 0 && created + updated + skipped === 0 ⇔ errors === total. But the aggregate ok counts only created / updated (:1359, :1516, :1534) and NOT skipped rows, which are ok: true (:1459-1472). ⛔ summary.ok === 0 would mark an all-skipped update-mode job failed, against the wizard's own rule; pin "all rows skipped → succeeded" as a CONTROL. Dry run: reportPreview counts valid rows as created / updated (:1101-1124), so an all-invalid dry run reads failed under B, consistent with gap (3). Cancel: summary.cancelled is tested first, so the rule runs only on the non-cancelled branch. Zero rows: import-prepare.ts refuses only > maxRows (:668) and a body with no payload (:580); a header-only CSV builds a total_rows: 0 job (data-action-routes.ts:586) that ends succeeded 0/0, and B's total > 0 guard keeps it so. Rule it in the same stroke (recommend: keep).
    • Lane and cost claims: C's protocol-class grading (filing-gate.md: a spec acceptance / declaration surface change, wider or narrower, is p1 + the in-development major's target:) holds; fix(rest)!: an import undo runs under the object's declared controls for every caller but the system principal #24431 is +584/−48, 17 files, merged; B's Clause-②: no holds under the mechanical boundary test (no wider accept set, no wider public surface).

    Corrections

    1. en.ts lines: "Import failed" is :847, "Import finished with errors" is :846 (card: 839 / 838); jobStatus.* is :913-919 (card: 905-911). Same lines at 85663a5. The strings themselves are as described.
    2. Lane: lanes/server.md:6 red line reads "触 packages/spec 的卡一律归 spec 席", and SKILL.md:163 gives describe / JSDoc prose to domain:spec. "The spec seat co-signs" is not a form the rule offers. B's three export.zod.ts sentences (succeeded, failed comments; error describe) are a separate spec-seat PR (+ gen:docs), or the whole card moves to domain:spec.
    3. "No measured enum-widening precedent in this clone" is wrong: PR feat(spec,platform-objects): add degraded to the job status vocabulary (#7072) #7340 (Job status vocabulary has no degraded value — #5548's services half cannot compile or persist without widening JobExecutionStatus + the two sys_job* selects #7072) widened JobExecutionStatus + the two sys_job* selects + translations, +148/−12, 10 files, with the UI label / colour / filter as a separate objectui follow-up (now ui/ is in-tree, so the card's one-PR claim for C stands); ADR-0044 D1 added ApprovalStatus: 'returned' with "dual-source enums, all updated together". Both are C's measured shape.
    4. The "contract declares it on purpose" claim is a source comment only: the generated reference content/docs/references/api/export.mdx:242-250 lists the five members without the comments, and no hand-written page states that succeeded admits per-row errors. B retracts no published statement.
    5. The two industry references point opposite ways: Salesforce Bulk API 2.0 stays JobComplete with every record failed (an A shape); BigQuery fails the whole load above maxBadRecords, default 0 (stricter than B). B is the minimal threshold form (100 %). Not re-checked online in this session.

    Governing texts the card did not cite

    1. The DbJobAdapter 把「handler 没抛错」记成 sys_job_run.status='success' —— 内部自行降级的 job(如 wait 唤醒打空)在作业审计面上仍显示成功 #5548 maintainer ruling (comments 5224023201 and 5225117584), recorded in spec text. The sibling job family asked the same question: a handler ran to completion, "work did not happen", may the audit status still say success? Ruled no: "an audit face that reports success for a run that accomplished nothing is the declared ≠ enforced shape applied to observability". The option "document that status means did-not-throw" (this card's A) was rejected: "legalizes silence on a machine-readable audit surface, against the standing rule that fail-open or fail-closed can be ruled but silence cannot". The option "make it a failure" (this card's B) was rejected there only because IJobService third-party implementations key retries on throw. Chosen: one additional outcome, "Vocabulary stays minimal … ⛔ Do not open an enum family". Landed via spec: JobHandler 增加可选的「跑完但没干成」回报通道 —— #5548 裁决(B-minimal)的契约半边,可加性,Promise&lt;void&gt; handler 零改动 #6617, Job status vocabulary has no degraded value — #5548's services half cannot compile or persist without widening JobExecutionStatus + the two sys_job* selects #7072 / PR feat(spec,platform-objects): add degraded to the job status vocabulary (#7072) #7340, PR fix(service-job,service-automation): map a degraded job outcome to sys_job_run.status instead of success (#5548) #7446; text at packages/spec/src/contracts/job-service.ts:83-85, packages/spec/src/system/job.zod.ts:476-509, sys-job-run.object.ts:59-60. Bearing here: A and D are the shape DbJobAdapter 把「handler 没抛错」记成 sys_job_run.status='success' —— 内部自行降级的 job(如 wait 唤醒打空)在作业审计面上仍显示成功 #5548 rejected; B is DbJobAdapter 把「handler 没抛错」记成 sys_job_run.status='success' —— 内部自行降级的 job(如 wait 唤醒打空)在作业审计面上仍显示成功 #5548's verdict applied to a family whose failed has no retry coupling (terminal, not undoable, error carries text, the wizard already distinguishes at :2696), so the one reason that forced a third member there is absent; degraded's "reason rides the existing error column" matches B's error sentence. check-prior-rulings.mjs searches ADR decision lines only and cannot see this ruling (re-run with degraded, terminal state added: 20 hits, all term recall).
    2. The batch write door's verdict convention. buildBatchDataResponse sets success: failed === 0 (protocol-batch-data.ts:528-537); the spec example "Partial Success Response (atomic=false)" carries success: false (batch.zod.ts:253-256). So the repo has three per-item verdict conventions today (batch: any failure = not success; sync import: counts, no verdict; async import: lifecycle succeeded). B does not unify them and fixes only the one lie; the ruling should say that partial-as-succeeded is kept on purpose, so the batch precedent does not reopen this.

    Recommendation (a recommendation; the ruling is the maintainer's)

    B. Axis ① first: a job's one-word status should be a verdict one field can be trusted on; #5548 already ruled that "ran, nothing accomplished" may not read as success; the import family's failed is free of the retry coupling that forced a third value there; C pays a permanent enum member and three measured closed-set breaks for a state every reader can derive from counts; A / D keep the shape #5548 rejected. Three lines for the ruling so nothing comes back: (1) predicate !cancelled && total > 0 && created + updated + skipped === 0 ⇒ failed with an error sentence pointing at results; dry run same rule; zero rows stay succeeded; partial stays succeeded, on purpose. (2) Execution: a server PR (the rule, the evaluation order, five pins: all failed → failed with errors === total and error set; 2 ok + 1 failed → succeeded, :176 untouched; all skipped → succeeded; cancel still wins; all-invalid dry run → failed) plus a spec-seat PR for the three docblock sentences and gen:docs, or the card moves to domain:spec whole. (3) The ruling comment names #5548 as its precedent.

    Facets, independently re-derived

    os-decision-facets

    Reply line for the maintainer: 「#27676 选 B」(含上面三条);or 「#27676 选 C」(then in #5548's form: one value, minimal vocabulary; not recommended); or A / D (contradicts #5548; say why the import family is the exception).


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    Ruling: needs-user-decision · letter B · maintainer 「27676 B」 (2026-10-29T21:42Z, project ruling thread 「待你裁决的问题」) · recorded by the seat 2026-10-29T21:44Z

    The maintainer (Sales) ruled B in the project's ruling thread (https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuAAoT2LLBar7yUSvgs2Mr7J). The ruling thread wrote nothing to GitHub; this comment is its record, posted by the domain:server#2 seat through the relay. Marker: maintainer-ruled. The recommendation (B) and the independent review 6503865890 (agrees B) are on this card above.

    The question

    A job of the async import door (POST /data/:object/import/jobs) whose every row failed reads succeeded today.

    • A Keep succeeded; sharpen the docs only.
    • B All rows failed → failed; partial failure stays succeeded. ← ruled
    • C Add a third terminal (partial).
    • D Close not planned.

    Execution parameters (settled in one pass)

    1. Predicate. At the terminal-status line of the job worker (packages/rest/src/data-action-routes.ts, the finalStatus computation): !cancelled && total > 0 && created + updated + skipped === 0 ⇒ failed, else today's rule. Not ok === 0: a skipped row is not counted in ok. error is set to one sentence saying every row failed and pointing at results, with no tracker number.
    2. Kept as succeeded on purpose: a zero-row job, an all-skipped job, and a partly-failed job. The batch door's "any failure ⇒ false" rule is not carried over. Cancellation still wins.
    3. Dry-run: the same rule (an all-error dry-run ends failed).
    4. Server PR (domain:server, after 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 (a) PR fix(rest)!: an async import job for an unregistered object is refused with 404 OBJECT_NOT_FOUND before any job is created #27711 merges; same file), with five pins: all rows fail → failed, errors === total, error present; 2 ok + 1 fail → succeeded (the existing partial pin is unchanged); all skipped → succeeded; cancellation still wins; all-error dry-run → failed.
    5. Spec docblock (packages/spec/src/api/export.zod.ts, the ImportJobStatus / ImportJobProgressSchema.error sentences, plus gen:docs) goes to the domain:spec seat as its own PR, not in the server PR.
    6. Precedent cited: DbJobAdapter 把「handler 没抛错」记成 sys_job_run.status='success' —— 内部自行降级的 job(如 wait 唤醒打空)在作业审计面上仍显示成功 #5548 (5224023201, 5225117584), the batch-door convention the review named.

    四维分析

    os-decision-facets

    Next: the seat files the spec docblock card and moves this card to execution (pm:queue, Blocked-by: #27711).

  4. changed the title [-][Decision] async import job: a job whose every row failed reads `succeeded` today — keep it, flip only that case to `failed`, or add a `partial` status (split from #27501 (b))[/-] [+]rest: an async import job whose every row failed ends `failed`, not `succeeded` (ruled B; split from #27501 (b))[/+] on Oct 29, 2026
  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    Claim: PM loop round 4
    Session: session_01L6bhgUCizoYWQVJWjhdR6F
    Account: os-sales
    Branch: claude/issue-27676-all-rows-failed-job
    Worktree: objectstack-issue-27676
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/rest/src/data-action-routes.ts (the async import job's terminal-status computation only), packages/rest/src/import-job-integration.test.ts, .changeset/*; ⛔ no packages/spec (the docblock is #27734) — stop on breach and 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: rest async import job terminal status (this lane) | maintainer ruling B 6509036166 | every client that polls an import job to its terminal status
    Thread-read: 6509036166
    Ruling-ref: 6509036166 (maintainer, letter B)
    Premise: holds (read on main 81d00a4e 2026-10-29T22:11Z: the terminal status is summary.cancelled ? 'cancelled' : 'succeeded' at data-action-routes.ts:765; failed is written only when the worker throws)
    Serial constraints cleared: #27711 (same file) merged; no open PR edits these files.

  6. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
    os-dev-report

    {
      "issue": 27676,
      "status": "done",
      "branch": "claude/issue-27676-all-rows-failed-job",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27795",
      "head": "2c10836a",
      "session": "session_01L6bhgUCizoYWQVJWjhdR6F",
      "premise_still_valid": true,
      "summary": "Ruling B (record 6509036166) built at the async import worker's terminal status in packages/rest/src/data-action-routes.ts. The existing cancel resolution is unchanged and runs first (summary.cancelled, then the in-memory cancelledImportJobs flag, then the durable row re-read). Only a status still 'succeeded' after all three is tested: summary.total > 0 && summary.created + summary.updated + summary.skipped === 0 makes it 'failed', and the final patch then also writes error = one sentence that every row failed and that each row's reason is in the job's results (no tracker number). Field mapping: ruling cancelled = the resolved status (summary.cancelled + the two late re-checks); total = summary.total (rows.length); created/updated/skipped = summary.created/updated/skipped (ImportRunSummary extends ImportProgress, packages/core/src/utils/import-runner.ts); summary.ok is not read. Dry runs take the same path. No packages/spec change (#27734). Readers measured: importJobUndoable needs a non-empty undo log, so a zero-write job was and stays not undoable (pinned: undoable false, undo 422 NOT_UNDOABLE); the cancel route acts on pending/running only; the ?status= filter passes through (pinned: listed under status=failed); the Console wizard already reads nothing-imported-plus-errors and jobStatus failed as 'failed', and its synthetic row needs a failed job with no per-row errors, which this never produces. No docs page states the old rule (git grep of content/docs outside references/releases and skills/: 0 hits), so Skills-docs: none. Two stale fixtures, direct consequences, were updated in this PR: import-row-cell-errors.test.ts (its job poll asserted succeeded for one-row all-failed jobs) and the dogfood import-reference-exposure poll (a withheld-target job fails every row); the dogfood one is now strict: failed exactly when no row is ok.",
      "mechanism_assumptions": "Held. The terminal line was at :765 on 81d00a4e as the claim said; ImportRunSummary carries total/created/updated/skipped/errors/ok/cancelled, and ok excludes skipped rows, as the review said.",
      "tests": "Red first at e5363269 (tests only): the all-failed and all-failed dry-run pins red (status expected failed, received succeeded), Tests 2 failed | 19 passed (21). Green: import-job-integration + import-row-cell-errors 27/27 at 2fa11634. Ablations through scripts/ablation-replace.mjs, suites import ./rest-server from source (no dist involved), each restored blob == HEAD (2e6727ca4e85) with git diff HEAD empty: (1) delete `if (everyRowFailed) finalStatus = 'failed';` -> 4 red (both all-failed pins + both import-row-cell-errors polls), Tests 4 failed | 23 passed (27); (2) predicate spelled summary.ok === 0 -> 'every row skipped keeps succeeded' red; (3) guard spelled !summary.cancelled (ignores the late re-checks) -> the late-cancel control red; (4) `&& summary.total > 0` deleted -> 'a job with no rows keeps succeeded' red. The first attempt at (4) used an empty --replacement; the tool refused it before running anything (no-op, declared), re-run with --delete. Package: pnpm --filter @objectstack/rest test at 2fa11634 (lock VERDICT command-exit 0): 342/342 files, 6701 passed, 326 skipped; the later commit 2c10836a touches only the dogfood test. pnpm --filter @objectstack/rest typecheck and pnpm --filter @objectstack/dogfood typecheck at 2c10836a: exit 0 (lock VERDICT command-exit 0, with the dogfood import-reference-exposure file 11/11). eslint packages/rest --no-inline-config: exit 0.",
      "gates": "dispatch-gates --commands at 2c10836a: 63 families. 61 exit 0 in the loop; check:dts-closure exited 1 once while the dogfood closure build was rewriting packages/metadata/dist (declarations missing mid-build), re-run after the build: exit 0, 219/219 declaration files; check:type-check-debt timed out in the loop waiting on the lock, re-run under os-verify-lock: VERDICT command-exit 0, OK 123/126 packages, re-measure 0 entries; check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package's dist), NOT MEASURED locally, carried by CI Lint & Repo Gates. --ran: 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN. PR check-runs at 2c10836a: 49 runs, 36 success, 13 skipped, 0 failure; Lint & Repo Gates success, TypeScript Type Check success, Test Core success, Build Core success, Governed Surface Queue Guard success, Check Changeset success. Open-PR overlap: none of the 35 open PRs touches data-action-routes.ts, import-job-integration.test.ts or import-row-cell-errors.test.ts (REST read of each PR's files), so the merge-tree check has no pair. origin/main merged once (138a6202) before pr_create.",
      "dogfood_battery": "2c10836a · os-verify-lock VERDICT command-exit 0 (held 31m29s, waited 6m03s; closure build + pnpm --filter @objectstack/dogfood test, no shard, no path) · Test Files 355 passed | 3 skipped (358) · Tests 2867 passed | 20 skipped (2887) · no failures. Earlier run at 2fa11634 was red: 2 failures, both test/import-reference-exposure.dogfood.test.ts 'jobs: a withheld target answers a cell naming its record as one naming none' (admin, member): its poll returned only on succeeded and the all-failed job now reads failed. Fixed in 2c10836a (stale fixture, consequence of this change), then the full battery re-ran at the final head as above.",
      "line_budget": "5 files, +175 / -7 vs the merge base with origin/main (data-action-routes.ts +21/-1, tests +134/-6, changeset +19); under the 3000-line human-merge threshold",
      "files_changed": [
        "packages/rest/src/data-action-routes.ts",
        "packages/rest/src/import-job-integration.test.ts",
        "packages/rest/src/import-row-cell-errors.test.ts",
        "packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts",
        ".changeset/27676-import-job-every-row-failed.md"
      ],
      "deviations": [
        "File surface: the claim names data-action-routes.ts, import-job-integration.test.ts and .changeset/*; two stale fixtures outside it were updated because this change made them red (packages/rest/src/import-row-cell-errors.test.ts, packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts). The seat should add both to the claim's file surface. No packages/spec touch.",
        "PR body is written once and was opened at 2fa11634, before the dogfood fixture fix: it does not list import-reference-exposure.dogfood.test.ts and its Verification section points at this report for the battery. Suggested seat edit: add under What changes / Tests: 'packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts: the async poll now returns on succeeded or failed and asserts failed exactly when no row is ok (stale fixture, consequence of this change).' and the battery line from dogfood_battery.",
        "The late-cancel pin first raced a fixed 50 ms settle under full-suite load; it now waits on the worker's own results write (2fa11634). Declared in the PR body.",
        "Harness-written Co-Authored-By trailer with a model name was not used; commits carry the model-free pair."
      ],
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (repository_dispatch -> fleet-write run 40973934714 -> POST /repos/objectstack-ai/objectstack/pulls, draft, no assignees, no reviewers, no labels sent); this os-dev-report via scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/27676/comments). git push is not counted.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: #27734 · the ImportJobStatus / ImportJobProgressSchema.error docblocks in packages/spec/src/api/export.zod.ts still say succeeded admits per-row errors and failed means a fatal error; spec seat card, noted, not filed",
        "none other"
      ]
    }
  7. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    Claim amendment to 6509513333 · 2026-10-30T00:59Z · File surface increment, declared

    The dev's os-dev-report 6512268257 names two test files outside the claim's File surface that this change made red, updated in the same PR as .claude/agents/os-dev.md requires (stale pins are fixed where the behaviour changes). The claim's File surface adds them:

    • packages/rest/src/import-row-cell-errors.test.ts (an existing pin that read succeeded for an all-rows-failed job)
    • packages/qa/dogfood/test/import-reference-exposure.dogfood.test.ts (a dogfood poll that waited for succeeded on an all-rows-failed job)

    Everything else in the claim is unchanged.

  8. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    ACCEPT → PR #27795 · head 2c10836a · 2026-10-30T01:17Z · landing sent (ruling B 6509036166; contract review PASS 6512470441). Follow-ups: #27839 (test robustness), #27734 (spec docblocks, spec lane).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions