Repository navigation
refactor(service-automation): move the Plugin Extension API bodies into plugin-extension-api.ts (#25222 slice 2) - #25520
Conversation
…to plugin-extension-api.ts The bodies of eight AutomationEngine methods from the Plugin Extension API section move unedited to the new module src/plugin-extension-api.ts: registerNodeExecutor, warnIfResumeAuthorityUndeclared, registerNodeAlias, registerTrigger, deriveTriggerBinding, registerConnector, registerDegradedConnector and assertSameOriginOrFree. Each class method keeps its name, docblock, signature and position, and calls its body with a PluginExtensionHost literal: methods as late-bound closures, fields by value (each is assigned once and never reassigned). The members with bodies of one to five lines stay, and so do getConnectorDescriptors (the spec liveness ledger cites engine.ts for the properties it reads) and the four bodies that delete from an engine Map (the tenant-audit census types such a receiver only through the class's field declarations). 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): 16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. 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 0fc555fa5ffdf26317730eec99f049400ba56a1a && git checkout 0fc555fa5ffdf26317730eec99f049400ba56a1a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 842a0aef9dc42bcc277be02bdb37053cfe2d94e5 1913a78bd350d97e7a9b1b6a13e862c6934ea80c && git checkout -B drift-repro 842a0aef9dc42bcc277be02bdb37053cfe2d94e5 && git merge --no-ff 1913a78bd350d97e7a9b1b6a13e862c6934ea80c
node scripts/docs-audit/affected-docs.mjs --json 842a0aef9dc42bcc277be02bdb37053cfe2d94e5
|
席位核对 · ACCEPT · 2026-10-23T18:59Z
Generated by Claude Code |
Requested by Sales · project thread
Part of #25222
Clause-②: no
Metadata-protocol: none
Ruling-ref: 6348891160
Slice 2 of the
engine.tssplit: the Plugin Extension API section ofAutomationEngine(node executors, triggers, connectors). The claim is 6370314776. It was narrowed twice before any push, by amendments 6370513520 and 6371220045, and the services seat agreed to the narrowed scope. The bodies of eight methods move, unedited, to the new modulepackages/services/service-automation/src/plugin-extension-api.ts:registerNodeExecutor,warnIfResumeAuthorityUndeclared,registerNodeAlias;registerTrigger,deriveTriggerBinding;registerConnector,registerDegradedConnector,assertSameOriginOrFree.That is 287 body lines. 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 frommainat4c8f40a1(slice 1's merge, #25270). The branch name has no issue number, so the claim comment is the identity record.What stays in
engine.ts.d.ts.resolveTriggerBinding,unregisterConnectorand the connector-provider registry. A host literal around one of them would be as long as its body.getConnectorDescriptorspackages/spec/liveness/connector.jsoncitesengine.ts#getConnectorDescriptorsas the evidence for nine connector properties, andcheck:liveness's key-mention pass asks that the cited file names each one. Moved,engine.tsnames neithericonnoroutputSchema: 745 anchored, 2 UNANCHORED, exit 1 (base: 747 anchored, 1 exempt, exit 0). The ledger belongs to the spec lane, so the cut is narrowed instead of re-pointing it.unregisterNodeExecutor.delete()on the engine'snodeExecutorsmap and twice onactionDescriptors.scripts/tenant-audit-census.mjstypes athis.Xreceiver only through a property declared on a class in the same file (thisProps). In the module,thisis the hostPick, so each receiver goes[untyped-receiver].unregisterTriggerthis.triggers.delete(type)andthis.boundFlowTriggers.delete(name). The second is also counted as an engine write placed by itsnameargument, so moved, the census reads 239 sites instead of 238.activateFlowTriggerthis.policyDisabledFlows.delete(flowName).deactivateFlowTriggerthis.boundFlowTriggers.delete(flowName).Measured with those four bodies moved:
check-tenant-audit-censusand its--self-testexit 1, with six of the seven.delete()sites reported[untyped-receiver], 239 sites instead of 238, drift on both committed census pages, and 9[prose-count]errors. The cures would be anUNTYPED_RECEIVERSrow, a ⛔ shrink-only ledger, or a census that reads the host interface, which is ascripts/change. Neither fits this card. So the bodies stay, as #25224 cut 8 keptsettleSelfRegistrationGrant.isTimeTriggeredKindstays with its only reader,activateFlowTrigger.The shape (slice 1's, one host interface)
PluginExtensionHosttypes each of its 14 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 thePickofPluginExtensionHostover exactly the members its own body reads. The generator read those members off each body's AST, in first-read order. The class method annotates its host literal as that function'sThisParameterType, which types the closures and rejects an extra member.(...a) => this.m(...a), so a spy or test double set on the instance is still the one called.logger, assigned only in the constructor, and theMap/SetfieldsnodeExecutors,actionDescriptors,resumeAuthorityOmissionWarned,triggers,boundFlowTriggers,flowsandconnectors, each assigned once at its declaration. A search of every file in the repo that namesAutomationEnginefinds no reassignment, cast or bracketed write of any of them. So a host's field is the engine's field, including where a body reads it later:registerNodeAliaskeeps itsconst engine = this;, soengineis now the host, and the delegating executor it builds readsengine.loggerandengine.nodeExecutorsat execute time. The host carries both.this., so the moved lines are unedited. One deliberate exception is in the signatures:registerConnectorandregisterDegradedConnectordeclare a default fororiginon the class method. The moved functions takeorigin: ConnectorOriginwithout it, and the class passes the value it resolved, so the default is written once.Imports
engine.tsdropsdefineActionDescriptor, which onlyregisterNodeExecutor's body read.engine.tscode reads them now:FLOW_RESERVED_NODE_TYPES,resolveFlowTriggerKind,resolveScheduleOrganizationandConnectorSchema. A docblock that stays names each one in a{@link …}, andtscandeslintcount the link as a use.defineActionDescriptor,FLOW_RESERVED_NODE_TYPES,resolveFlowTriggerKind,resolveScheduleOrganizationandConnectorSchemafrom the spec,describeThrownForLog, and, as types only,engine.ts's ownAutomationEngine,NodeExecutor,FlowTrigger,FlowTriggerBindingandConnectorActionHandler. Its only import ofengine.tsisimport type, so there is no runtime cycle.index.tsre-exports by name from./engine.js, so nothing new reaches the package entry.Proof of move-only
git diff --color-moved=plain --color-moved-ws=allow-indentation-change 4c8f40a1 HEADover the two files): 287 of the 288 deleted lines and 315 of the 465 added lines are detected as moved.defineActionDescriptorout).engine.ts, the narrowed import, the module import with its comment, and the eight stub bodies; in the module, the license header, the module docblock, the imports,PluginExtensionHostwith 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 eight method texts de-indented one level. It names each of the other 84: 31 for the license header, module docblock and imports; 27 for the host docblock, the interface and the blank after it; 8 pointer docblocks; and 18 signature lines. The 18 include the twooriginlines that drop their default, and the verbatim parameter lines between a rewritten head and an edited line, which form runs shorter than the script's three-line minimum.engine.tsis in the module, apart from the 1 import line above (288 deleted, 9 of them blank).engine.tsare found nowhere in base: the narrowed import, the module import block, and the stub lines. The other 17 are lines such aslogger: this.logger,and};that base already contains elsewhere..d.ts: see the next section.@objectstack/service-automation's suite passes the same at base and head, 198 files and 2619 tests..d.tsreading (ruling 6348891160, item 2)Base is
dist/built at4c8f40a1in this container, after a fullturbo run build. Head isdist/built at this PR's commit.index.d.tsandindex.d.cts(identical to each other on both sides): base8fc937b1e1efa57ee91dce01f391cb5943241cf4312fa55daa2f16981298b772, head8fc937b1e1efa57ee91dce01f391cb5943241cf4312fa55daa2f16981298b772.32aab1ec7a95b4e90d8d50a2e06b36d3b8207e25e1e527068b7d3460020794fb, head32aab1ec7a95b4e90d8d50a2e06b36d3b8207e25e1e527068b7d3460020794fb.index.d.tsbase vs head: 0 lines. 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 2196, a moved private member, one line each, then the same normaliser:private deriveTriggerBinding;made public (deriveTriggerBinding;) givesb182e3bdc77105e66d8b237803c163a6fe22a9cb1594d17dd9ad227586b6f963.private deriveTriggerBindingRenamed;) gives4514dd988483d47533074597ec7a1213b79d05b572685de6c35aed3beb902b39.32aab1ec…, 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./plugin-extension-api.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 identical to base4c8f40a1, 238 write call sites certified (162 decidable; 2 provably without tenant context, 29 more unreadable), 23 prose figures held, everyUNTYPED_RECEIVERSrow matched. Only the scanned-source count moves, by the one new file.check:liveness(spec lane, not derived, run by hand aspackages/spec'stsx scripts/liveness/check-liveness.mts): output identical at base and head, 748 (entry, cited file) pairs, 747 anchored, 1 exempt, and 977 of 977 symbol anchors resolve. Theconnector/actions.keyrow citesengine.ts#registerConnectorand quotes a line of its body. The method stays as a shell, so the anchor resolves and the file still nameskey. The quoted line is now in the module, which the spec lane may re-point to at its own pace.docs/qasymbol anchorsengine.ts#registerConnector(integration-system.json:91) and#registerDegradedConnector(:189), andRUNNER.md:282'sgetConnectorDescriptors(), name class methods that stay.automation.json'sautomation.flow-error-handlingquotes the trigger-fired failure line, which stays inengine.tswithactivateFlowTrigger.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. 1978 decision-letter citations at base and at head; the only difference in the output is the scanned file count, 6652 to 6653, the one new file.text-slot-template.test.tswalks all ofsrc/, so it already reads the module. Thedispatcher-error-vocabularyrow forengine.tsnamesOS_METADATA_CONVERSION_TODO, which no moved body stamps.path:linecitation in the repo points into the moved ranges of baseengine.ts(the bodies between:3400and:4155). Lines ofengine.tsafter the import block (base:156) move down by 12, and lines after the last moved body (base:4155) move up by 219. The one liveengine.ts:44string, inpackages/spec/scripts/liveness/evidence.test.ts, is a parser fixture and sits above the import block.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon this commit derives 58 families from the 3 changed paths (755 changed lines, under the 3000-line human-merge threshold). All 58 ran in this container on this commit, each exit code captured before any pipe, and all 58 exit 0.dispatch-gates.mjs --ranreconciles: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN.check:liveness,check:platform-checklist,check-adr-symbol-anchorsandcheck:adr-anchors, with the readings in the readers section above.@objectstack/service-automation'svitest run: 198 of 198 files, 2619 of 2619 tests, the same at base and head.Acceptance notes
engine.tsholds 33this.<field>.delete/insert/update(sites, most of them in the resume and suspension sections. A later slice that moves any of those bodies under this shape meets the same census refusal. That question goes to the coordinator as its ownneeds_decisionbefore the slice that needs it. It does not block this one.🤖 Generated with Claude Code
https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE