Skip to content

fix(objectql): a flow a composed stack declares only on its top level, beside package bodies, registers with the automation engine at boot - #28091

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

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

domain:server#4 · os-elon-musk · session_016WV4cyVfkboDHh1cixMUJn

Fixes #28048

Clause-②: yes (widening)
Metadata-protocol: none

维护者速读

  • 改了什么:合成栈(composeStacks,manifest: 'preserve')里,某个输入带 packages[] 包体、又只在顶层声明了一个 flow(没有包体声明同名)时,ObjectQL 的 manifest 服务现在把这个“残留”flow 注册进 ObjectQL 注册表,归属栈自己的 manifest.id,和残留对象、残留连接器已有的做法一样;自动化引擎开机即注册并可运行它。
  • 为什么改:实测修复前,这个 flow 进了元数据服务(/meta 能列出),但自动化引擎开机只从注册表拉 flow,所以 getFlow 返回空、执行报 “not found”,触发器不会挂上:声明了却静默不生效。
  • 风险与回滚:只多注册一种原本漏掉的残留项(开机能跑的东西变多了,属放宽);包体声明的 flow 仍只注册一次、归包体;单栈与常规合成不变。不改 spec,不改作者能写什么。回滚即 revert 本 PR。
  • 先看:packages/objectql/src/plugin.ts(unclaimedTopLevelItemsOfType 与 manifest 服务里的残留注册循环)。
  • 两分钟验证:pnpm --filter @objectstack/service-automation exec vitest run src/residual-top-level-flow-boot.test.ts(需先构建 @objectstack/objectql),预期 3 passed:残留 flow 开机注册、归栈 id、可运行;包体 flow 只注册一次;无顶层 flow 的栈不变。
  • 需要你做的:无,席位落地(欠一次契约复审,席位安排)。

What changes

  • packages/objectql/src/plugin.ts. unclaimedTopLevelConnectors(stack) becomes unclaimedTopLevelItemsOfType(stack, type) for type 'connector' | 'flow': the residual rule's own answer (unclaimedTopLevel, @objectstack/metadata), filtered to one type, as unclaimedTopLevelObjects reads it for objects. The manifest service registers the residual's connectors and flows in one loop, in the place the connectors already registered: after every body and after the residual objects, under the residual rule's id, through registry.registerItem(type, …), the verb a body's connectors and flows take. Each item is stamped with the stack's version on a shallow copy, so the artifact's own entry is left as it arrived. The "empty packages: []" warning no longer fires for a stack whose only residual is a flow. The connector path is unchanged in behaviour; its docblock moved into the shared reader.
  • packages/services/service-automation/src/residual-top-level-flow-boot.test.ts (new). A real kernel (ObjectKernel, ObjectQLPlugin over SQLite, AutomationServicePlugin) boots composeStacks([mixed, other], { manifest: 'preserve' }) through the manifest service, the way AppPlugin hands a config boot's stack to it. Pins: the residual flow registers with the engine at boot, owned by the stack's id, and a run succeeds; a flow a package body declares and the top level repeats is registered once, owned by the body; CONTROL: a stack with no top-level flow registers exactly the bodies' flows.
  • packages/objectql/src/manifest-service-residual-objects.test.ts. Three engine-half pins, mirroring the connector ones: a residual flow registers under the stack's id and version beside a body's own; it registers when the stack has no other residual item; CONTROL: a top-level flow a body repeats is the body's, registered once.
  • .changeset/28048-residual-top-level-flow-registers.md. @objectstack/objectql patch.

Why

The measurement (before the fix, at origin/main 2e955dde)

The new boot test's stack, booted against the unfixed @objectstack/objectql build: mixed carries packages: [{ manifest: { id: 'com.acme.orders', flows: [acme_body_flow] } }] and, on its top level only, flows: [acme_residual_flow].

  • ql.registry.listItems('flow'): [["acme_body_flow","com.acme.orders"]]. The residual flow is absent.
  • automation.listFlows(): ["acme_body_flow"].
  • automation.getFlow('acme_residual_flow'): null; getFlow('acme_body_flow') answers.
  • automation.execute('acme_residual_flow'): { success: false, error: "Flow 'acme_residual_flow' not found" }.

The card's premise held. The composed stack's manifest.id here is the last input's (com.acme.other), which is the id the residual rule names; the pin reads it off the stack rather than hard-coding it.

Why flows, and why this path

The triage ruling (6521012414) places the fix in the manifest service and names flows. Flows are a type a boot-time reader takes from the registry alone (the automation plugin's boot pull), exactly like connectors, so they take the connectors' path. The card's wider option (register the whole residual through registerMetadataCollections) lives in packages/objectql/src/engine.ts, which is a hot file held by #25522 and outside this claim's surface; it was not taken.

Clause-②

yes (widening): a declared top-level flow that was silently dropped now registers and runs at boot. Nothing an author can write is refused that was accepted before, and no key is added (Metadata-protocol: none).

Verification

  • residual-top-level-flow-boot.test.ts: red before the fix (1 of 3: the residual case), green after (3 of 3), against a rebuilt @objectstack/objectql. connector-credential-residual-connector.test.ts stays 4 of 4.
  • manifest-service-residual-objects.test.ts: red before (2 of 15: both residual-flow pins; the flow CONTROL green), 15 passed after.
  • Ablation on the committed head with scripts/ablation-replace.mjs ((['connector', 'flow'] as const) → (['connector' /* ABLATION-28048 */] as const), objectql rebuilt under the mutation): engine pins 2 failed / 13 passed, boot pins 1 failed / 6 passed (the connector pins stay green); restored, git diff HEAD empty, rebuilt, green.
  • Full suites: @objectstack/objectql 494 files, 9603 passed; @objectstack/service-automation 226 files, 2860 passed. pnpm --filter @objectstack/objectql typecheck and pnpm --filter @objectstack/service-automation typecheck: exit 0. pnpm lint: exit 0.
  • dispatch-gates --commands derived 63 families on the committed head; all run with exit codes recorded. One exit 3 on the first pass (check:dual-build-cjs-loads, no dist for 41 packages), measured green after turbo run build --filter='./packages/**'. --ran: 63 derived, 63 run, 0 NOT-MEASURED (a derived zero). The head is 10 commits behind origin/main; none of them touches the files this diff changes.

Acceptance notes

  • Other residual types. The manifest service now registers the residual's objects, connectors and flows. The other registry-read collections a residual can carry (permissions and the rest of METADATA_ARRAY_KEYS) are still metadata-service only, as the card notes by reading. Not measured and not widened here (the triage ruling names flows); named in the os-dev-report.
  • Both doors. The test boots the config door's hand-off to the manifest service. The artifact door hands the engine the same stack through the same service; it was not booted separately.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English (the 维护者速读 block is the repo's own Chinese reader section).
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cyVfkboDHh1cixMUJn

…, beside package bodies, registers with the automation engine at boot

The manifest service registered a multi-package stack's residual objects
and connectors only. The automation plugin's boot pull reads flows from
the ObjectQL registry, so a residual flow was listed by the metadata
service and never registered with the engine. Residual flows now take the
connectors' path: after every body, under the residual rule's id, stamped
on a shallow copy.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cyVfkboDHh1cixMUJn
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/objectql, touching 3 documentable anchor(s).

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

  • content/docs/data-modeling/objects.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services-checklist.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/kernel/services.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via ObjectQLPlugin (symbol, a top-level class))

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

  • content/docs/releases/v17/17-0.mdx (via ObjectQLPlugin (symbol, a top-level class))
  • content/docs/releases/v17/17-1.mdx (via ObjectQLPlugin (symbol, a top-level class))

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
  • the SDK route bridge reached 58 of 210 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 — 20 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 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e02989dd532df0026150f02e1e203a39c0d8a814 — the merge of head ea51ef0d69650160126edf7da3102aad0756eab3 into base 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e, 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 e02989dd532df0026150f02e1e203a39c0d8a814 && git checkout e02989dd532df0026150f02e1e203a39c0d8a814
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e ea51ef0d69650160126edf7da3102aad0756eab3 && git checkout -B drift-repro 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e && git merge --no-ff ea51ef0d69650160126edf7da3102aad0756eab3

node scripts/docs-audit/affected-docs.mjs --json 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e

⚠️ 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 6b125a7ab7ba57b7c4f783ac1ef76b881659a96e → 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

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head ea51ef0 · 2026-10-30T12:26Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: ea51ef0d69650160126edf7da3102aad0756eab3
Local-runs: none
Scope: packages/objectql/src/plugin.ts, packages/objectql/src/manifest-service-residual-objects.test.ts, packages/services/service-automation/src/residual-top-level-flow-boot.test.ts + packages/metadata/src/plugin.ts (unclaimedTopLevel, ARTIFACT_FIELD_TO_TYPE, registerArtifactBodyCollections), packages/objectql/src/engine.ts (registerMetadataCollections, stampPackage, resolveMetadataItemName), packages/objectql/src/registry.ts (SchemaRegistry.registerItem), packages/services/service-automation/src/plugin.ts (the start() boot pull, syncFlowsFromProtocol, resolveFlowContenders, packagedFlowReader), packages/metadata-protocol/src/protocol.ts (packagedArtifactOwner), packages/spec/src/stack.zod.ts (composeStacks steps 3a and 8, preservePackageEntries), scripts/check-test-source-alias.mjs (the @objectstack/service-automation row), .changeset/28048-residual-top-level-flow-registers.md (read for ② only). Reviewed at 2026-10-30T12:42Z, face declaration (the PR's Clause-②: yes (widening)), served at the tier the brief names. The remainder of the diff (the changeset) is prose outside ④.

Inputs read: card #28048 (body, triage 6521012414, claim 6521637331, os-dev-report 6522793952), PR #28091 (body, file list, git diff origin/main...ea51ef0d in the local clone), the head's check-runs. Not the dispatch order.

① Derived judgments

  • Accept set: unchanged — right. No schema, no key, no value moves (packages/spec untouched; Metadata-protocol: none holds). A stack that was accepted before is accepted now; nothing previously accepted is refused.
  • Public surface, boot: the registry now holds the residual's flow items — right. unclaimedTopLevel (packages/metadata/src/plugin.ts:405) lists every top-level item whose (type, name) slot no body claims, flows: 'flow' among them (ARTIFACT_FIELD_TO_TYPE:144). The new unclaimedTopLevelItemsOfType(artifact, 'flow') filters that one answer; nothing is re-derived (the rule's ⛔ holds). The automation plugin's start() boot pull reads ql.registry.listItems('flow') alone (service-automation/src/plugin.ts ~1489), so a residual flow now reaches the engine, under the stack's manifest.id, exactly as residual objects (ADR-0130 D4) and connectors (ADR-0015 D7) already do.
  • The verb is the body's verb — right. A body's flows land through registerMetadataCollections → registerItem(pluralToSingular(key), stampPackage(item, ownerId, ownerVersion), 'name', ownerId) (engine.ts:8977). The residual path calls registerItem(type, stamped, 'name', ownerId) on a shallow copy stamped by applyProtection when a version exists; stampPackage (engine.ts:4673) does the same stamping, on the original. registerItem itself stamps _packageId when none is set, so the ownerVersion === undefined branch still lands owned. resolveMetadataItemName for flows reads item.name, which artifactItemSlot already required, so the two paths cannot disagree on the key. The pinned "artifact's own entry left as it arrived" is the one deliberate difference, and the right one for an entry two doors read.
  • Classification downstream — right, by parity. packagedArtifactOwner (metadata-protocol/src/protocol.ts:16010) answers from the registry's _packageId, so a residual flow is classed packaged under the stack's id: ADR-0126 §2 sealing, the activation ledger's attribution and resolveFlowContenders precedence all treat it as the residual connectors and objects are treated. Same as the metadata door stamps it (registerArtifactBodyCollections with the residual's provenance). No new class is minted.
  • The empty packages: [] warning no longer fires for a flow-only residual — right. Something was registered.
  • No boot-refusing class for flows — right to register unconditionally. registerItem refuses only security-catalog names, default record pages, package actions and picklist duplicates; validate is diagnostic. The objects path's two unserved classes (ADR-0029 ownership, dangling picklist) have no flow analogue, so the connectors' unconditional loop is the correct template.
  • Late artifact (after start()) — unchanged, right to leave. A residual flow registered then sits in the registry until a protocol sync arms it, which is what a body's flow registered through registerApp at that moment also gets. bridged and the objectql: a foreign bound action named like an action the target object already declares is not attached and nothing reports it, while the two packages' handlers overwrite each other silently — the collision follow-up of #24400 (PR #24458) #24472 arrived set keep reading residual?.ownerId (objects); the bridge moves objects only and flows carry no bound actions, so neither needs the flow owner.
  • Changeset coverage — right. Only @objectstack/objectql publishes a moved source; service-automation gains a test file only.

② Semver level

  • .changeset/28048-residual-top-level-flow-registers.md: @objectstack/objectql: patch, with both declaration lines line-initial. The PR body carries the same two lines. Check Changeset on the head: success.
  • Clause-②: yes (widening) — judged right. The question is 本卡放宽接受集或扩大公开面吗: the accept set is unchanged, but what a boot registers and runs widens (a declared flow that was dropped now registers, is listed by the registry, and executes). no would understate a behavioural widening at the boot door; (narrowing) has no basis — nothing is refused. The arm is the one clause2-line.mjs reads as a widening, not malformed.
  • Metadata-protocol: none — right. No key, value, type, meaning or default an author writes against changes.
  • Level: AGENTS.md step 3 — the level follows the kind alone; none takes patch; a non-protocol widening ships patch. patch is correct. (The "both take at least minor" line in clause2-line.mjs's docblock predates ruling A, which that file's own header and check-changeset-no-major.mjs:907-910 state; the gate enforces the ruling, and the gate is green.)

③ Boundary flags

  • Dev open_questions: none declared.
  • Dev out_of_scope_findings [1]: the residual's other registry-read collections (permission sets and the rest of METADATA_ARRAY_KEYS) stay metadata-service only; card Task item 2 asked for a security-domain pin, the triage ruling 6521012414 names flows only, so that half was not taken; the dev says "Closing this card with Fixes A flow a composed stack declares only on its top level, beside package bodies, is never registered with the automation engine at boot — the manifest service registers the residual's objects and connectors only #28048 leaves it untracked unless the seat files it." Answered: the narrowing to flows is the triage ruling's, so Fixes #28048 stands; the remainder is a reproducible defect by the card's own reading and under Prime Directive 10 needs a card, not a note. Escalated to the owning seat (this review is read-only): file the follow-up card for the residual's remaining registry-read types (permission sets first — plugin-security's packaged-permission-set-lock.ts reads listItems('permission')) before or at this PR's landing. Should-fix, process; no code change on this head.
  • Both doors (PR acceptance note): the artifact door hands the same stack to the same manifest service; the engine-half pins in manifest-service-residual-objects.test.ts exercise that service directly with a hand-built payload, so the fix is pinned independent of the door. Answered; no second boot owed.
  • Head is 12 commits behind origin/main (merge-base 2e955dde). Two of them touch service-automation (ec7e15ab engine.ts, 4793aa4a run-resume.ts); none touches plugin.ts, the boot pull, or any file this diff changes. CI ran on the merge commit e02989dd. No rebase owed.
  • Check-runs on ea51ef0d at 2026-10-30T12:42Z: green — Build Core, Check Changeset, Governed Surface Queue Guard, every Lint · job and Lint & Repo Gates, Type Check · source gates / consumer gates / debt ledger / workspace, Test Core 1/8, 2/8, 3/8, 6/8, 7/8, 8/8, Test UI, Live E2E UI, PR-body guards (32 of 34 non-skipped runs concluded success). Still pending at record time: Test Core 4/8 and 5/8 (and therefore the required Test Core context). None red. The verdict below is the review's; the landing still waits for every required context to conclude success, which the owning seat reads before it arms anything.

④ Design judgment

packages/objectql/src/plugin.ts — holds.

  • Scenario 1, the card's: composeStacks([mixed, other], { manifest: 'preserve' }) where mixed carries packages: [orders body] and a top-level flow no body declares. Read in stack.zod.ts: preservePackageEntries contributes mixed's declared packages[] untouched and wraps other as a body; step 8 keeps the flattened top level because packages[] does not carry every input collection. So the composed stack's top level carries the flow, its bodies do not, and unclaimedTopLevel lists it under manifest.id (the last input's, com.acme.other). The new loop registers it after every body; the boot pull in start() finds it. The PR's reading of the owner matches the tree.
  • Scenario 2, double registration: a top-level flow a body repeats is claimed by claimedArtifactSlots on (flow, name), so it is not residual and is registered once, by registerApp, under the body. Any body claims it, not only its own input's. Pinned twice (engine half and boot half).
  • Scenario 3, ordering: manifest.register runs inside a dependent plugin's init() (AppPlugin's, and the test's test.stack which depends on both the engine and the automation plugin); every init() completes before any start(), and the automation boot pull is in start(). The registration is synchronous before the bridge promise. Holds.
  • Scenario 4, no top-level flows: unclaimedTopLevelItemsOfType answers undefined, the filter drops it, residualItems is empty, and the packages: [] warning condition reduces to the live tree's. Pinned by the CONTROL pins.
  • Scenario 5, stored row of the same name: ADR-0048 coexistence under distinct composite keys; resolveFlowContenders arms the packaged body and shadows the stored row, as for any packaged flow. Holds.
  • Should-fix (non-blocking): unclaimedTopLevel(artifact) is now evaluated three times per register call — once in unclaimedTopLevelObjects and once per type in the new map — each re-running resolveArtifactPackageOrder (a schema parse of every body) and claimedArtifactSlots. The live tree already paid it twice; the generalisation was the place to read the residual once and filter by type. Registration-time cost only, no behavioural effect; fix on the next touch of this seam.

