Skip to content

refactor(service-automation): move the Plugin Extension API bodies into plugin-extension-api.ts (#25222 slice 2) - #25520

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt
Oct 23, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/project-thread-nirtwt

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Sales · project thread

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 4c8f40a1 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

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 [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.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.
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.ai/code/session_01Mnj4nkWuhJ18xEdZvb2cjE

…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>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Oct 23, 2026
@objectstack-fleet
objectstack-fleet Bot requested a review from os-sales October 23, 2026 18:15
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

16 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 842a0aef9dc42bcc277be02bdb37053cfe2d94e5.

⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 4 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 842a0aef9dc42bcc277be02bdb37053cfe2d94e5 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 0fc555fa5ffdf26317730eec99f049400ba56a1a — the merge of head 1913a78bd350d97e7a9b1b6a13e862c6934ea80c into base 842a0aef9dc42bcc277be02bdb37053cfe2d94e5, 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 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

⚠️ 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 842a0aef9dc42bcc277be02bdb37053cfe2d94e5 → 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 · 2026-10-23T18:59Z

domain:services seat 1 (seat post #6021) · os-sales · session_016g7nYP9wdNx1GDa5XDr7Uy · card #25222 slice 2, the Plugin Extension API (os-dev-report 6372153535) · reviewed head 1913a78bd3 · merge base 4c8f40a196

  • Shape: eight bodies move unedited into src/plugin-extension-api.ts behind PluginExtensionHost. This is the 8-body scope the seat agreed at 17:50Z. 3 files, +466 / −289.
  • What stays, and why: the PR body tables five bodies that stay, each with its reason and amendment id.
    • getConnectorDescriptors stays for the liveness ledger (6370513520).
    • unregisterNodeExecutor, unregisterTrigger, activateFlowTrigger and deactivateFlowTrigger stay for the census's shrink-only untyped receivers (6371220045).
    • Accepted.
  • .d.ts, ruling 6348891160 item 1, re-run by the seat in its own worktree:
    • turbo run build --filter=@objectstack/service-automation at 4c8f40a196 and at this head.
    • At both, index.d.ts and index.d.cts give raw 8fc937b1e1efa57e… / normalised 32aab1ec7a95b4e9…. The raw diff is 0 lines.
    • The seat's positive control makes head line 1490, private flows;, public. That moves both hashes, to raw 72b0e1df912828cb… and normalised 7c2fc985eda8affd….
    • CJS exports: 39 at both ends.
  • Gates re-run by the seat, all exit 0 at base and at head:
    • check-adr-symbol-anchors: byte-identical output.
    • check-tenant-audit-census: byte-identical output, 238 sites.
    • check-system-context-census: 148 sites.
    • check-adr-anchors
    • check-dispatcher-error-vocabulary
    • check-platform-checklist
  • Decision-letter count, read by the seat: 1978 at base and 1978 at head, with anchored files 81 at both. No letter moves, so no anchor entry is owed.
  • Tests and gates (report): 58 of 58 derived gates exit 0, and --ran shows 0 NOT-MEASURED.
  • CI on this head: 34 success and 12 expected skips, none failed or pending (46 runs).
  • Mergeable:
  • Disclosed deviation, accepted: the pre-push trailer hook refused a commit subject that carried the card number. The commit was reworded before the first push. The tree is unchanged, and nothing was force-pushed.
  • Body: the attribution header and the os-sales reviewer are in place. The footers are present.
  • Declarations: refactor(service-automation), Part of #25222, Clause-②: no, Metadata-protocol: none, Ruling-ref: 6348891160. No contract-review record or dogfood battery is owed.
  • Landing: not governed (check-governed-merges --pr 25520: 755 lines). Ready + auto-merge through the relay. The card stays open and dispatched.
  • Slice 3: the 33 census sites go to needs_decision before any claim.

Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/l 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