Skip to content

refactor(service-automation): move 14 delete-bearing AutomationEngine bodies into their seam modules - #25725

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-mt4e9l
Oct 25, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-mt4e9l

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Leehom · project thread

Part of #25222 (slice 8a of the engine.ts split; #25222 remains open for slice 8b, resumeInternal).

Clause-②: no
Metadata-protocol: none

Claim 6391244018 (domain:server seat 1). Ruling-ref: 6384677413 (ruling B: slice 8 after the census reader, landed as #25660) and 6348891160 (the series' .d.ts bar). Resumed from the trial commit claude/project-thread-nirtwt@6305caf7 that Release 6385919610 handed over, rebuilt onto main f6ba1343b and re-proved there.

What moved

Move-only. The bodies of 14 AutomationEngine methods move, token for token, to the end of the seam module that already holds their section. No new module. Each class method keeps its name, docblock, signature and position, and calls its body with a host literal of exactly the members the body reads.

  • dispatch-claims.ts: pruneInProcessDispatchClaims
  • run-resume.ts: persistSuspendedRun, forgetSuspendedRun, evictConsumedSuspension, takeSubflowParentStrand
  • plugin-extension-api.ts: unregisterNodeExecutor, unregisterTrigger, unregisterConnector
  • flow-definitions.ts: unregisterConnectorProvider, withdrawFlow, hydrateFlowActivations, toggleFlow
  • consumed-suspension-restore.ts: journalConsumedSuspension, restoreOneConsumedSuspension

Shape B, as in slices 1 to 7. Methods go to the host as late-bound closures, so a spy set on the instance is still the one called. store and flowActivationStore go through getters, because setSuspendedRunStore and setFlowActivationStore reassign them. Every other field goes by value: a grep for this.FIELD = over the package source finds only the constructor's this.logger = logger among them, so the value passed is the field, including where an async body reads it after an await. The five async stubs drop async and return the promise the moved function makes. forgetSuspendedRun keeps its durableRecordAlreadyConsumed = false default in both places. engine.ts exports nothing new: the bodies read only names already exported (IN_PROCESS_DISPATCH_CLAIM_TTL_MS, MAX_CONSUMED_SUSPENSIONS, buildRunTrigger, and types).

The six bodies Release 6385919610 names as stays (execute, bubbleToParent, registerFlow, activateFlowTrigger, deactivateFlowTrigger, declineOntoDisabledSubflows) stay, for its reasons. Slice 8b (resumeInternal) is a later PR.

Rebuilt onto current main

The trial sat on 9dac9eb8d6. Since then main changed engine.ts (#25703 adds the constructor-read field credentialResolver) and flow-definitions.ts (#25643, #25703). The rebuild is a cherry-pick of 6305caf7 onto f6ba1343b with one textual conflict: both sides appended members to FlowDefinitionsHost (credentialResolver from main, ten members from the trial); both are kept. None of the 14 bodies reads credentialResolver (measured below), so no host gains it.

One reader the trial missed: flow-activation-ledger.test.ts

The trial's readings did not include vitest. On the rebuilt tree one test went red: the pin "the only writers of the activation projection are hydration and toggleFlow" cuts the engine's sources into members and asks which member writes flowLedgerDisabled. It cut on a four-space-indented line, which is a member's name in engine.ts but an ordinary statement inside a top-level function, so once hydrateFlowActivations and toggleFlow moved, the pin cut them mid-body and named a chunk carrying neither name. The pin now cuts a seam module only where a line starts in column 0, and keeps its engine.ts split (second commit, test file only). Control: with this.flowLedgerDisabled.add(name); injected into the moved withdrawFlow body, the pin goes red and names withdrawFlow; the file was restored by git checkout HEAD -- and its hash matched the HEAD blob f711ccad.

Move proof (TypeScript AST, base f6ba1343b, head 035ed1efa)

  • All 14 moved functions are token-identical to their base method: AST leaf tokens (kind and text) of the body, the parameters (the head's this parameter dropped), the return type and the async modifier. Body token counts: 41, 106, 103, 33, 30, 64, 103, 22, 22, 74, 129, 266, 134, 731.
  • Negative controls: a one-token on-disk edit in a scratch copy (rec.at < cutoff to rec.at <= cutoff in pruneInProcessDispatchClaims) reads DIFFERENT, and so does an in-memory one-token change to toggleFlow.
  • Hosts: for each of the 14, the set of this.X reads in the moved body equals the Pick keys of its this type, which equals the keys of the class stub's host literal, and the stub calls the function of its own name. No moved body reads credentialResolver.
  • Every line deleted from engine.ts (590) is found among the modules' added lines, except the five async signature lines the stubs drop and the 12 lines of the four rewritten import comments.

Readers, byte-identical at both ends

Taken in two worktrees, base f6ba1343b and head (re-taken on the final head):

  • check-startup-registry-verdict: 46 seams across 3,491 files, 39 read-only, both ends; --list identical apart from engine.ts line numbers, so registerFlow's line is unchanged; --self-test identical.
  • Tenant-audit census: check-tenant-audit-census.mjs and its --self-test identical (238 write call sites certified, 162 decidable, 23 prose figures, every UNTYPED_RECEIVERS row matched); tenant-audit-census.mjs human output identical; --json identical apart from its ref field, the measured commit.
  • check-system-context-census and its --self-test identical (149 elevation read sites in 77 files, 129 symbols); isystem-census.mjs --json identical.
  • check-platform-checklist (986 of 996 symbol anchors, the 10 on the [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 (2,384 anchors across 142 records), check-adr-anchors, and spec check:liveness (1,187 classified): each output identical.

.d.ts and bundles

  • dist/index.d.ts is raw byte-identical at both ends, sha256 a5cbe3bbb8aff2805674e1d9f119946199697e469a3e8a5b9bc7eb4fc08bc7cb (index.d.cts the same bytes), so the ruling's normalised identity holds trivially.
  • ESM and CJS export lists: 39 names each, the same list in both bundles, identical at both ends, with each export's typeof and function name unchanged.

Tests and gates (head 035ed1efa)

  • @objectstack/service-automation vitest: Test Files 201 passed (201), Tests 2636 passed (2636), the same at base f6ba1343b. Package typecheck (tsc --noEmit and check:test-typecheck) passes; ESLint over the package is clean.
  • dispatch-gates --commands derives 60 families on the 7 paths; all 60 exit 0, and --ran reconciles 60 derived, 60 run, 0 NOT-MEASURED from recorded exit codes. Two needed a prerequisite first, not a fix: check-plugin-teardown-shape --self-test needed its pinned fixture commit fetched into this shallow clone, and check:dual-build-cjs-loads needed the workspace's dist/ trees.
  • check-governed-merges --branch: 0 of 7 paths on the register, NOT governed; 1,582 changed lines, under 3,000.
  • skip-changeset: nothing published changes (the .d.ts bytes and the export lists are identical).

Acceptance notes


Generated by Claude Code

… bodies into their seam modules

Move-only. The bodies of 14 AutomationEngine methods move, token for
token, to the end of the seam module that already holds their section;
each class method keeps its name, docblock and signature and calls its
body with a host literal of exactly the members it reads.

- dispatch-claims.ts: pruneInProcessDispatchClaims
- run-resume.ts: persistSuspendedRun, forgetSuspendedRun,
  evictConsumedSuspension, takeSubflowParentStrand
- plugin-extension-api.ts: unregisterNodeExecutor, unregisterTrigger,
  unregisterConnector
- flow-definitions.ts: unregisterConnectorProvider, withdrawFlow,
  hydrateFlowActivations, toggleFlow
- consumed-suspension-restore.ts: journalConsumedSuspension,
  restoreOneConsumedSuspension

Each host interface gains the members the new bodies read;
flowActivationStore goes through a getter, because
setFlowActivationStore reassigns it. The module headers and the
engine.ts import comments name the new bodies.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMkNEokXPqZh51HTHCnyph
…am module at its top-level declarations

The pin splits the engine's sources into members and asks which one
writes the activation projection. A moved body's statements sit at the
four-space indent a class member's name has in engine.ts, so the
member split cut hydrateFlowActivations and toggleFlow mid-body once
they moved, and the pin named a chunk that carries neither name. A
seam module is now cut only where a line starts in column 0; engine.ts
keeps its split.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UMkNEokXPqZh51HTHCnyph
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 25, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-automation, touching 79 documentable anchor(s).

5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx (via /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))
  • content/docs/api/declarative-endpoints.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it))
  • content/docs/automation/approvals.mdx (via /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))
  • content/docs/automation/flows.mdx (via getRun (symbol, a field of interface ConsumedSuspensionRestoreHost), getRun (literal, a string literal in getRun; a string literal in restoreOneConsumedSuspension), /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it))

⛔ 5 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level type))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level type), getRun (symbol, a field of interface ConsumedSuspensionRestoreHost), getRun (literal, a string literal in getRun; a string literal in restoreOneConsumedSuspension))
  • content/docs/releases/v17/17-1.mdx (via /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))
  • content/docs/releases/v17/17-6.mdx (via AutomationEngine (symbol, a top-level type), /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))
  • content/docs/releases/v17/17-7.mdx (via RESUME_IN_PROGRESS (literal, a string literal in restoreOneConsumedSuspension), RUN_NOT_FOUND (literal, a string literal in restoreOneConsumedSuspension), STORE_UNAVAILABLE (literal, a string literal in restoreOneConsumedSuspension), /api/v1/automation/:name/runs (route, bridged from symbol getRun — its route source's handler names it), /automation/:name/runs/:runId (route, the route ledger binds it to client method automation.getRun))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 01d0a20bbbaac90a5ee2481e01b7009bf29d877f — the merge of head 035ed1efa423a8d135616afaf09f5658e907b319 into base 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 01d0a20bbbaac90a5ee2481e01b7009bf29d877f && git checkout 01d0a20bbbaac90a5ee2481e01b7009bf29d877f
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881 035ed1efa423a8d135616afaf09f5658e907b319 && git checkout -B drift-repro 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881 && git merge --no-ff 035ed1efa423a8d135616afaf09f5658e907b319

node scripts/docs-audit/affected-docs.mjs --json 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1252db9a7099e3ea49fdfa2bb2c0d2ba97de4881 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:server seat 1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC · 2026-10-25T01:06Z

Head 035ed1efa, report 6391731963, claim 6391244018 (Resume-from claude/project-thread-nirtwt@6305caf7).

item reading
Shape draft, base main, Part of #25222 (no closing keyword; the card stays open for slice 8b, resumeInternal), skip-changeset label, Clause-②: no line-initial.
Files (7, +932/−650) engine.ts and the five seam modules the claim names, plus flow-activation-ledger.test.ts. Exactly the claim's surface; no packages/spec/**, no docs/adr/**. Not governed (check-governed-merges --pr 25725: 0 of 7 paths), 1,582 changed lines.
Move-only report's AST proof: all 14 bodies token-identical to the base methods, with a one-token negative control reading DIFFERENT. Seat's own read on the diff: no moved body assigns a this.<field> (writes stay in the class), and every reassignable field a moved body reads (store, flowDispatchStore, flowActivationStore) reaches the host through a live getter, not a snapshot. Hypothesis measured true: no moved body reads credentialResolver.
Test change the flowLedgerDisabled writer pin now cuts a seam module only at column-0 lines and keeps the engine.ts split unchanged; the assertion (only hydrateFlowActivations / toggleFlow write it) is unchanged. Negative control on this head: an injected write in moved withdrawFlow turns it red. Accepted deviation: the trial missed this, the second commit fixes it.
.d.ts byte-identical at base and head; ESM/CJS export lists identical (39). Ruling 6348891160 holds.
Gates dev 60 derived, 60/60 exit 0; vitest 201/201 files, 2,636/2,636 tests at both ends. CI at this act: in progress. Lint & Repo Gates and Type Check owed success before ready.
Landing ready + auto-merge through the relay once every check on this head is green or an expected skip. Slice 8b (resumeInternal) is the next claim on #25222 after this merges.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 25, 2026 01:35
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 25, 2026 01:35
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 25, 2026
Merged via the queue into main with commit f3fea0a Oct 25, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-mt4e9l branch October 25, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants