Repository navigation
Commit f38832b
<!-- 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
- packages/services/service-automation/src
0 commit comments