packages/objectql/src/manifest-service-residual-objects.test.ts — holds. The three pins reuse the file's residualStack / payload helpers (the shape AppPlugin.init hands over) and assert (name, _packageId, _packageVersion) off listItems('flow'): residual under RELEASE_ID/1.0.0 beside the body's own under SERVICE_ID/2.4.0; a flow-only residual (both top-level objects claimed); CONTROL repeat registered once under the body. The "entry left as it arrived" pin reads the artifact's own object. The dev's ablation (['connector'] only) reds exactly these two residual pins and keeps the CONTROL green, which is the discriminating shape.

packages/services/service-automation/src/residual-top-level-flow-boot.test.ts — holds. Real kernel (ObjectKernel, ObjectQLPlugin over SQLite :memory:, AutomationServicePlugin) booting the composed stack through the manifest service; asserts the registry owner, listFlows, getFlow non-null and a successful execute — the card's four measured readings, each one inverted. Its value imports (@objectstack/core, @objectstack/objectql, @objectstack/driver-sql, @objectstack/spec) are all in KNOWN_UNALIASED_TEST_IMPORTS['@objectstack/service-automation'] (scripts/check-test-source-alias.mjs:550), so the gate holds; the package's tsconfig.test.json includes src/**/*, so the file is compiled. The owner is read off the composed stack rather than hard-coded, which is right given selectManifest's last rule. No cross-package file read, so the cross-package-inputs gate is not engaged.

Must-fix: none.

Implemented-by: claude/project-thread-i1zxkw
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

维护者速读

  • 改了什么:合成栈里只在顶层声明、没有包体认领的 flow,开机时注册进 ObjectQL 注册表(归栈自己的 manifest.id),和残留对象、残留连接器一样;自动化引擎开机即能找到并运行它。
  • 为什么改:修复前这种 flow 能在 /meta 列出,但引擎开机只从注册表取 flow,执行报 not found、触发器不挂:声明了却静默不生效(A flow a composed stack declares only on its top level, beside package bodies, is never registered with the automation engine at boot — the manifest service registers the residual's objects and connectors only #28048)。
  • 风险与回滚:Clause-②: yes (widening),开机能跑的东西多了一类;包体声明的 flow 仍只注册一次、归包体;单栈与常规合成不变;不改 spec。回滚即还原本 PR。
  • 先看:packages/objectql/src/plugin.ts(unclaimedTopLevelItemsOfType 与 manifest 服务里的残留注册循环)。
  • 两分钟验证:先构建 @objectstack/objectql,再 pnpm --filter @objectstack/service-automation exec vitest run src/residual-top-level-flow-boot.test.ts,3 passed。
  • 需要你做的:无(非受管;契约复审 PASS;席位按本任落地授权落地)。

席位意见:ACCEPT。

席位核对

Head-sha: ea51ef0d69650160126edf7da3102aad0756eab3

  • Shape: 4 files, +258 / −36: objectql/src/plugin.ts registers residual top-level flows the way it already registers residual connectors; one objectql pin file and one service-automation boot test; a patch changeset for @objectstack/objectql. check-governed-merges --pr 28091: NOT governed, 294 lines.
  • Contract review: PASS on this head, record 6523113357 (CONTRACT_REVIEW_TIER, no must-fix). Should-fix 1 (the residual is computed once per type at register time) is registration-time cost only and is left for the next touch of this seam. Should-fix 2 (the residual's other registry-read types, permission sets first) is already filed as objectql: a composed stack's residual top-level permissions (and other registry-read types) are never registered, like flows were before #28048 #28093, so Fixes #28048 stands under triage ruling 6521012414, which names flows only.
  • Gate check-runs on the head: 48 runs, 35 success, 13 skipped, 0 failure/pending (required Test Core aggregate 8/8 included); mergeable_state clean.
  • Docs drift advisory: it matched pages only through the ObjectQLPlugin symbol; none of them describes residual-flow registration, so no doc change is owed.
  • Landing: under the maintainer's landing grant for this seat term (seat 4 thread, 2026-10-29 22:04Z): pr_ready + automerge_enable through the relay.

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

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant