Skip to content

Commit f38832b

Browse files
refactor(service-automation): move the Plugin Extension API bodies into plugin-extension-api.ts (#25222 slice 2) (#25520)
<!-- claude-projects-attribution: {"github_login":"os-sales"} --> _Requested by **Sales** · [project thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4UuCEzLkAB12FfRHL9ipwfKPd)_ Part of #25222 Clause-②: no Metadata-protocol: none Ruling-ref: 6348891160 Slice 2 of the `engine.ts` split: the Plugin Extension API section of `AutomationEngine` (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 module `packages/services/service-automation/src/plugin-extension-api.ts`: - node executors: `registerNodeExecutor`, `warnIfResumeAuthorityUndeclared`, `registerNodeAlias`; - triggers: `registerTrigger`, `deriveTriggerBinding`; - connectors: `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 from `main` at `4c8f40a1` (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` - 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 section's 21 members with bodies of one to five lines stay whole: the getters and setters, `resolveTriggerBinding`, `unregisterConnector` and the connector-provider registry. A host literal around one of them would be as long as its body. - Five longer bodies stay too. They are recorded here so the next slice does not re-measure them: | Body | Why it stays | Amendment | |---|---|---| | `getConnectorDescriptors` | `packages/spec/liveness/connector.json` cites `engine.ts#getConnectorDescriptors` as the evidence for nine connector properties, and `check:liveness`'s key-mention pass asks that the cited file names each one. Moved, `engine.ts` names neither `icon` nor `outputSchema`: 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. | 6370513520 | | `unregisterNodeExecutor` | It calls `.delete()` on the engine's `nodeExecutors` map and twice on `actionDescriptors`. `scripts/tenant-audit-census.mjs` types a `this.X` receiver only through a property declared on a class in the same file (`thisProps`). In the module, `this` is the host `Pick`, so each receiver goes `[untyped-receiver]`. | 6371220045 | | `unregisterTrigger` | The same census refusal, on `this.triggers.delete(type)` and `this.boundFlowTriggers.delete(name)`. The second is also counted as an engine write placed by its `name` argument, so moved, the census reads 239 sites instead of 238. | 6371220045 | | `activateFlowTrigger` | The same census refusal, on `this.policyDisabledFlows.delete(flowName)`. | 6371220045 | | `deactivateFlowTrigger` | The same census refusal, on `this.boundFlowTriggers.delete(flowName)`. | 6371220045 | Measured with those four bodies moved: `check-tenant-audit-census` and its `--self-test` exit 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 an `UNTYPED_RECEIVERS` row, a ⛔ shrink-only ledger, or a census that reads the host interface, which is a `scripts/` change. Neither fits this card. So the bodies stay, as #25224 cut 8 kept `settleSelfRegistrationGrant`. `isTimeTriggeredKind` stays with its only reader, `activateFlowTrigger`. ## The shape (slice 1's, one host interface) - `PluginExtensionHost` types 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. - Each moved function types `this` as the `Pick` of `PluginExtensionHost` over 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's `ThisParameterType`, which types the closures and rejects an extra member. - 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. - Fields are passed by value: `logger`, assigned only in the constructor, and the `Map`/`Set` fields `nodeExecutors`, `actionDescriptors`, `resumeAuthorityOmissionWarned`, `triggers`, `boundFlowTriggers`, `flows` and `connectors`, each assigned once at its declaration. A search of every file in the repo that names `AutomationEngine` finds 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: `registerNodeAlias` keeps its `const engine = this;`, so `engine` is now the host, and the delegating executor it builds reads `engine.logger` and `engine.nodeExecutors` at execute time. The host carries both. - The moved bodies keep saying `this.`, so the moved lines are unedited. One deliberate exception is in the signatures: `registerConnector` and `registerDegradedConnector` declare a default for `origin` on the class method. The moved functions take `origin: ConnectorOrigin` without it, and the class passes the value it resolved, so the default is written once. ## Imports - `engine.ts` drops `defineActionDescriptor`, which only `registerNodeExecutor`'s body read. - Four value imports stay unchanged though no `engine.ts` code reads them now: `FLOW_RESERVED_NODE_TYPES`, `resolveFlowTriggerKind`, `resolveScheduleOrganization` and `ConnectorSchema`. A docblock that stays names each one in a `{@link …}`, and `tsc` and `eslint` count the link as a use. - The module imports what the moved bodies read: `defineActionDescriptor`, `FLOW_RESERVED_NODE_TYPES`, `resolveFlowTriggerKind`, `resolveScheduleOrganization` and `ConnectorSchema` from the spec, `describeThrownForLog`, and, as types only, `engine.ts`'s own `AutomationEngine`, `NodeExecutor`, `FlowTrigger`, `FlowTriggerBinding` and `ConnectorActionHandler`. Its only import of `engine.ts` is `import type`, so there is no runtime cycle. `index.ts` re-exports by name from `./engine.js`, so nothing new reaches the package entry. ## Proof of move-only - **Moved block** (`git diff --color-moved=plain --color-moved-ws=allow-indentation-change 4c8f40a HEAD` over the two files): 287 of the 288 deleted lines and 315 of the 465 added lines are detected as moved. - Deleted, not moved (1): the narrowed import line (`defineActionDescriptor` out). - Added, not moved (150): in `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, `PluginExtensionHost` with 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. - **An independent check** (a script over base `engine.ts`, head `engine.ts` and the module): - 313 of the module's lines are 8 verbatim runs of base `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 two `origin` lines 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. - Every line deleted from `engine.ts` is in the module, apart from the 1 import line above (288 deleted, 9 of them blank). - 52 of the 69 lines added to `engine.ts` are found nowhere in base: the narrowed import, the module import block, and the stub lines. The other 17 are lines such as `logger: this.logger,` and `};` that base already contains elsewhere. - No string or template literal in the moved ranges spans a line, so de-indenting changed no string value. - **`.d.ts`**: see the next section. - **Bundles**: the ESM and CJS export lists are identical, 39 names each at base and head, and ESM equals CJS on each side. The bundle JS bytes differ because code moved within the bundle. - **Tests**: `@objectstack/service-automation`'s suite passes the same at base and head, 198 files and 2619 tests. ## `.d.ts` reading (ruling 6348891160, item 2) Base is `dist/` built at `4c8f40a1` in this container, after a full `turbo run build`. Head is `dist/` built at this PR's commit. - raw sha256, `index.d.ts` and `index.d.cts` (identical to each other on both sides): base `8fc937b1e1efa57ee91dce01f391cb5943241cf4312fa55daa2f16981298b772`, head `8fc937b1e1efa57ee91dce01f391cb5943241cf4312fa55daa2f16981298b772`. - normalised sha256, all four files: base `32aab1ec7a95b4e90d8d50a2e06b36d3b8207e25e1e527068b7d3460020794fb`, head `32aab1ec7a95b4e90d8d50a2e06b36d3b8207e25e1e527068b7d3460020794fb`. - raw diff, `index.d.ts` base vs head: **0 lines**. This slice reorders no union, so the normalised diff is empty too. - 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 on the head `index.d.ts`, line 2196, a moved private member, one line each, then the same normaliser: - `private deriveTriggerBinding;` made public (`deriveTriggerBinding;`) gives `b182e3bdc77105e66d8b237803c163a6fe22a9cb1594d17dd9ad227586b6f963`. - the same member renamed (`private deriveTriggerBindingRenamed;`) gives `4514dd988483d47533074597ec7a1213b79d05b572685de6c35aed3beb902b39`. - Both differ from `32aab1ec…`, so the normaliser hides neither kind of change. - Item 1 holds: (a) the declaration set and visibility are the same; (b) the files are byte-identical, before and after the normalisation; (c) the ESM and CJS export lists are identical; (d) there is no raw-diff line. ## Readers re-pointed - `src/flow-activation-ledger.test.ts`, the #10243 grep pins: `ENGINE_SOURCES` gains `./plugin-extension-api.ts`, so a moved body is not a place the retired `flowEnabled` map could come back unseen. **Readers examined and not re-pointed:** - `check-tenant-audit-census` (derived): output identical to base `4c8f40a1`, 238 write call sites certified (162 decidable; 2 provably without tenant context, 29 more unreadable), 23 prose figures held, every `UNTYPED_RECEIVERS` row matched. Only the scanned-source count moves, by the one new file. - `check:liveness` (spec lane, not derived, run by hand as `packages/spec`'s `tsx 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. The `connector/actions.key` row cites `engine.ts#registerConnector` and quotes a line of its body. The method stays as a shell, so the anchor resolves and the file still names `key`. The quoted line is now in the module, which the spec lane may re-point to at its own pace. - The `docs/qa` symbol anchors `engine.ts#registerConnector` (`integration-system.json:91`) and `#registerDegradedConnector` (`:189`), and `RUNNER.md:282`'s `getConnectorDescriptors()`, name class methods that stay. `automation.json`'s `automation.flow-error-handling` quotes the trigger-fired failure line, which stays in `engine.ts` with `activateFlowTrigger`. `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 #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.ts` is 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.ts` walks all of `src/`, so it already reads the module. The `dispatcher-error-vocabulary` row for `engine.ts` names `OS_METADATA_CONVERSION_TODO`, which no moved body stamps. - No `path:line` citation in the repo points into the moved ranges of base `engine.ts` (the bodies between `:3400` and `:4155`). Lines of `engine.ts` after the import block (base `:156`) move down by 12, and lines after the last moved body (base `:4155`) move up by 219. The one live `engine.ts:44` string, in `packages/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/objectstack` on 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 --ran` reconciles: 58 derived, 58 run, 0 NOT-MEASURED, 0 UNRUN. - Run by hand at base and head, not derived: `check:liveness`, `check:platform-checklist`, `check-adr-symbol-anchors` and `check:adr-anchors`, with the readings in the readers section above. - `@objectstack/service-automation`'s `vitest run`: 198 of 198 files, 2619 of 2619 tests, the same at base and head. ```text 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/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 - Nothing is outside this slice. The judgement calls are the two narrowings, stated in the amendments and in the table above. - For the rest of the series: base `engine.ts` holds 33 `this.<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 own `needs_decision` before the slice that needs it. It does not block this one. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE Co-authored-by: Claude <noreply@anthropic.com>
1 parent 9019af9 commit f38832b

3 files changed

Lines changed: 466 additions & 289 deletions

File tree

0 commit comments

Comments
 (0)