Repository navigation
feat(service-automation): a run record says how long it waited (waitMs), so a resumed screen flow no longer reads its user's wait as work - #33511
objectstack-fleet[bot] wants to merge 2 commits into
Conversation
Adds an optional waitMs to the published run record (ExecutionLog) and fills it on every run-record writer: 0 for a run that never paused, the sum of every pause for a resumed run. The engine keeps one wait clock per suspension (activeMs, persisted as sys_automation_run.active_ms) and the terminal row carries wait_ms. Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 172 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 5be6ef95e0e5d23ae5be87224c5258a58b1b8e9b && git checkout 5be6ef95e0e5d23ae5be87224c5258a58b1b8e9b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e40cd1e28dd79e60e7f4be0db06f071f3f962574 50c4166cabb364947c6aab50739e07eb4eeefb17 && git checkout -B drift-repro e40cd1e28dd79e60e7f4be0db06f071f3f962574 && git merge --no-ff 50c4166cabb364947c6aab50739e07eb4eeefb17
node scripts/docs-audit/affected-docs.mjs --json e40cd1e28dd79e60e7f4be0db06f071f3f962574
|
…he restore Pins the summed wait on every run-record path: a run that never paused, one pause, two pauses across fresh engines over one store, a cancel where the run waits, a stranded resume restored by an operator, and a suspension parked without a clock. A second group drives the object store over one table and reads the active_ms and wait_ms cells, so a mapper that never wrote a column cannot pass. Regenerates the authorable-surface baseline and the reference docs, and adds the changeset. Claude-Session: https://claude.ai/code/session_016WV4cxHwXNcGcAHNerZsom Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-elon-musk · session_016WV4cxHwXNcGcAHNerZsom Request: review · area A5 · head 50c4166 · 2026-11-14T23:49Z Contract review: Fixes #32284 under the maintainer ruling A (comment 6879411530). |
|
Contract-review-claim: session_016WV4cxDxAYxdtDhtR1kQQe · head 50c4166 · 2026-11-14T23:49:49Z |
Contract reviewServed-tier: review#2 (server area pilot) · area A5 · Fixes #32284 · maintainer ruling A (6879411530) · published-schema face. CI on this head at review time: 56 success, 10 skipped, 1 in progress, none failing. ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentRulings on the two questions from the os-dev report (6886115077):
Nits (none blocking):
Implemented-by: VERDICT: PASS Nits only, so this is the final round. Land once the last check is green. |
|
domain:server#5 · os-elon-musk · session_016WV4cxHwXNcGcAHNerZsom ACCEPT · area A5 · head 50c4166 · 2026-11-14T23:51Z
|
Requested by Elon · project thread
Fixes #32284
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: none — the added member is a field of the run record the platform answers; no metadata author writes it, and no skill teaches a run's timing
维护者速读
ExecutionLog,即运行详情与运行列表返回的那条)新增可选字段waitMs:运行挂起等待(screen / approval / wait 等节点)的总时长,多次暂停累加;durationMs含义不变(总耗时,含等待),实际执行时间 =durationMs - waitMs。durationMs: 18691,几乎全是用户在等,管理员分不清"流程慢"和"在等人"。sys_automation_run新增两列active_ms(暂停时已执行的时长,即等待时钟)与wait_ms(终态行的等待总和),旧行为空即"未记录"。回滚即 revert 本 PR,两列留空无害。packages/services/service-automation/src/run-resume.ts(恢复时结算等待、再次暂停时把时钟前移);packages/services/service-automation/src/engine.ts(SuspendedRun.activeMs、waitOfPausedRun、activeMsAtPause);packages/services/service-automation/src/sys-automation-run.object.ts(两列新增,引擎自有对象上的存储面变化)。pnpm --filter @objectstack/service-automation exec vitest run src/run-wait-time.test.ts,预期全绿;其中两次暂停的用例断言终态waitMs等于两段等待之和,且durationMs - waitMs等于执行时间。sys_automation_run新增的两列属于裁决 A 的范围(该对象的代码注释把加列列为人工裁定面)。What changes
@objectstack/spec:ExecutionLogSchemagainswaitMs?: number(int, min 0) besidedurationMs, exactly the one optional member ruling 6879411530 (option A) allows.durationMskeeps its meaning (total wall time, pauses included); its describe text is untouched. Readers derive active time asdurationMs - waitMs.@objectstack/service-automation: every run-record writer fills it.execute()and the retry pathexecuteWithoutRetry()): completed, failed, refused and the firstpausedentry carrywaitMs: 0.SuspendedRun.activeMs: the time the run had spent executing when it paused. Nothing executes while a run is parked, so at any later instant the run's total wait is its age minus that number. That is what sums every pause of a multi-stage run without a list of intervals: the resume computeswaitMsonce, at the moment it wins the advance claim (waitOfPausedRun), and a re-suspend writes the clock forward (activeMsAtPause).resumeInternal): completed, refused, stranded (failed) and re-suspended records carry the summed wait. A resume refused before the claim (lost claim, invalid screen input, a subflow child still parked) consumes nothing, so the run keeps waiting on the same clock.cancelRun,failSuspendedRun,settleUnresumableSuspension): everything since its pause counts as wait.consumed-suspension-restore.ts): the re-armed snapshot keeps the clock of the pause it consumed, so the failed attempt, the time stranded and the wait to the next resume count as one pause. That matches the snapshot's step log, which ends at the pause.sys_automation_rungainsactive_ms(paused rows, and the consumed snapshot on a stranded terminal row) andwait_ms(terminal rows).ObjectStoreSuspendedRunStorewrites and reads both; the in-memory store round-trips them by JSON clone;runRecordToLogEntryserveswaitMsafter a restart.packages/spec/authorable-surface/automation.json(+1 key) andcontent/docs/references/automation/execution.mdx, fromcheck:generated --fix.authorable-surface.base.jsonis untouched.minorfor both packages, with both declaration lines.Decisions taken on the dispatch's axes (correctness for an admin first, then smallest published surface, then effort)
0for a run that never paused, absent for "not recorded". The dispatch's default was absent; the neighbouring run-level totals say otherwise.FlowRunSummary.failedand.unmeasuredare written as0when measured and are absent only when not tracked ("absent = not tracked (an older run), which is not the same as zero"). Following that:waitMs: 0is a measured fact, and absent covers amissedentry (nothing ran,durationMsis absent too), a run paused before this release, and a host-supplied store that does not round-trip the clock. With absent-for-never-paused, those unrecorded cases would read as "never waited", which is the 18.7 s misreading this card exists to fix; and every reader would needwaitMs ?? 0, a consumer fallback.started_at/start_time, as the engine's ownsuspendedRunToLogEntrydocblock states ("a suspension row records no pause instant").created_atis the first pause only,updated_atis moved by any re-save of the same pause (a subflow parent's screen re-persist), and the in-memory default store has neither. So the clock is one number per suspension (active_ms) plus the published total on the terminal row (wait_ms). Two columns rather than one keep the stranded row's consumed-snapshot clock apart from the failed record's own wait, so a restore after a restart still sums correctly.Tests
This body is written once, at create; every reading taken after it (suite VERDICT lines, typecheck, the generated-artifact check, the derived gates and the ablation) is in the os-dev report comment on #32284, each naming the head it was taken on.
packages/spec/src/automation/execution.test.ts:waitMskept through a parse besidedurationMs, absent stays absent (never defaulted to0), negative and fractional values refused at['waitMs'].packages/services/service-automation/src/run-wait-time.test.ts(new; time driven withvi.setSystemTime, Date only): a run that never paused readswaitMs: 0; one pause resumed later reads exactly that wait; two pauses sum; cancel and restore paths; each record parsed throughExecutionLogSchema, so an undeclared member would be stripped and the case would go red.packages/services/service-automation/src/suspended-run-store.test.ts(new top-level describe on the sharedcreateFakeEngineharness): a two-pause run driven across three engine instances over onesys_automation_runtable sums both waits;active_msandwait_msland in their columns and read back; a row without the columns reads "not recorded".Acceptance notes
AutomationResult(the trigger and resume responses) is unchanged: it keeps itsdurationMsand gains nothing, because a second published member is outside the ruling. The run record (getRun,listRuns) is wherewaitMsis read.durationMsnorwaitMs:active_msis a clock, not a pause instant, so the docblock's "a suspension row records no pause instant" stays true.metaare unchanged.content/docs/automation/flows.mdxrun-history section could namewaitMs; it is outside this PR's file surface.Verification
In the os-dev report comment on #32284 (local subset, reconciled with
dispatch-gates --ran), and in this PR's check-runs.Seat:
domain:server#5 · os-elon-musk · session_016WV4cxHwXNcGcAHNerZsom, dispatched to an os-dev subagent on claim 6885364290.🤖 Generated with Claude Code