Skip to content

refactor(service-automation): move 42 engine bodies into five seam modules (#25222 slice 7) - #25597

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt
Oct 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

Part of #25222
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160

This is slice 7 of the engine.ts split. Its claim is comment 6378948491, narrowed by one body in comment 6379054858. It follows the seat's shape call A: one PR, five seam modules. The bodies of 42 AutomationEngine methods move, unedited, into five new modules under packages/services/service-automation/src/:

Module Host Bodies What they do
dispatch-claims.ts DispatchClaimsHost 7 claim, claimInProcess, settleDispatch, readDispatch, nextRunId, warnAdvanceClaimDegraded, releaseSuspension: claiming, settling and reading a dispatch, minting a run id, announcing a degraded advance claim, releasing a suspension
flow-definitions.ts FlowDefinitionsHost 12 resolveFunction, registerConnectorProvider, canonicalizeStoredFlow, canonicalizeFlow, unregisterFlow, recordFlowRefusal, getFlowRuntimeStates, describeUnboundReason, getTriggerBindingAudit, flowCredentialHoldings, hookSecretResolver, rollbackFlow: canonicalising, unregistering, refusing and rolling back a flow; its runtime states and trigger bindings; its function, connector-provider and credential surfaces
subflow-guards.ts SubflowGuardsHost 11 describeDisabledFlow, subflowTargets, packagedSubflowCallers, parkedRunsOf, refuseUnderReachingCallers, disabledPackagedSubflows, ledgerDisabledChainReaches, refuseEnableOntoDisabledSubflow, describeDisabledSubflows, rejudgeSubflowCallers, refuseCustomerAuthoredToggle: finding and describing disabled or packaged subflows and their callers, and refusing what would reach a disabled subflow
consumed-suspension-restore.ts ConsumedSuspensionRestoreHost 6 rowSupersedesJournal, resolveConsumedSuspensionWitnesses, restoreConsumedSuspension, resolveRestoreChain, peekConsumedSuspension, inspectConsumedSuspension: resolving a consumed suspension's witnesses, and restoring, peeking at or inspecting one
node-text-rendering.ts NodeTextRenderingHost 6 unknownNodeTypes, warnIfNodeTypeVocabularyNeverSealed, setNodeError, renderRefusalMessage, renderFlowTextSlot, translatedFlowTemplate: the node-type vocabulary checks a run makes, a failed node's error variable, and rendering refusal messages and flow text slots

The change is move-only, and the card stays open for the later slices. The branch is claude/project-thread-nirtwt, which the harness pins for this session. It was restarted from main at ea6fe000 after slice 6 (#25567) merged. The branch name has no issue number, so the claim comment is the identity record.

Scope

  • The 33-site question. The ruling on the 33 this.FIELD.delete/insert/update( sites in 21 methods is still pending with Sales. This slice moves 0 of those sites, because none of the 42 bodies holds one. check-tenant-audit-census and check-system-context-census give byte-identical output at base and head (see Readers).
  • Return types. Each of the 42 methods declares its return type; the generator refuses one that does not. So no public body with an inferred return type moves.
  • Four bodies stay, because each reads a name that engine.ts documents as private, and moving it would mean exporting that name:
    • claimAdvance returns AdvanceClaim, whose docblock says "Module-private: it is a control-flow verdict, not a contract."
    • validateNodeInputSchemas throws InputSchemaViolationError, whose docblock says "Not exported on purpose — callers read the ADR-0112 code off the result, never the throw."
    • refuseInvalidScreenInput and buildSubflowResumeSignal (slice 6's two) read ENGINE_BUILT_SIGNAL / engineBuilt. docs/adr/0019-approval-as-flow-node.md calls it "a module-private symbol", and its docblock says "Module-private and symbol-keyed".
  • pullConnectorSource stays too (the claim amendment, comment 6379054858). connector-pull.ts already has a top-level pullConnectorSource, which index.ts re-exports. With the method's body moved as well, the bundle held two top-level functions of that name, and the bundler renamed the public one: both bundles exported pullConnectorSource2 as pullConnectorSource, so its .name changed. That would be an observable change to a public export, so the body stays. On this head, every exported function's .name matches base in both bundles (see Bundles below).
  • None of the other stays moves. The remaining-bodies table (comment 6376268510) marks these as stays:
    • for liveness, retryExecution and getConnectorDescriptors;
    • for private statics, resolveCanonicalDescriptor, resolveResumeAuthority and resolveEffectiveSuspension. A moved function reading a private static would not compile, and tsc passes;
    • for the startup-registry gate, knownNodeTypes, getUnknownNodeTypeAudit, sealNodeTypeVocabulary and warnUnknownNodeTypes.
  • Size. 2,406 changed lines, under the 3,000-line bar: engine.ts +365 / -771, the five modules +1,268, and the test 1 / 1. check-governed-merges --branch on this commit says NOT governed, 0 of 7 paths on the register.

What stays in engine.ts

  • Every moved method keeps its name, docblock, signature and position in the class, because tsup emits each member, private ones included, into the published .d.ts.

The shape (slices 1 to 6)

  • The host interfaces. Each module's host holds exactly the members its bodies read, each typed 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.

    • Each moved function types this as the Pick of its host over exactly the members its own body reads, read off each body's AST in first-read order.
    • Six bodies read no member and are called directly, without a host: nextRunId, subflowTargets, describeDisabledSubflows, rowSupersedesJournal, unknownNodeTypes and setNodeError.
  • Methods are passed as late-bound closures, (...a) => this.m(...a), so a spy or test double set on the instance is still the one called.

  • Six fields go through a getter, in the store form ([maintainer] refactor(plugin-security): split src/security-plugin.ts (10,746 lines, +2,803 in 30 days) along its SecurityPlugin seams, one move-only slice per PR #25223 cut 1, PR refactor(plugin-security): move SecurityPlugin's leaf readers into security-plugin-object-inputs.ts (#25223 cut 1) #25290), because a method reassigns each. The write sites are engine.ts at the base ea6fe000:

    • flowDispatchStore: setFlowDispatchStore (:2778);
    • functionResolver: setFunctionResolver (:3962);
    • flowCredentialSource: setFlowCredentialSource (:4746);
    • store: setSuspendedRunStore (:2769), and the constructor (:2745);
    • nodeTypeVocabularySealed: sealNodeTypeVocabulary (:9424);
    • i18nServiceSource: setI18nServiceSource (:4769).

    For each one, the stub builds const readX = () => this.X; and the host member get X() { return readX(); }. Each read in the body reads the engine's current value, as it did inside the class.

  • Four warned-once flags go through a get/set pair ([maintainer] refactor(plugin-auth): split src/auth-manager.ts (10,338 lines, +1,867 in 30 days) along its AuthManager seams, one move-only slice per PR #25224 cut 10, PR refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508), because a moved body sets each. The stub adds const writeX = (v: AutomationEngine['X']) => { this.X = v; }; and the host member set X(v) { writeX(v); } beside the getter, and the host interface member is not readonly. So each write lands on the engine, as it did through this.

    • git grep -nE "(dispatchClaimDegradationWarned|dispatchOutcomeDegradationWarned|advanceClaimDegradationWarned|nodeTypeSealOmissionWarned)" over the whole repo at the base finds 13 lines, all in engine.ts:
      • the four declarations, each = false: nodeTypeSealOmissionWarned (:2540), dispatchClaimDegradationWarned (:2725), advanceClaimDegradationWarned (:2734), dispatchOutcomeDegradationWarned (:2741);
      • one {@link} in a docblock (:2544);
      • the four writes, each in the one moved body that sets it: claim (:2815), readDispatch (:2911), warnAdvanceClaimDegraded (:3310), warnIfNodeTypeVocabularyNeverSealed (:9481);
      • the four reads just above those writes, in the same bodies.
    • The constructor writes none of them, and no other file names any of them.
  • Fifteen fields go by value: logger, inProcessDispatchClaims, nodeExecutors, connectorProviders, flows, flowRefusals, flowShadowing, boundFlowTriggers, policyDisabledFlows, subflowDeclinedFlows, triggers, flowVersionHistory, flowLedgerDisabled, flowStatusDisabled and consumedSuspensions. The seat asked for proof that none is reassigned after construction:

    • git grep -nE "this\.(FIELDS)\s*(=[^=>]|\?\?=|\|\|=|&&=)" -- packages/services/service-automation/src, with the fifteen names, finds 1 line, in the constructor: this.logger = logger; (:2744).
    • Across the repo, the same pattern on any receiver (\.(FIELDS)) for the fourteen names other than logger finds nothing. The bracket form, ['name'] followed by = or ??=, finds nothing for any of the fifteen. And \.(flows|triggers|logger)\s*(=[^=>]|\?\?=) on any receiver in the package finds only that constructor line.
    • The bodies change the collections' contents in place, on the engine's own Maps and Sets, and never rebind the fields.
  • Async. The stubs of the nine async methods drop async and return the moved function's promise, as in slices 3 to 6.

    • The caller gets the very promise the body makes.
    • Dropping async is the only edit to those signature lines, and the .d.ts does not print async.
  • Parameter defaults. canonicalizeStoredFlow and canonicalizeFlow take options: { includeRetired?: boolean } = {}.

    • The class stub keeps that default exactly as written, so a caller that omits options still gets {}.
    • The moved text keeps it too, token-identical, because the moved lines are unedited. That default is now dead: the stub always passes options, already defaulted. It is disclosed here, not edited.
  • The moved bodies keep saying this., so the moved lines are unedited.

Module-level declarations and imports

  • Seven helpers gain export, in place. The moved bodies read them, so each module imports them from ./engine.js. Nothing else about them changes: the same line, the same docblock, the same position. index.ts adds no re-export, and the package's export map is . only, so none of them is reachable from outside the package. The signatures at head:
    • export const CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION: readonly string[] = [ (read by canonicalizeFlow)
    • export const MAX_RESTORE_CHAIN_HOPS = 32; (read by resolveRestoreChain)
    • export function chainEntryOf(runId: string, result: SuspensionRestoreResult): ChainRestoreEntry { (read by restoreConsumedSuspension)
    • export function flowRegistrationTodo(flowName: string, site: RegistrationCheckSite): ConversionTodoNotice { (read by canonicalizeStoredFlow)
    • export function graftConditionEnvelopes(converted: unknown, parsed: unknown): unknown { (read by canonicalizeFlow)
    • export function negotiatedServiceLocale(service: RefusalI18nService, requested: string): string { (read by translatedFlowTemplate)
    • export function nestedChildRunId(correlation: string | undefined): string | undefined { (read by resolveRestoreChain)
    • None of their docblocks says the name is private. Two other files place CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION "in service-automation/src/engine.ts" (metadata-core's artifact-forward-conversion.ts and metadata-protocol's protocol.ts), and it stays there.
    • The cut-6 trap. flowRegistrationTodo's signature names types from other files (RegistrationCheckSite, ConversionTodoNotice). On this PR the rolled-up .d.ts gains no declaration and moves none; its only raw change is 87 reordered string-literal unions, each shown below as a swap.
    • The ESM and CJS export lists are identical at base and head (39 names each, ESM equal to CJS on each side). They are listed in the .d.ts section.
  • The modules' imports use the same specifiers as engine.ts's own imports. A comment above each value import from ./engine.js says the value is read only inside function bodies, at call time, so the import cycle is safe under ESM live bindings, as in slices 3, 5 and 6. The tsup bundle is one file and has no cycle.
  • Import edits in engine.ts.
    • import { FLOW_HOOK_SECRET_KEY, flowCredentialClassLabel, flowCredentialPositions } from './flow-credential-projection.js'; moves, verbatim, to flow-definitions.ts: only moved bodies read those names.
    • The 6-line [#15788] comment and import { renderTextSlot, FlowTextTemplateError } from './builtin/template.js'; move, verbatim, to node-text-rendering.ts. The comment's claim ("template.ts imports only ../guard-refusal.js and package-external contracts, so nothing it pulls in reaches back here") is as true there as it was in engine.ts.
    • Three imports are narrowed to the names engine.ts still reads:
      • @objectstack/spec/automation keeps FLOW_RESERVED_NODE_TYPES and FLOW_STRUCTURAL_NODE_TYPES; FlowSchema, validateControlFlow and collectFlowGraphs go to the modules;
      • @objectstack/spec keeps CONVERSION_TODO_CODE and the types ConversionNotice and ConversionTodoNotice; applyConversionsToFlow goes to flow-definitions.ts;
      • @objectstack/spec/system keeps resolveBundleLocale; flowRefusalMessageKey goes to node-text-rendering.ts.
    • No engine.ts docblock {@link}s any of the names that left, so no link stops resolving. tsc with noUnusedLocals passes, so every name left in an engine.ts import is still read or linked.
    • engine.ts gains the five module imports, after the ./run-resume.js one, each with a comment naming its bodies.
  • Authored text. Each module's docblock and its host interface's docblock are new. Each host docblock names the method that reassigns each getter field and the body that sets each get/set flag.

Proof of move-only

  • Moved block. This is git diff --color-moved=plain --color-moved-ws=allow-indentation-change ea6fe000 HEAD over engine.ts and the five modules. It detects 752 of the 771 deleted lines and 892 of the 1,633 added lines as moved.
    • Deleted, not moved (19):
      • the signature heads of the nine async methods, each of which drops async;
      • the seven helper declaration lines, each of which gains export;
      • the three import lines that are narrowed.
    • Added, not moved (741): the modules' docblocks, imports and host interfaces; the 42 pointer docblocks and rewritten function heads; the five module imports in engine.ts and their comments; the narrowed imports; the nine heads without async; the seven export lines; the 42 stub bodies; and lines git does not pair with a moved block.
  • An independent check. A script compared base and head engine.ts against the five modules.
    • Module lines. 898 of the modules' 1,268 lines are in 43 verbatim runs of base engine.ts: the 42 method texts, de-indented one level, and the [#15788] comment with its ./builtin/template.js import. The script names each of the other 370:

      • 278 head lines: per module, the copyright line, the docblock, the imports and their comments, the host docblock and the host interface (10, 22, 15, 9 and 6 members);
      • the 42 pointer docblocks;
      • 50 signature lines: 42 export function heads, and 8 this parameter lines, each a Pick of the host.
    • Deleted lines. Every line deleted from engine.ts (771 in all, 9 of them blank) is among the modules' lines, apart from the 19 above. The deleted ./flow-credential-projection.js import, the [#15788] comment and the ./builtin/template.js import are among them, verbatim.

    • Added lines. Of the 365 lines added to engine.ts, 284 are found nowhere in base:

      • the five module import blocks (47 lines) and their comments (17);
      • the three narrowed imports and the seven export lines;
      • the nine heads without async;
      • the stub lines: 42 pointer comments, 36 host literals with their 36 calls, 35 method closures, 16 by-value members, 15 read/write closures, 15 getter and setter members, and 6 direct calls.

      The other 81, such as logger: this.logger, and };, are lines base already contains elsewhere.

    • Literals. No string or template literal in the 42 method bodies spans a line, checked on the TypeScript AST of base engine.ts. So de-indenting changed no string value.

  • Tests. @objectstack/service-automation's suite passes the same at base and head, 199 of 199 files and 2628 of 2628 tests.

.d.ts reading (ruling 6348891160, item 2)

The merge base is ea6fe000, the head of main, so the merge-base reading and the base reading are one reading.

  • Base is dist/, built at ea6fe000 in this container, in a worktree of that commit.
  • Head is dist/, built at this PR's commit.
  • The package's dependencies were built by turbo run build --filter=@objectstack/service-automation (31 of 31 tasks), and both sides resolve the same built dependencies.

The readings:

  • Raw sha256, index.d.ts and index.d.cts, which are identical to each other on both sides: base 1a5162a4dd929399460a3936e22747a74cf0a8db24d398ffbab5d7ead7bf212e, head deb08bdfb32c2a247b98780ad67162bd2938da46669e81af313526f69af6ecc4.

  • Normalised sha256, all four files: base 0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232, head 0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232.

  • Raw diff, index.d.ts base vs head: 174 lines, 87 per side, with 19,626 lines on each side. They are 87 one-line c hunks: each base line and its head line are the same line with one string-literal union's members in another order. Outside the union, each pair is byte-identical, and each union holds the same members on both sides.

  • The 87 pairs, which are five distinct swaps. All of them sit inside three declarations, SysAutomationRun, SysFlowDispatch and SysFlowCredential, which inline the spec's field and flow types:

    Member Union The swap Lines (base = head)
    type: 49 field types "secret" moves from 3rd to 6th, after "code" 5851, 6366, 13468, 13983, 15679, 16194
    dialect: "cron" | "cel" | "template" "template" moves from 3rd to 1st 72 lines: 5889, 5933, 5942, 5976, 6002, 6011, 6020, 6033, 6043, 6088, 6097, 6109, 6118, 6130, 6139, 6151, 6160, 6202, 6396, 6428, 6756, 6775, 6829, 6840, 13506, 13550, 13559, 13593, 13619, 13628, 13637, 13650, 13660, 13705, 13714, 13726, 13735, 13747, 13756, 13768, 13777, 13819, 14013, 14045, 14373, 14392, 14446, 14457, 15717, 15761, 15770, 15804, 15830, 15839, 15848, 15861, 15871, 15916, 15925, 15937, 15946, 15958, 15967, 15979, 15988, 16030, 16224, 16256, 16584, 16603, 16657, 16668
    type?: 6 action types "form" and "api" swap (2nd and 3rd) 6720, 14337, 16548
    type?: 49 field types "secret" moves from 3rd to 6th, after "code" 6747, 14364, 16575
    category?: 7 categories "flow" and "data" swap (1st and 2nd) 6858, 14475, 16686

    For example, line 5889 is dialect: "cron" | "cel" | "template"; at base and dialect: "template" | "cron" | "cel"; at head. A script checked each of the 87 pairs: it replaced each union with a placeholder and compared the rest of the line, and it compared each union's sorted members. All 87 are swaps, 0 are not.

  • The normaliser, the ruling's one-liner, verbatim:
    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 | sha256sum

  • Positive controls, each one line changed in the head index.d.ts, then the same normaliser:

    • Line 1865, private claimInProcess;, a private member whose body moved, made public (claimInProcess;): 196ae606dce0584f296ce860802b020ab52eb604cde3f4f09cb835f5deba1b0c.
    • The same member renamed (private claimInProcessRenamed;): a972fa4d01a6a8cd8654a4ce08619a48df26fe4745758cc0bcfca4c9edf4bf4d.
    • Line 5889 with one union member changed ("cel" to "cel2"): a21f02dba31881caec16bae8623f780cc6aae2cc9cd56f110d7eb0cb4a66b188.
    • All three differ from 0df32585…. So the normaliser does not hide a visibility change, a rename or a changed union member; it hides only member order.
  • Item 1 holds:

    • (a) the declaration set and visibility are the same: no line is added, removed or moved, and the 87 changed lines change only member order;
    • (b) the files are byte-identical after the normalisation;
    • (c) the ESM and CJS export lists are identical;
    • (d) every raw-diff line is a union-member swap.

Bundles

  • Export lists. 39 names each, identical at base and at head, and ESM equal to CJS on each side: AutomationEngine, AutomationServicePlugin, CONNECTOR_PULL_PROVIDERS, ConnectorPullError, DEFAULT_MAX_EXECUTION_LOG_SIZE, DEFAULT_MAX_TERMINAL_RUNS_PER_FLOW, FLOW_CREDENTIAL_MIGRATION_ID, FLOW_CREDENTIAL_OBJECT, FLOW_CREDENTIAL_VALUE_FIELD, FlowCredentialChannel, FlowCredentialChannelRefusal, FlowCredentialUnresolvableError, InMemoryFlowActivationStore, InMemoryFlowDispatchStore, InMemorySuspendedRunStore, MAX_PERSISTED_HISTORY_STEPS, ObjectStoreFlowActivationStore, ObjectStoreFlowDispatchStore, ObjectStoreSuspendedRunStore, SysAutomationRun, SysFlowCredential, SysFlowDispatch, UnscopedRunDataAccessError, createPackageFileLoader, describeFlowContender, formatRunSummaryLine, installBuiltinNodes, migrateFlowCredentialsIntoChannel, pullConnectorSource, registerConnectorNodes, registerCrudNodes, registerHttpNodes, registerLogicNodes, registerScreenNodes, renderFlowContender, resolveFlowPrecedence, resolveRunDataContext, stampSystemInsertOwner, summarizeRun.
  • Each export's code. A script fingerprinted each export's Function#toString in both bundles, at base and at head. Only AutomationEngine differs, because its stubs changed. In the CJS bundle, six other functions (migrateFlowCredentialsIntoChannel and the five register*Nodes) print differently too, but only in esbuild's import-namespace variable names (import_automation3 becomes another number, because the new modules add import statements). With those names folded, they are identical.
  • Each export's name. Every exported function's .name matches base in both bundles. (AutomationEngine prints as _AutomationEngine at both ends.) This is the check that caught pullConnectorSource; see Scope.
  • The bundle JS bytes otherwise differ only because code moved within the bundle.

Readers

One reader re-pointed. src/flow-activation-ledger.test.ts's ENGINE_SOURCES holds the #10243 grep pins, which are absence checks. It gains the five modules, so a moved body is not a place the retired mechanism could come back unseen. That 1-line edit is the PR's seventh file.

Readers examined. The base for each is ea6fe000, in a worktree of that commit.

  • check-tenant-audit-census (derived): output byte-identical to base.

    • It certifies 238 write call sites (162 decidable; 2 provably without tenant context, 29 more unreadable).
    • It holds 23 prose figures and matches every UNTYPED_RECEIVERS row.
    • Its --self-test output is identical too.
  • check-system-context-census (derived): output byte-identical. It finds 149 elevation read sites in 22 packages across 77 files, in 129 symbols.

  • check:liveness (spec lane, not derived; run by hand as packages/spec's tsx scripts/liveness/check-liveness.mts): output byte-identical at base and head.

    • 753 (entry, cited file) pairs: 752 anchored, 1 exempt.
    • 982 of 982 symbol anchors resolve.
  • check:platform-checklist (not derived; run by hand): output identical at base and head. 987 of 997 symbol anchors resolve; the other 10 are the same ones, 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.

  • check:adr-anchors (decision letters; run by hand at the merge base and the head): output identical apart from the count of files holding a citation, 6699 at base and 6702 at head.

    • The 52309 citations are the same in number: the citations in the moved bodies of flow-definitions.ts, subflow-guards.ts and node-text-rendering.ts are now counted in those three files.
    • It reads 84 anchored files and 1939 decision-letter citations, the same at both ends.
    • Neither engine.ts nor any of the five modules is on the anchored surface.
  • check:startup-registry-verdict (not derived; run by hand at both ends, on the hand-gate list): 46 seams, 40 of them read-only, none recording a verdict, at base and at head.

    • Its --list is the same at both ends apart from line numbers and the scanned-file count, 3463 at base and 3468 at head; the five extra files are the new modules.
    • None of its four engine bodies moves. warnIfNodeTypeVocabularyNeverSealed and unknownNodeTypes move, and the registerFlow and sealNodeTypeVocabulary seams still name the same warn sites, at their new line numbers.
  • Symbol anchors into moved methods. Four anchors name a method whose body moved:

    • docs/adr/0087-metadata-protocol-upgrade-contract.md, engine.ts#canonicalizeStoredFlow;
    • docs/qa/platform-checklist/areas/automation.json, engine.ts#packagedSubflowCallers;
    • packages/spec/liveness/translation.json, engine.ts#renderRefusalMessage and engine.ts#translatedFlowTemplate.

    Each still resolves: the method stays in engine.ts with its docblock and calls its body. This is the same as slice 5's engine.ts#recordLog anchor in approvals.json. The readers above give identical output, and the translation.json key-mention pairs (refusals, message) stay anchored through engine.ts's own docblocks.

  • path:line citations. None points into this package's engine.ts at a line this slice moves. The one fixture citation, service-automation/src/engine.ts:44 in packages/spec/scripts/liveness/evidence.test.ts, is a realm-attribution string that scanEvidence parses without reading the file.

Gates

  • Derived. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on this commit derives 60 families from the 7 changed paths: 2,406 changed lines, under the 3,000-line human-merge threshold. These are the same 60 as slice 6.
    • All 60 ran in this container on this commit, with each exit code captured before any pipe, and all 60 exit 0. dispatch-gates.mjs --ran reconciles: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.
    • An earlier run on the first commit, the one that still moved pullConnectorSource, was stopped after 23 families when I narrowed the slice. It is not counted.
  • By hand, at base and head, not derived: check:liveness, check:platform-checklist, check-adr-symbol-anchors, check:adr-anchors and check:startup-registry-verdict, with the readings in the Readers section above.
  • check-governed-merges --branch on this commit: 0 of 7 paths on the register, NOT governed, 2,406 lines.
  • Package checks.
    • @objectstack/service-automation's vitest run gives 199 of 199 files and 2628 of 2628 tests, the same at base and head.
    • The package's typecheck (tsc --noEmit and check:test-typecheck) passes.
    • ESLint on the seven files passes.
  • Open PRs, re-listed just before this PR was opened: 11.
node scripts/check-ci-filter-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-keyed-text-bounds.mjs :: exit 0
node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0
node scripts/check-platform-object-tenancy-census.mjs :: exit 0
node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-position-name-fold-loaders.mjs :: exit 0
node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0
node scripts/check-registry-log-declared.mjs :: exit 0
node scripts/check-registry-log-declared.mjs --self-test :: exit 0
node scripts/check-rest-log-spy-declared.mjs :: exit 0
node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0
node scripts/check-system-context-census.mjs :: exit 0
node scripts/check-system-context-census.mjs --self-test :: exit 0
node scripts/check-tenant-audit-census.mjs :: exit 0
node scripts/check-tenant-audit-census.mjs --self-test :: exit 0
node scripts/check-undeclared-dep-imports.mjs :: exit 0
node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0

Acceptance notes

  • This is the slice the seat agreed (shape call A), less pullConnectorSource, which stays so that the public pullConnectorSource export keeps its name. The claim amendment says so.
  • Six host getters in the slice-5 store form, and four get/set pairs in the refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508 form, one for each warned-once flag a moved body sets.
  • Seven helpers gain export in place. No new public export: the export lists and every export's code, apart from AutomationEngine, are the same.
  • The .d.ts changes only in the order of members in 87 string-literal unions, each shown above.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE

…dules

Move-only. The bodies of 42 AutomationEngine methods move unedited into
five new modules: dispatch-claims.ts (claiming, settling and reading a
dispatch, minting a run id, releasing a suspension; 7 bodies),
flow-definitions.ts (canonicalising, unregistering, refusing and rolling
back a flow, its runtime states and trigger bindings, and its function,
connector and credential surfaces; 12), subflow-guards.ts (finding and
refusing what reaches a disabled subflow; 11),
consumed-suspension-restore.ts (resolving, restoring, peeking at and
inspecting a consumed suspension; 6) and node-text-rendering.ts (the
node-type vocabulary checks, a failed node's error variable, and refusal
and flow-text rendering; 6). Each method keeps its name, docblock,
signature and position in the class and calls its body with a host
literal holding exactly the members the body reads; six bodies read none
and are called directly. Each async stub drops async and returns the
promise the moved function makes.

Fields assigned only at their declaration or in the constructor go by
value. Six fields a method reassigns go through getters. The four
warned-once flags a moved body sets go through get/set pairs, so each
write lands on the engine. claimAdvance, validateNodeInputSchemas,
refuseInvalidScreenInput and buildSubflowResumeSignal stay in the class,
because each reads a name documented as private to engine.ts.
pullConnectorSource stays too: a second top-level function of that name
would make the bundler rename the public pullConnectorSource export. Seven
helpers the moved bodies read gain export in place; index.ts re-exports
none of them. The imports only moved bodies read move with them. The
ENGINE_SOURCES grep pins read the new modules too.

Claude-Session: https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 24, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 24, 2026 03:27
@github-actions github-actions Bot added the tests label Oct 24, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-automation, touching 157 documentable anchor(s).

14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/data-api.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers))
  • content/docs/api/error-catalog.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers), RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/api/error-handling-server.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers), RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/api/index.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers))
  • content/docs/automation/flows.mdx (via getFlowRuntimeStates (symbol, a method of class AutomationEngine; a top-level function), getTriggerBindingAudit (symbol, a method of class AutomationEngine; a top-level function), restoreConsumedSuspension (symbol, a method of class AutomationEngine; a top-level function), /runs/:runId/cancel (route, a path literal in refuseUnderReachingCallers))
  • content/docs/deployment/cloud-connection.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/deployment/environment-variables.mdx (via getTriggerBindingAudit (symbol, a method of class AutomationEngine; a top-level function))
  • content/docs/deployment/install-modes.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/deployment/tenancy-modes.mdx (via getTriggerBindingAudit (symbol, a method of class AutomationEngine; a top-level function))
  • content/docs/kernel/contracts/data-engine.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers))
  • content/docs/permissions/permission-sets.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/permissions/sharing-rules.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/protocol/kernel/error-handling.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers))
  • content/docs/protocol/objectql/types.mdx (via DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers))

⛔ 7 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level type))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level type))
  • content/docs/releases/v17/17-5.mdx (via getTriggerBindingAudit (symbol, a method of class AutomationEngine; a top-level function))
  • content/docs/releases/v17/17-6.mdx (via AutomationEngine (symbol, a top-level type), getTriggerBindingAudit (symbol, a method of class AutomationEngine; a top-level function), DELETE_RESTRICTED (literal, a string literal in refuseUnderReachingCallers), RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow), /runs/:runId/cancel (route, a path literal in refuseUnderReachingCallers))
  • content/docs/releases/v17/17-7.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/releases/v18/18-0.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))
  • content/docs/releases/v18/18-1.mdx (via RESOURCE_CONFLICT (literal, a string literal in refuseCustomerAuthoredToggle; a string literal in refuseEnableOntoDisabledSubflow))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f899c0079fbb086d876ec3eda38dd4fe740e8239 — the merge of head d3347f07b526583da918c89f31c23443b5f0616b into base e073455929a60b67c48da3f91324261e57aaa26b, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f899c0079fbb086d876ec3eda38dd4fe740e8239 && git checkout f899c0079fbb086d876ec3eda38dd4fe740e8239
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin e073455929a60b67c48da3f91324261e57aaa26b d3347f07b526583da918c89f31c23443b5f0616b && git checkout -B drift-repro e073455929a60b67c48da3f91324261e57aaa26b && git merge --no-ff d3347f07b526583da918c89f31c23443b5f0616b

node scripts/docs-audit/affected-docs.mjs --json e073455929a60b67c48da3f91324261e57aaa26b

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs e073455929a60b67c48da3f91324261e57aaa26b → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · 2026-10-24T04:03Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25222 slice 7 (claim 6378948491, amendment 6379054858; os-dev-report 6379558889) · reviewed head d3347f07b5 · merge base ea6fe0001e

  • Shape (A, as the seat called it): 42 bodies move from engine.ts into five seam modules: dispatch-claims 7, flow-definitions 12, subflow-guards 11, consumed-suspension-restore 6, node-text-rendering 6. 7 files, +1,634 / −772, 2,406 changed lines, under the 3,000 bar.
  • Narrowing before push, accepted: pullConnectorSource stays. With its body moved, the bundler would export connector-pull.ts's public pullConnectorSource as pullConnectorSource2, which renames a published function.
  • Stays, each with its docblock line in the claim:
    • claimAdvance, whose AdvanceClaim return type is marked "Module-private".
    • validateNodeInputSchemas, whose InputSchemaViolationError is marked "Not exported on purpose".
    • Slice 6's two bodies.
  • Seat conditions from 01:52Z: the seven helpers are exported in place with their signatures, and index.ts adds no re-export. The four warned flags take get/set pairs in the refactor(plugin-auth): move AuthManager's request routing into auth-manager-request-routing.ts (#25224 cut 10) #25508 form; the grep shows each one is written only in its moved body. Parameter defaults are kept and disclosed. Body counts are in the claim.
  • .d.ts, ruling 6348891160 item 1 (d), re-run by the seat in its own worktree (turbo run build --filter=@objectstack/service-automation at both ends):
    • Raw: 1a5162a4dd929399… at base and deb08bdfb32c2a24… at head, on both index.d.ts and index.d.cts.
    • Raw diff: 348 lines, which is 174 changed lines (87 pairs).
    • Normalised: 0df32585069f7431… at both ends. The normalised diff is 0 lines, so every raw line is a union-order swap.
    • Positive control: head line 1490, private flows;, made public, moves the hashes to raw 4bbf16361e3cf073… and normalised 89e8a8fac9607092….
  • Export lists, re-run by the seat: ESM and CJS each have 39 names, with sorted-list hash 836f5186 at both ends.
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors (2,384 anchors)
    • check-tenant-audit-census (238)
    • check-system-context-census (149)
    • check-adr-anchors
    • check-dispatcher-error-vocabulary
    • check-platform-checklist
    • check-startup-registry-verdict: 46 seams at both ends
    • spec liveness
  • Decision letters, read by the seat: 1939 at both ends.
  • Reader migrated: flow-activation-ledger.test.ts's ENGINE_SOURCES gains the five new modules. Only the list changes.
  • Tests and gates (report): 199 files / 2,628 tests at both ends, and 60 of 60 derived gates exit 0. CI: 34 success and 12 expected skips, with none failed or pending (46 runs). Mergeable: git merge-tree onto today's main is clean.
  • Next slice: every remaining body over 3 lines is either site-bearing or a stay, so slice 8 waits on the 33-site ruling.
  • Landing: HELD. Sales paused merges for a release at 2026-10-24T03:57Z. The PR is not governed (check-governed-merges --pr 25597: 2,406 lines). The seat readies it and arms auto-merge through the relay only after merges resume, re-checking git merge-tree against main at that point. The card stays open and dispatched.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 24, 2026 09:21
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 24, 2026 09:21
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 24, 2026
Merged via the queue into main with commit e57a4c3 Oct 24, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-nirtwt branch October 24, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants