Repository navigation
Commit ea6fe00
<!-- 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
This is slice 6 of the `engine.ts` split, and its claim is comment
6377626691. The bodies of fifteen `AutomationEngine` methods move,
unedited, into a new module,
`packages/services/service-automation/src/run-resume.ts`:
- `resolveRunContext`, `seedRunGrants`, `resolveDecisionSegmentContext`
and `expandDeclaredLookups`, which resolve a run's context, grants and
declared lookups;
- `resume`, `refuseGatedResume`, `refuseUndeclaredSuspension`,
`loadSuspendedRun`, `loadSuspendedRunStrict` and `creditChildRun`, which
load, gate and resume a suspended run;
- `failSuspendedRun`, `settleUnresumableSuspension`, `cancelRun`,
`cloneVariablesAtPause` and `failAncestors`, which fail, settle or
cancel a suspended run, along with its ancestors.
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 `03243fc6` after slice 5
(#25551) 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
fifteen 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 fifteen methods declares its return
type, including the two public ones, `resume` and `cancelRun`. So no
public body with an inferred return type moves.
- **None of the bodies that must stay moves.** The remaining-bodies
table (comment 6376268510) marks these as stays:
- for liveness, `retryExecution` and `getConnectorDescriptors`;
- for private statics, `resolveCanonicalDescriptor`,
`resolveResumeAuthority` and `resolveEffectiveSuspension`.
`refuseGatedResume` and `refuseUndeclaredSuspension` call them through
host closures, and they stay in the class;
- for the startup-registry gate, `knownNodeTypes`,
`getUnknownNodeTypeAudit`, `sealNodeTypeVocabulary` and
`warnUnknownNodeTypes`.
- **Two more bodies stay: `refuseInvalidScreenInput` and
`buildSubflowResumeSignal`.**
- The table put them in this group. But they read `ENGINE_BUILT_SIGNAL`
and `engineBuilt`, and `docs/adr/0019-approval-as-flow-node.md` calls
that symbol "module-private".
- Moving them would mean exporting both names from `engine.ts`. The
package's export map is `.` only and `index.ts` would re-export neither,
so the symbol would stay out of reach outside the package. Even so, the
ADR's line would no longer be true.
- So they stay in the class, as the claim says, and the table's stays
are now 11.
- **Size.** 1,369 changed lines, under the 3,000-line bar: `engine.ts`
+132 / -546, the new module +689, and the test 1 / 1.
## 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 5)
- **The host interface.** `RunResumeHost` holds the 25 members these
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 `RunResumeHost` over
exactly the members its own body reads, read off each body's AST in
first-read order.
- `creditChildRun` reads no member, so it is called directly, without a
host.
- **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. There are 17 of them:
- `seedRunGrants`, `expandDeclaredLookups`, `refuseGatedResume`,
`resumeInternal`, `resolveEffectiveSuspension` and
`resolveSuspendedNodeType`;
- `resolveResumeAuthority`, `resolveDeclaredResumeAuthority`,
`resolveCanonicalDescriptor`, `loadSuspendedRunStrict` and
`evictConsumedSuspension`;
- `journalConsumedSuspension`, `forgetSuspendedRun`, `recordLog`,
`claimAdvance`, `loadSuspendedRun` and `failSuspendedRun`.
- **Four fields go through a getter**, in slice 5's `store` form (#25223
cut 1, PR #25290), because a method reassigns each. The write sites are
`engine.ts` at this head:
- `store`: `setSuspendedRunStore` (`:2769`), and the constructor
(`:2745`);
- `userGrantsResolver`: `setUserGrantsResolver` (`:3988`);
- `recordExpander`: `setRecordExpander` (`:3998`);
- `nodeTypeVocabularySealed`: `sealNodeTypeVocabulary` (`:9424`).
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.
- No moved body writes any field: the generator refuses a `this.X =`,
`op=`, `++` or `--` in a moved body, and found none. So no get/set pair
(#25508) is needed.
- **Four fields go by value**: `logger`, `suspendedRuns`,
`cacheOnlySuspensions` and `consumedSuspensions`. The seat asked for
proof that none is reassigned after construction:
- The declarations (`engine.ts` at this head):
- `private logger: Logger;`
- `private suspendedRuns = new Map…`
- `private cacheOnlySuspensions = new Set…`
- `private consumedSuspensions = new Map…`
- `git grep -nE
"this\.(logger|suspendedRuns|cacheOnlySuspensions|consumedSuspensions)\s*=[^=]"
-- packages/services/service-automation/src` finds 1 line, in the
constructor: `this.logger = logger;` (`:2744`).
- Across the repo, `git grep -nE
"\.(suspendedRuns|cacheOnlySuspensions|consumedSuspensions)\s*(=[^=>]|\?\?=)"`
finds nothing. The bracket form, `['logger']` and the other three names
followed by `=` or `??=`, finds nothing either. And `git grep -nE
"\.logger\s*(=[^=>]|\?\?=)" -- packages/services/service-automation`
finds only that constructor line.
- The bodies change the three collections' contents in place, on the
engine's own `Map` and `Set`, and never rebind the fields.
- **Async.** The stubs of the twelve async methods drop `async` and
return the moved function's promise, as in slices 3 to 5.
- 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`.
- The moved bodies keep saying `this.`, so the moved lines are unedited.
## Module-level declarations and imports
- **Nothing is newly exported.** Every top-level name the bodies read is
already exported from `engine.ts`: `buildRunTrigger`,
`NodeExecutionResult`, `StepLogEntry` and `SuspendedRun`.
- The module imports `buildRunTrigger` by value, and the rest,
`AutomationEngine` included, as types.
- `buildRunTrigger` is read only inside function bodies, at call time. A
comment above the import says so, as in slices 3 and 5.
- **The module's other imports** use the same specifiers as
`engine.ts`'s own imports:
- the types `FlowParsed`, `FlowRunSummary`, `AutomationContext`,
`AutomationResult` and `ResumeSignal`;
- the values `RESUME_AUTHORITY_SERVICE` and `describeThrownForLog`;
- the `./runtime-identity.js` and `./guard-refusal.js` import lines,
verbatim. These are moved lines.
- **Two `engine.ts` import lines change.**
- `./runtime-identity.js` keeps `runTriggerUserId` and `type
SeededRunContext`. `runIsUnscopedUserMode`, `flowTouchesData` and
`runOrgUserIds` were read only by moved bodies.
- `import { refuseNode } from './guard-refusal.js';` goes, because only
a moved body read `refuseNode`.
- `engine.ts` gains the `./run-resume.js` import, after the
`./run-history.js` one, with a 4-line comment naming its bodies.
- **Two imports stay, for their `{@link}`s.** `RESUME_AUTHORITY_SERVICE`
and `SeededRunContext` are no longer read in `engine.ts` code, but
`engine.ts` docblocks still `{@link}` them: 2 links each. `tsc` counts a
`{@link}` as a use under `noUnusedLocals`, so both stay imported and the
links still resolve.
- A control re-added `validateScreenInputs` and `flowTouchesData`, which
`engine.ts` neither reads nor links, to its imports. `tsc` failed with
TS6133 on both. So the setting is live for this file.
- **Authored text.** The module docblock and the host interface's
docblock are new. The host docblock names the method that reassigns each
getter field.
## Proof of move-only
- **Moved block.** This is `git diff --color-moved=plain
--color-moved-ws=allow-indentation-change 03243fc HEAD` over
`engine.ts` and `run-resume.ts`. It detects 534 of the 546 deleted lines
and 582 of the 821 added lines as moved.
- **Deleted, not moved (12):** the signature heads of the twelve async
methods, each of which drops `async`.
- **Added, not moved (239):**
- the module's docblock, imports and host interface;
- the fifteen pointer docblocks and rewritten function heads;
- the `./run-resume.js` import and its comment, and the narrowed
`./runtime-identity.js` import;
- the twelve heads without `async`;
- the fifteen stub bodies;
- lines git does not pair with a moved block.
- **An independent check.** A script compared base and head `engine.ts`
against the new module.
- **Module lines.** 579 of the module's 689 lines are in 15 verbatim
runs of base `engine.ts`: the fifteen method texts, de-indented one
level. The script names each of the other 110:
- 76 head lines: the copyright line, the docblock, the imports and their
comment, the host docblock and the 25-member host interface;
- the 15 pointer docblocks;
- 19 signature lines: fifteen `export function` heads, and four `this`
parameter lines, each a `Pick` of the host.
- **Deleted lines.** Every line deleted from `engine.ts` (546 in all, 8
of them blank) is among the module's lines, apart from the 12 async
heads above. The two deleted import lines are among them, verbatim.
- **Added lines.** Of the 132 lines added to `engine.ts`, 101 are found
nowhere in base. They are:
- the narrowed `./runtime-identity.js` import;
- the `./run-resume.js` import with its comment;
- the twelve heads without `async`;
- the stub lines.
The other 31, such as `logger: this.logger,` and `};`, are lines base
already contains elsewhere.
- **Literals.** No string or template literal in the fifteen method
bodies spans a line, checked on the TypeScript AST of base `engine.ts`.
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 at head, and ESM equals CJS on each side. The bundle JS
bytes differ only because code moved within the bundle.
- **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 `03243fc6`, the head of `main`, so the merge-base
reading and the base reading are one reading.
- **Base** is `dist/`, built at `03243fc6` 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
`1a5162a4dd929399460a3936e22747a74cf0a8db24d398ffbab5d7ead7bf212e`.
- **Normalised sha256**, all four files: base
`0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232`, head
`0df32585069f743135440763aabff8c23473231181297c8d164f5f7ee4f10232`.
- **Raw diff**, `index.d.ts` base vs head: **0 lines**, with 19,626
lines on each side. 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 line 3583 of the head `index.d.ts`, a
private member whose body moved. Each control changes that one line,
then runs the same normaliser:
- `private loadSuspendedRunStrict;` made public
(`loadSuspendedRunStrict;`) gives
`6218e57ef5778f6cd7830065199a40ae791e8f5948ce7ba5b329a19b77ef6aa3`.
- The same member renamed (`private loadSuspendedRunStrictRenamed;`)
gives
`bc864cef7530af8f0921d14956e47c22a3d20aad6be293d6b494009cb1020c4e`.
- Both differ from `0df32585…`, 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
**One reader re-pointed.** `src/flow-activation-ledger.test.ts`'s
`ENGINE_SOURCES` holds the #10243 grep pins, which are absence checks.
It gains `./run-resume.ts`, so a moved body is not a place the retired
mechanism could come back unseen. That 1-line edit is the PR's third
file.
**Readers examined.** The base for each is `03243fc6`, 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.
- No `packages/spec/liveness` row cites any of the fifteen methods.
- **`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 #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 scanned-file count,
6691 at base and 6692 at head, the extra file being `run-resume.ts`.
- It reads 84 anchored files, 52285 citations and 1947 decision-letter
citations, the same at both ends.
- Neither `engine.ts` nor `run-resume.ts` is on the anchored surface.
- `docs/adr/0019-approval-as-flow-node.md` is not edited, and its
"module-private" line about `ENGINE_BUILT_SIGNAL` stays true (see
Scope).
- **`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.
- None of its four engine bodies moves.
- The scanned file count is 3460 at base and 3461 at head; the extra
file is `run-resume.ts`.
- **`path:line` citations.** The only one into this package's
`engine.ts` is `service-automation/src/engine.ts:44`, in
`packages/spec/scripts/liveness/evidence.test.ts`. It is a
realm-attribution fixture that `scanEvidence` parses without reading the
file.
- Lines of `engine.ts` from base `:68` to `:143` move up by 1. Lines
after base `:143` move down by 20 until the first moved body.
- Lines after the last moved body move up by 414, since the file goes
from 11,108 to 10,694 lines.
## Gates
- **Derived.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on this commit derives 60 families from the
3 changed paths: 1,369 changed lines, under the 3,000-line human-merge
threshold.
- All of them ran in this container on this commit, with each exit code
captured before any pipe, and all 60 exit 0.
- The run went in two parts. My own run limit stopped the first part
during the 51st family, so that family and the 9 after it ran again in a
second run. The 51st family's interrupted run is not counted.
- `dispatch-gates.mjs --ran` reconciles: 60 derived, 60 run, 0
NOT-MEASURED, 0 UNRUN.
- The one family more than slice 5's 59 is `node
scripts/ci/prune-turbo-cache.mjs --self-test`, which #25523 added to
`main`.
- **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.
- **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 three files passes.
- **Open PRs**, re-listed just before this PR was opened: 11. I diffed
each one's head against its merge base with `origin/main`. None of them
touches `service-automation/src`, the readers above, their ledgers, or
`docs/adr/0019-approval-as-flow-node.md`.
- #25545 edits `scripts/check-system-context-census.mjs`, but only its
`plugin-security` rows.
- The Version Packages PR #24876 touches only the package's
`CHANGELOG.md` and `package.json`.
- `main` has moved to `9c43e19f` (#25436, a `scripts/pm` queue guard,
and #25562, release notes), which touches none of these files.
```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/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
- The slice is the slice-6 group the remaining-bodies table named, less
`refuseInvalidScreenInput` and `buildSubflowResumeSignal`. Those two
stay so that ADR-0019's "module-private" line about
`ENGINE_BUILT_SIGNAL` stays true. The claim says so.
- Four host getters, in the slice-5 `store` form. No get/set pair is
needed.
- No `export` is added to `engine.ts`.
- After this slice, about 815 movable lines in bodies over 3 lines are
left for slice 7, and they need no ruling. The 21 site-bearing bodies,
and the five startup-registry bodies tied to `registerFlow`, wait on the
33-site ruling.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4395cbc commit ea6fe00
3 files changed
Lines changed: 822 additions & 547 deletions
File tree
- packages/services/service-automation/src
0 commit comments