Repository navigation
refactor(service-automation): move the DAG traversal bodies into dag-traversal.ts (#25222 slice 3) - #25531
Conversation
…traversal.ts The bodies of eight AutomationEngine methods move unedited to the new module src/dag-traversal.ts: executeNode, traverseNext, runRegion, executeWithTimeout, celScope, valueEnvelopeRefusals, evaluateValueEnvelope and evaluateCondition. Each class method keeps its name, docblock, signature and position, builds a DagTraversalHost literal holding exactly the members its body reads, and calls the body; an async method's stub drops `async` and returns the moved function's promise. The two module-level helpers only those bodies read (runUserOf, decisionTakesEveryBranch) and the import lines only they read move with them. Four engine.ts declarations the moved bodies share with the code that stays (FlowSuspendSignal, isSuspendSignal, FlowRefusalSignal, isRefusalSignal) gain `export` in place; index.ts re-exports by name, so the package entry is unchanged. The #10243 grep pins in flow-activation-ledger.test.ts read the new module too. Claude-Session: https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE 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 — 6 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 e92ff0b87076f02c9a83a1b2376bbbb4dcfa8f28 && git checkout e92ff0b87076f02c9a83a1b2376bbbb4dcfa8f28
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b8c959013b1ed5b9c6767994d3eb805b5c45b13e 96ba7f24233dc24922f53077d47968a0a046d28d && git checkout -B drift-repro b8c959013b1ed5b9c6767994d3eb805b5c45b13e && git merge --no-ff 96ba7f24233dc24922f53077d47968a0a046d28d
node scripts/docs-audit/affected-docs.mjs --json b8c959013b1ed5b9c6767994d3eb805b5c45b13e
|
席位核对 · ACCEPT · 2026-10-23T20:47Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25222
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
Slice 3 of the
engine.tssplit: the DAG traversal ofAutomationEngine. The claim is 6373476307. The bodies of eight methods move, unedited, to the new modulepackages/services/service-automation/src/dag-traversal.ts:executeNode,traverseNext,runRegion,executeWithTimeout;celScope,valueEnvelopeRefusals,evaluateValueEnvelope,evaluateCondition.The change is move-only, and the card stays open for the later slices. Branch:
claude/project-thread-nirtwt, the harness-pinned branch for this session, restarted frommainatf38832be(slice 2's merge, #25520). The branch name has no issue number, so the claim comment is the identity record.Scope
retryExecution,executeWithoutRetry,seedDeclaredVariablesandseedRunVariablesgo in slice 4, on its own claim.this.FIELD.delete/insert/update(sites in 21 methods is pending with Sales. This slice moves 0 of them, because none of the eight bodies is among the 21.check-tenant-audit-censusandcheck-system-context-censusgive byte-identical output at base and head (see Readers).runRegion,evaluateValueEnvelopeandevaluateCondition, so the inferred-return-type.d.tstrap from [maintainer] refactor(metadata-protocol): splitsrc/protocol.ts(33,065 lines, +10,169 in 30 days) along itsObjectStackProtocolImplementationseams, one move-only slice per PR #25467 does not apply.What stays in
engine.ts.d.ts.recordLog,finishRefusedRunandcompactStepsForHistory;validateNodeInputSchemasandsetNodeError;renderRefusalMessage,renderFlowTextSlotandtranslatedFlowTemplate, which the spec translation ledger cites;The shape (slices 1 and 2, one host interface)
DagTraversalHosttypes each of its 11 members as the class member itself (AutomationEngine['m']), so a renamed member is a compile error. There is no cast, and no member becomes public or protected.thisas thePickofDagTraversalHostover exactly the members its own body reads. The generator read those members off each body's AST, in first-read order. A body that reads no member (executeWithTimeout,celScope,valueEnvelopeRefusals) is called directly, without a host.(...a) => this.m(...a), so a spy or test double set on the instance is still the one called. Recursion stays inside the class:executeNodeandtraverseNextreach each other through the host's closures.logger, assigned only in the constructor, and thenodeExecutorsmap, assigned once at its declaration. Neither is reassigned anywhere in the repo. So a host's field is the engine's field, including where an async body reads it after anawait.executeNode,traverseNext,runRegion,executeWithTimeout) dropasyncand return the moved function's promise, as [maintainer] refactor(plugin-security): splitsrc/security-plugin.ts(10,746 lines, +2,803 in 30 days) along itsSecurityPluginseams, one move-only slice per PR #25223 did. The caller gets the very promise the body makes, with no extra await and no extra microtask. Droppingasyncis the only edit to those four signature lines, and the.d.tsdoes not printasync.this., so the moved lines are unedited.Module-level declarations and imports
runUserOf, read byevaluateCondition. It is exported from the module (one word added), so the two{@link runUserOf}that stay inengine.tsresolve throughimport { type runUserOf }, as [maintainer] refactor(plugin-security): splitsrc/security-plugin.ts(10,746 lines, +2,803 in 30 days) along itsSecurityPluginseams, one move-only slice per PR #25223 cut 6 did.decisionTakesEveryBranch, read bytraverseNext.FlowSuspendSignal,isSuspendSignal,FlowRefusalSignalandisRefusalSignaleach gainexport, as [maintainer] refactor(plugin-security): splitsrc/security-plugin.ts(10,746 lines, +2,803 in 30 days) along itsSecurityPluginseams, one move-only slice per PR #25223 did.index.tsre-exports./engine.jsby name, so the package entry is unchanged; the export lists and the.d.tsbelow prove it../engine.js. It imports those four,DEFAULT_BRANCH_LABEL(already exported) andAutomationEngineby value. The class is imported only for its public staticMAX_NODE_REENTRIES, read inexecuteNode's runaway guard, as [maintainer] refactor(plugin-auth): splitsrc/auth-manager.ts(10,338 lines, +1,867 in 30 days) along itsAuthManagerseams, one move-only slice per PR #25224 did for statics. Each of these names is read only inside a function body, at call time, never while a module is evaluating. So the import cycle with./engine.jsis safe under ESM live bindings, and the tsup bundle is one file and has no cycle.createEvalUser,structuralConditionRefusal,FlowValueSlotSchema/VALUE_ENVELOPE_REFUSAL(the service-automation: evaluate a value-role CEL envelope in the assignment executor and validate it at registerFlow — the executor half of #14149 (spec half landed in PR #15113) #15137 comment),ExpressionEngine/validateExpression(the static-import comment),readPartialSteps, andisRegionSuspensionRefusal/refuseRegionSuspension.FlowEdgeParsed,findRegionEntry,isGuardRefusalandattachCarriedRefusal.Proof of move-only
git diff --color-moved=plain --color-moved-ws=allow-indentation-change f38832be HEADover the two files): 925 of the 938 deleted lines and 950 of the 1,122 added lines are detected as moved.runUserOf's head, which gainsexport; the four signal declarations' heads, which gainexport; and the four async methods' heads, which dropasync.engine.ts, the 12 replacement lines (the narrowed imports, theexportheads and the heads withoutasync), the module import with its comment, and the eight stub bodies; in the module, the license header, the module docblock, the imports,runUserOf'sexporthead,DagTraversalHostwith its docblock, one pointer docblock per function and the rewritten signature heads. The rest are lines such as a blank or a closing brace that git does not pair with a moved block.engine.ts, headengine.tsand the module):engine.ts(the script reads 1,059, counting the empty string after the final newline into the last run): the eight method texts de-indented one level, the two helpers with their docblocks, and the moved import blocks with their comments. It names each of the other 83: 35 for the license header, the module docblock and the authored imports; 3 single-line imports moved verbatim (runs shorter than the script's three-line minimum);runUserOf's head withexport; 23 for the host docblock, the interface and the blank after it; 8 pointer docblocks; and 13 signature lines (eightexport functionheads and fivethisparameter lines, each aPickof the host).engine.tsis in the module, apart from the 13 lines above (938 deleted, 33 of them blank).engine.tsare found nowhere in base: the narrowed imports, the module import block, the fourexportheads, the four heads withoutasync, and the stub lines. The other 7 are lines such aslogger: this.logger,and};that base already contains elsewhere.engine.ts), so de-indenting changed no string value. The two helpers and the import blocks moved at their own indentation..d.ts: see the next section.exportinside the module graph only:index.tsre-exports./engine.jsby name, so none of them reaches the package entry. The bundle JS bytes differ because code moved within the bundle.@objectstack/service-automation's suite passes the same at base and head, 199 files and 2628 tests..d.tsreading (ruling 6348891160, item 2)Base is
dist/built atf38832bein this container, after a fullturbo run build(77 of 77 tasks). Head isdist/built at this PR's commit.index.d.tsandindex.d.cts(identical to each other on both sides): base1a5162a4dd929399460a3936e22747a74cf0a8db24d398ffbab5d7ead7bf212e, head1a5162a4dd929399460a3936e22747a74cf0a8db24d398ffbab5d7ead7bf212e.0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232, head0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232.index.d.tsbase vs head: 0 lines (19,626 lines each side). This slice reorders no union, so the normalised diff is empty too.node -e 'const s=require("fs").readFileSync(process.argv[1],"utf8");process.stdout.write(s.replace(/"[^"\n]*"(?:\s*\|\s*"[^"\n]*")+/g,r=>r.split(/\s*\|\s*/).sort().join(" | ")))' dist/index.d.ts | sha256sumindex.d.ts, line 4764, the private member whose body moved, one line each, then the same normaliser:private executeNode;made public (executeNode;) givesc779de31f261a001dedd376fa77768604dba9074b8abb5238ba902a67dad5f0b.private executeNodeRenamed;) gives844c29245b716d852a5a6e3bd5a111f3bb51b43956e9ada4aa6d9dc3a7813980.0df32585…, so the normaliser hides neither kind of change.Readers re-pointed
src/flow-activation-ledger.test.ts, the Decide whether POST /api/v1/automation/:name/toggle belongs in the manage_metadata write set — it mutates flow enablement with no authoring capability #10243 grep pins:ENGINE_SOURCESgains./dag-traversal.ts, so a moved body is not a place the retiredflowEnabledmap could come back unseen.Readers examined and not re-pointed:
check-tenant-audit-census(derived): output byte-identical to basef38832be, 238 write call sites certified (162 decidable; 2 provably without tenant context, 29 more unreadable), 23 prose figures held, everyUNTYPED_RECEIVERSrow matched. Its--self-testoutput is identical too. No moved body holds athis.FIELD.delete/insert/update(site.check-system-context-census(derived): output byte-identical, 149 elevation read sites in 22 packages across 76 files, in 128 symbols.check:liveness(spec lane, not derived, run by hand aspackages/spec'stsx scripts/liveness/check-liveness.mts): output identical at base and head, 753 (entry, cited file) pairs, 752 anchored, 1 exempt, and 982 of 982 symbol anchors resolve. No liveness row cites a method this slice moves.flow.jsoncites#executeWithoutRetryand#retryExecution, which stay (slice 4).docs/qa/platform-checklist/areas/automation.json:818anchorsengine.ts#runRegion. The method stays as a shell, and thepartialStepsthe anchor's note names is a parameter of its signature, which stays.check:platform-checklist(not derived, run by hand): output identical at base and head, 987 of 997 symbol anchors resolved, the same 10 on the named [finding] the platform-checklist corpus resolves symbol anchors with its OWN rule, not the shared resolver — a permissive token match where the ruling says there is to be exactly one implementation #16898 residual.check-adr-symbol-anchors(not derived, run by hand): output identical at base and head, 2384 anchors across 142 records resolve. The two ADR symbol anchors into this file,#variables(ADR-0072) and#canonicalizeStoredFlow(ADR-0087), resolve on declarations that stay.check:adr-anchors(decision letters, run by hand):engine.tsis not on the anchored surface. 1969 decision-letter citations at base and at head; the only difference in the output is the scanned file count, 6674 to 6675, the one new file.check:startup-registry-verdict(not derived, run by hand because its docblock names this file): 46 seams, 40 read-only, none recording a verdict, at base and head. The scanned file count is 3446 at base and 3448 at head: the new module, andpackages/spec/src/migrations/registry.ts, a git-ignored build output present only in the head worktree.text-slot-template.test.tswalks all ofsrc/, so it already reads the module.builtin/node-config-contract-ledger.test.tsreads onlysrc/builtin/. The other test files that nameengine.tsname it in prose only.path:linecitation in the repo points into the moved ranges of baseengine.ts(the module-level lines:16to:180, and the bodies:11047to:12294). Lines ofengine.tsafter base:180move up by 54, and lines after base:12294move up by 874. The one liveservice-automation/src/engine.ts:44string, inpackages/spec/scripts/liveness/evidence.test.ts, is a realm-attribution fixture thatscanEvidenceparses without reading the file.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this commit derives 59 families from the 3 changed paths (2,062 changed lines, under the 3,000-line human-merge threshold). All 59 ran in this container on this commit, each exit code captured before any pipe, and all 59 exit 0.dispatch-gates.mjs --ranreconciles: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.check:liveness,check:platform-checklist,check-adr-symbol-anchors,check:adr-anchorsandcheck:startup-registry-verdict, with the readings in the readers section above.@objectstack/service-automation'svitest run: 199 of 199 files, 2628 of 2628 tests, the same at base and head.Acceptance notes
retryExecution,executeWithoutRetry,seedDeclaredVariables,seedRunVariables) is claimed after this PR merges. The 33-site ruling decides the slices after that one.🤖 Generated with Claude Code
https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE