Repository navigation
refactor(service-automation): move 42 engine bodies into five seam modules (#25222 slice 7) - #25597
Conversation
…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>
📓 Docs Drift CheckThis PR changes 1 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 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 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
|
席位核对 · ACCEPT · 2026-10-24T04:03Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25222
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
This is slice 7 of the
engine.tssplit. 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 42AutomationEnginemethods move, unedited, into five new modules underpackages/services/service-automation/src/:dispatch-claims.tsDispatchClaimsHostclaim,claimInProcess,settleDispatch,readDispatch,nextRunId,warnAdvanceClaimDegraded,releaseSuspension: claiming, settling and reading a dispatch, minting a run id, announcing a degraded advance claim, releasing a suspensionflow-definitions.tsFlowDefinitionsHostresolveFunction,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 surfacessubflow-guards.tsSubflowGuardsHostdescribeDisabledFlow,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 subflowconsumed-suspension-restore.tsConsumedSuspensionRestoreHostrowSupersedesJournal,resolveConsumedSuspensionWitnesses,restoreConsumedSuspension,resolveRestoreChain,peekConsumedSuspension,inspectConsumedSuspension: resolving a consumed suspension's witnesses, and restoring, peeking at or inspecting onenode-text-rendering.tsNodeTextRenderingHostunknownNodeTypes,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 slotsThe 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 frommainatea6fe000after slice 6 (#25567) merged. The branch name has no issue number, so the claim comment is the identity record.Scope
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-censusandcheck-system-context-censusgive byte-identical output at base and head (see Readers).engine.tsdocuments as private, and moving it would mean exporting that name:claimAdvancereturnsAdvanceClaim, whose docblock says "Module-private: it is a control-flow verdict, not a contract."validateNodeInputSchemasthrowsInputSchemaViolationError, whose docblock says "Not exported on purpose — callers read the ADR-0112codeoff the result, never the throw."refuseInvalidScreenInputandbuildSubflowResumeSignal(slice 6's two) readENGINE_BUILT_SIGNAL/engineBuilt.docs/adr/0019-approval-as-flow-node.mdcalls it "a module-private symbol", and its docblock says "Module-private and symbol-keyed".pullConnectorSourcestays too (the claim amendment, comment 6379054858).connector-pull.tsalready has a top-levelpullConnectorSource, whichindex.tsre-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 exportedpullConnectorSource2 as pullConnectorSource, so its.namechanged. That would be an observable change to a public export, so the body stays. On this head, every exported function's.namematches base in both bundles (see Bundles below).retryExecutionandgetConnectorDescriptors;resolveCanonicalDescriptor,resolveResumeAuthorityandresolveEffectiveSuspension. A moved function reading a private static would not compile, andtscpasses;knownNodeTypes,getUnknownNodeTypeAudit,sealNodeTypeVocabularyandwarnUnknownNodeTypes.engine.ts+365 / -771, the five modules +1,268, and the test 1 / 1.check-governed-merges --branchon this commit says NOT governed, 0 of 7 paths on the register.What stays in
engine.ts.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.thisas thePickof its host over exactly the members its own body reads, read off each body's AST in first-read order.nextRunId,subflowTargets,describeDisabledSubflows,rowSupersedesJournal,unknownNodeTypesandsetNodeError.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
storeform ([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 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 areengine.tsat the baseea6fe000: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 memberget 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 itsAuthManagerseams, 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 addsconst writeX = (v: AutomationEngine['X']) => { this.X = v; };and the host memberset X(v) { writeX(v); }beside the getter, and the host interface member is notreadonly. So each write lands on the engine, as it did throughthis.git grep -nE "(dispatchClaimDegradationWarned|dispatchOutcomeDegradationWarned|advanceClaimDegradationWarned|nodeTypeSealOmissionWarned)"over the whole repo at the base finds 13 lines, all inengine.ts:= false:nodeTypeSealOmissionWarned(:2540),dispatchClaimDegradationWarned(:2725),advanceClaimDegradationWarned(:2734),dispatchOutcomeDegradationWarned(:2741);{@link}in a docblock (:2544);claim(:2815),readDispatch(:2911),warnAdvanceClaimDegraded(:3310),warnIfNodeTypeVocabularyNeverSealed(:9481);Fifteen fields go by value:
logger,inProcessDispatchClaims,nodeExecutors,connectorProviders,flows,flowRefusals,flowShadowing,boundFlowTriggers,policyDisabledFlows,subflowDeclinedFlows,triggers,flowVersionHistory,flowLedgerDisabled,flowStatusDisabledandconsumedSuspensions. 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).\.(FIELDS)) for the fourteen names other thanloggerfinds 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.Maps andSets, and never rebind the fields.Async. The stubs of the nine async methods drop
asyncand return the moved function's promise, as in slices 3 to 6.asyncis the only edit to those signature lines, and the.d.tsdoes not printasync.Parameter defaults.
canonicalizeStoredFlowandcanonicalizeFlowtakeoptions: { includeRetired?: boolean } = {}.optionsstill gets{}.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
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.tsadds 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 bycanonicalizeFlow)export const MAX_RESTORE_CHAIN_HOPS = 32;(read byresolveRestoreChain)export function chainEntryOf(runId: string, result: SuspensionRestoreResult): ChainRestoreEntry {(read byrestoreConsumedSuspension)export function flowRegistrationTodo(flowName: string, site: RegistrationCheckSite): ConversionTodoNotice {(read bycanonicalizeStoredFlow)export function graftConditionEnvelopes(converted: unknown, parsed: unknown): unknown {(read bycanonicalizeFlow)export function negotiatedServiceLocale(service: RefusalI18nService, requested: string): string {(read bytranslatedFlowTemplate)export function nestedChildRunId(correlation: string | undefined): string | undefined {(read byresolveRestoreChain)CONVERSIONS_NOT_REPLAYED_AT_REHYDRATION"inservice-automation/src/engine.ts" (metadata-core'sartifact-forward-conversion.tsandmetadata-protocol'sprotocol.ts), and it stays there.flowRegistrationTodo's signature names types from other files (RegistrationCheckSite,ConversionTodoNotice). On this PR the rolled-up.d.tsgains no declaration and moves none; its only raw change is 87 reordered string-literal unions, each shown below as a swap..d.tssection.engine.ts's own imports. A comment above each value import from./engine.jssays 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.engine.ts.import { FLOW_HOOK_SECRET_KEY, flowCredentialClassLabel, flowCredentialPositions } from './flow-credential-projection.js';moves, verbatim, toflow-definitions.ts: only moved bodies read those names.[#15788]comment andimport { renderTextSlot, FlowTextTemplateError } from './builtin/template.js';move, verbatim, tonode-text-rendering.ts. The comment's claim ("template.tsimports only../guard-refusal.jsand package-external contracts, so nothing it pulls in reaches back here") is as true there as it was inengine.ts.engine.tsstill reads:@objectstack/spec/automationkeepsFLOW_RESERVED_NODE_TYPESandFLOW_STRUCTURAL_NODE_TYPES;FlowSchema,validateControlFlowandcollectFlowGraphsgo to the modules;@objectstack/speckeepsCONVERSION_TODO_CODEand the typesConversionNoticeandConversionTodoNotice;applyConversionsToFlowgoes toflow-definitions.ts;@objectstack/spec/systemkeepsresolveBundleLocale;flowRefusalMessageKeygoes tonode-text-rendering.ts.engine.tsdocblock{@link}s any of the names that left, so no link stops resolving.tscwithnoUnusedLocalspasses, so every name left in anengine.tsimport is still read or linked.engine.tsgains the five module imports, after the./run-resume.jsone, each with a comment naming its bodies.Proof of move-only
git diff --color-moved=plain --color-moved-ws=allow-indentation-change ea6fe000 HEADoverengine.tsand the five modules. It detects 752 of the 771 deleted lines and 892 of the 1,633 added lines as moved.async;export;engine.tsand their comments; the narrowed imports; the nine heads withoutasync; the sevenexportlines; the 42 stub bodies; and lines git does not pair with a moved block.engine.tsagainst 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.jsimport. The script names each of the other 370:export functionheads, and 8thisparameter lines, each aPickof 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.jsimport, the[#15788]comment and the./builtin/template.jsimport are among them, verbatim.Added lines. Of the 365 lines added to
engine.ts, 284 are found nowhere in base:exportlines;async;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.@objectstack/service-automation's suite passes the same at base and head, 199 of 199 files and 2628 of 2628 tests..d.tsreading (ruling 6348891160, item 2)The merge base is
ea6fe000, the head ofmain, so the merge-base reading and the base reading are one reading.dist/, built atea6fe000in this container, in a worktree of that commit.dist/, built at this PR's commit.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.tsandindex.d.cts, which are identical to each other on both sides: base1a5162a4dd929399460a3936e22747a74cf0a8db24d398ffbab5d7ead7bf212e, headdeb08bdfb32c2a247b98780ad67162bd2938da46669e81af313526f69af6ecc4.Normalised sha256, all four files: base
0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232, head0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232.Raw diff,
index.d.tsbase vs head: 174 lines, 87 per side, with 19,626 lines on each side. They are 87 one-linechunks: 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,SysFlowDispatchandSysFlowCredential, which inline the spec's field and flow types:type:"secret"moves from 3rd to 6th, after"code"dialect:"cron" | "cel" | "template""template"moves from 3rd to 1sttype?:"form"and"api"swap (2nd and 3rd)type?:"secret"moves from 3rd to 6th, after"code"category?:"flow"and"data"swap (1st and 2nd)For example, line 5889 is
dialect: "cron" | "cel" | "template";at base anddialect: "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 | sha256sumPositive controls, each one line changed in the head
index.d.ts, then the same normaliser:private claimInProcess;, a private member whose body moved, made public (claimInProcess;):196ae606dce0584f296ce860802b020ab52eb604cde3f4f09cb835f5deba1b0c.private claimInProcessRenamed;):a972fa4d01a6a8cd8654a4ce08619a48df26fe4745758cc0bcfca4c9edf4bf4d."cel"to"cel2"):a21f02dba31881caec16bae8623f780cc6aae2cc9cd56f110d7eb0cb4a66b188.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:
Bundles
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.Function#toStringin both bundles, at base and at head. OnlyAutomationEnginediffers, because its stubs changed. In the CJS bundle, six other functions (migrateFlowCredentialsIntoChanneland the fiveregister*Nodes) print differently too, but only in esbuild's import-namespace variable names (import_automation3becomes another number, because the new modules add import statements). With those names folded, they are identical..namematches base in both bundles. (AutomationEngineprints as_AutomationEngineat both ends.) This is the check that caughtpullConnectorSource; see Scope.Readers
One reader re-pointed.
src/flow-activation-ledger.test.ts'sENGINE_SOURCESholds 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.UNTYPED_RECEIVERSrow.--self-testoutput 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 aspackages/spec'stsx scripts/liveness/check-liveness.mts): output byte-identical at base and head.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.flow-definitions.ts,subflow-guards.tsandnode-text-rendering.tsare now counted in those three files.engine.tsnor 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.--listis 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.warnIfNodeTypeVocabularyNeverSealedandunknownNodeTypesmove, and theregisterFlowandsealNodeTypeVocabularyseams still name the samewarnsites, 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#renderRefusalMessageandengine.ts#translatedFlowTemplate.Each still resolves: the method stays in
engine.tswith its docblock and calls its body. This is the same as slice 5'sengine.ts#recordLoganchor inapprovals.json. The readers above give identical output, and thetranslation.jsonkey-mention pairs (refusals,message) stay anchored throughengine.ts's own docblocks.path:linecitations. None points into this package'sengine.tsat a line this slice moves. The one fixture citation,service-automation/src/engine.ts:44inpackages/spec/scripts/liveness/evidence.test.ts, is a realm-attribution string thatscanEvidenceparses without reading the file.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon 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.dispatch-gates.mjs --ranreconciles: 60 derived, 60 run, 0 NOT-MEASURED, 0 UNRUN.pullConnectorSource, was stopped after 23 families when I narrowed the slice. It is not counted.check:liveness,check:platform-checklist,check-adr-symbol-anchors,check:adr-anchorsandcheck:startup-registry-verdict, with the readings in the Readers section above.check-governed-merges --branchon this commit: 0 of 7 paths on the register, NOT governed, 2,406 lines.@objectstack/service-automation'svitest rungives 199 of 199 files and 2628 of 2628 tests, the same at base and head.typecheck(tsc --noEmitandcheck:test-typecheck) passes.origin/main. None of them touchesservice-automation/src, the readers above, their ledgers,docs/adr/0019-approval-as-flow-node.md, or the three files that hold anchors into moved methods.CHANGELOG.mdandpackage.json.mainhas moved toe0734559. Of what landed sinceea6fe000, only refactor(plugin-security): move the authorization middleware out of security-plugin.ts (#25223 cut 12) #25545 touches a reader above,scripts/check-system-context-census.mjs, and only in itsplugin-securityrows.Acceptance notes
pullConnectorSource, which stays so that the publicpullConnectorSourceexport keeps its name. The claim amendment says so.storeform, 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.exportin place. No new public export: the export lists and every export's code, apart fromAutomationEngine, are the same..d.tschanges only in the order of members in 87 string-literal unions, each shown above.🤖 Generated with Claude Code
https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE