fix(service-job,service-automation): map a degraded job outcome to sys_job_run.status instead of success (#5548) - #7446
Conversation
…s_job_run.status instead of success (#5548)
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also reference the affected code. These are read-only:
|
… to assertEngineUpdateDispatch (#5548)
Fixes #5548
The services half of the 2026-08-08 maintainer ruling (B-minimal). The producer half shipped as #6617 (
JobRunOutcomeonJobHandler) and the consumer vocabulary as #7072/#7340 (degradedinJobExecutionStatusand bothField.selectsites); the adapters still discarded the resolved value, soDbJobAdapterdecided a run's outcome purely by whether the handler threw.The ruling, quoted verbatim and untranslated:
What changed
packages/services/service-jobDbJobAdapter.wrap()reads the handler's resolved value.{ outcome: 'degraded', reason? }landssys_job_run.status: 'degraded'with the reason inerror;undefinedand{ outcome: 'completed' }keep landingsuccess; a throw keeps landingfailedand retrying.bumpJob()mirrorsdegradedontosys_job.last_status/last_errorand leavesfailure_countflat —degradedis not a failure, so the failure/alerting signal must not move (contract,job-service.ts).IntervalJobAdapter/CronJobAdaptermap the same third state onto the in-memoryJobExecution. Without this,DbJobAdapter.getExecutions()(which delegates to the inner adapter) would reportsuccessfor the very run whose persisted row saysdegraded.replay()'s synthetic row is settled from the execution the inner adapter just recorded, so a replayed degraded run no longer writes onedegradedrow next to onesuccessrow.IJobService.triggerresolvesvoidby contract, so the outcome cannot travel back through it.packages/services/service-automationwaitnode's timer wake-up — the ruling's named acceptance case — adopts the channel: a shot that fires into an unreachable suspended-run store resolves{ outcome: 'degraded', reason: 'STORE_UNAVAILABLE' }. It still does not throw and still keeps the one-shot ARMED with itssys_jobrowactive, so wait 定时唤醒 job 在 resume 没能消费掉暂停时也会自我取消 —— store 短暂不可达即丢掉这一次唤醒,run 挂到下次重启才被捞回 #5529 is untouched. Thereasonis the short code only; the driver's own (possibly multi-line) message stays in the log record'smetaper finding(service-automation): builtin/wait-node.ts 里还有五处外来 cause 插进日志 message —— 其中三处是 #4632 亲自标为 error 的耐久性诊断,且已实测被切碎 #5737.@objectstack/service-jobadded as a devDependency so the end-to-end regression can drive the real adapter instead of a spy.Two judgement calls the card left open, and why
reasonlands — the existingerror/last_errorcolumns, no new column. This is not a fresh decision: it was taken at thedomain:servicesseat on Job status vocabulary has nodegradedvalue — #5548's services half cannot compile or persist without wideningJobExecutionStatus+ the twosys_job*selects #7072 and is recorded in theJobExecutionStatusTSDoc, together with its honest cost (a column labelled "Error" carries a non-error note whenstatus === 'degraded', so a reader must gate on the status). Implementing anything else here would contradict a shipped contract comment.bumpJobmirrorsdegradedontosys_job.last_status— yes. The summary row is the operator's first stop, and leaving it onsuccesswould move the defect one table over rather than fixing it;sys_job.last_status's ownField.selectwas widened by feat(spec,platform-objects): adddegradedto the job status vocabulary (#7072) #7340 for exactly this. What deliberately does not move isfailure_count(and nothing about retry), which is the direct consequence of "degraded is not a failure".Verification
packages/services/service-job: 56/56 pass (13 new).packages/services/service-automation: 890/890 pass (3 new).pnpm --filter '@objectstack/service-job' typecheckclean;tsc --noEmiton service-automation reports only three pre-existingTS2341errors innested-region-parity.test.ts(untouched file, present onorigin/main; that package has notypecheckscript).git checkout origin/main -- PATH(nevergit stash):expected 'success' to be 'degraded'. The 6 that stay green are exactly the additivity/legacy cases plus the two timer-adapter cases (different file, still fixed) — the predicted direction.wait-node.tsremoved ⇒ 2 of the 3 end-to-end cases red (expected 'success' to be 'degraded'), success control still green. That is what proves the end-to-end pin exercises the producer adoption and not just the adapter.undefinedis asserted to still landsuccesswith a nullerrorand a flatfailure_count.scripts/pm/dispatch-gates.mjs:check:nul-bytes,check:docs-audit-scope,check:changeset-gate-self-tests,check-changeset-fixed,check-changeset-no-major— all green locally. ESLint clean on the changed files.Every rejection-class / status-class assertion reads the value written into the
sys_job_run/sys_jobcell — never "the handler was called" or "it did not throw", since that criterion is the defect itself.⛔ Out of scope by the dispatch's own boundary:
packages/specandpackages/platform-objectsare untouched (both halves already landed). Nothing further was found missing in either.Generated by Claude Code