Skip to content

fix(objectql): every item a composed stack declares only on its top level, beside package bodies, registers in the engine at boot - #28153

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-7q2yv3
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-7q2yv3

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Elon · project thread

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

Fixes #28093

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

维护者速读

  • 改了什么:合成栈(带 packages[])只在顶层声明、没有包体声明的“残留”项,ObjectQL 的 manifest 服务以前只把对象、连接器、流程注册进引擎注册表;现在其余所有类型(权限集、岗位、能力、共享规则、队列、分派规则、视图、应用、动作、钩子等)都按包体同类项的同一路径注册,归属栈自己的 manifest.id 与版本。
  • 为什么改:实测 main 上 28 种残留类型里有 26 种从未进注册表;只问注册表的读者(包括元数据保存门的托管内容封印)把它们当作未声明。安全层面:残留的顶层安全规则没有像包体里的同一规则那样被封印,现在已注册并封印(机制只交维护者)。
  • 风险与回滚:注册的东西变多(放宽);包体已有的判官同样作用于残留项,所以残留项若本身不合规(权限集 packageId 指向别的包、视图容器名不一致、动作不合 schema、声明了队列却没组合 ownership-queue 服务)会像包体一样在开机时被拒绝。picklist 维持只进元数据服务。回滚即 revert 本 PR。
  • 先看:packages/objectql/src/plugin.ts(unclaimedTopLevelItems 与 registerUnclaimedTopLevelItems)。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/manifest-service-residual-objects.test.ts src/manifest-service-residual-security-seal.test.ts,预期 60 passed。
  • 需要你做的:无,席位落地(欠一次契约复审,席位安排)。

What changes

  • packages/objectql/src/plugin.ts.
    • The manifest service reads the residual rule's answer (unclaimedTopLevel, @objectstack/metadata) once per registration and hands it to both readers (the fix(objectql): a flow a composed stack declares only on its top level, beside package bodies, registers with the automation engine at boot #28091 contract review's should-fix).
    • unclaimedTopLevelItemsOfType(stack, 'connector' | 'flow') becomes unclaimedTopLevelItems(residual): every residual item except the types in RESIDUAL_TYPES_WITH_OWN_PATH (object, which keeps its own path; picklist, which stays metadata-only because the residual objects rule is judged on that premise; object_extension, which the residual rule never lists).
    • New registerUnclaimedTopLevelItems: after every body and after the residual objects, under the residual rule's id with the stack's version stamped on a shallow copy, through the registration a body's item of that type takes. A view container is filed under its object and expanded into its views (ADR-0017) after the one divergent-name judge (viewContainerNameRefusal); a permission set is judged by the one owner-binding judge (judgePermissionSetOwnerBinding, ADR-0086 D3) in the body seam's envelope; every other item is one registry.registerItem(type, …).
    • The late-artifact bound-action target check now judges the residual's actions with the bodies'.
  • packages/objectql/src/manifest-service-residual-objects.test.ts. New describe: every formerly dropped type registers under the stack's id and version (one case per type, and all at once); a residual view container registers under its object and expands; a divergent residual view container is refused in the body seam's words; residual queues and assignment rules register when the ownership-queue service is composed, and refuse the boot like a body's when it is not; a residual permission set whose packageId names another package is refused; a top-level item a body repeats is the body's, registered once; CONTROL: a stack with no residual item registers exactly the bodies' items.
  • packages/objectql/src/manifest-service-residual-security-seal.test.ts (new). The security pin. Population read from the type registry (every domain: 'security' type with allowEnvironmentOverlay: false): each residual item is registered under the stack's id, an environment save over it is refused 403 NOT_OVERRIDABLE with nothing stored, and the by-name read still serves the declared permission set.
  • .changeset/28093-residual-top-level-items-register.md. @objectstack/objectql patch.

Why

Before (measured at origin/main 91a423ef)

A composed stack with packages[], booted through the manifest service, whose top level declares one item of each registry type that no body declares:

Reaches the ObjectQL registry Types
yes object, connector, flow
no (metadata service only) app, view, page, dashboard, report, action, workflow, position, permission, capability, sharing_rule, queue, assignment_rule, api, webhook, agent, tool, skill, rag_pipeline, hook, mapping, picklist, analytics_cube, email_template, doc, book

Security, at rule level: a residual top-level rule of type permission or position was not sealed as managed content the way the same rule in a package body is; now it is registered and sealed. The other security types (capability, sharing_rule, queue, assignment_rule) were not reachable the same way. A residual queue or assignment rule on a composition without an ownership-queue service was silently inert, where a body's refuses the boot; it now refuses the same way.

Why this path

The triage ruling: register each dropped type the way objects, connectors and flows are, or refuse it loudly. Every type is registered; none needed a new refusal. The body seam itself (registerMetadataCollections) lives in packages/objectql/src/engine.ts (hot file, #25522), outside the claim's surface, so the residual registration calls the same judges and the same shared helpers the body seam calls rather than that method.

Clause-②

yes (widening): declared residual items that were not registered in the engine now are. What a body's item of the same type is judged by now judges the residual's too, so a residual item a body's would be refused for (an off-contract action, a permission set whose packageId names another package, a divergent view container, a queue with no ownership-queue service) now refuses the boot. The divergent view container was already refused by the config boot's metadata door; the others reach only strict: false input or a composition without the queue service. Metadata-protocol: none: nothing an author can write changes.

Verification

  • New pins red on origin/main: 41 of 45 (green there: the two CONTROL pins, the security file's population check and its runtime-create control). Green after: 60 of 60 in the two files.
  • Ablations on the committed head (scripts/ablation-replace.mjs, each restored with git diff HEAD empty): residual registration removed → 45 failed (including fix(objectql): a flow a composed stack declares only on its top level, beside package bodies, registers with the automation engine at boot #28091's connector and flow pins); claim filter dropped → the four body-repeat and CONTROL pins fail; permission owner judge, view expansion and view name judge each removed → exactly its own pin fails.
  • @objectstack/objectql full suite: 495 files, 9647 passed. pnpm --filter @objectstack/objectql typecheck: exit 0.
  • @objectstack/service-automation residual-top-level-flow-boot.test.ts: 3 passed. @objectstack/cli serve-config-boot-residual.integration.test.ts (real os serve / os build / os validate boots): 14 passed.
  • dispatch-gates --commands derived 61 families on the committed head; all run with exit codes recorded. Two exit 3 on the first pass (check-plugin-teardown-shape --self-test in a shallow clone; check:dual-build-cjs-loads, 10 packages without a dist), both exit 0 after fetching the pinned commit and turbo run build --filter='./packages/**'. --ran: 61 derived, 61 run, 0 NOT-MEASURED. pnpm lint: exit 0. The head is 17 commits behind origin/main; none of them touches the files this diff changes.

Acceptance notes

  • Picklists stay metadata-only. Registering a residual picklist would change which residual objects the data door serves (the objects rule lists and does not serve an object whose field names a picklist only the residual declares). Left as is; a separate card if wanted.
  • No new refusal was written. Every type registers; the refusals a residual item can now meet at boot are the ones a package body's item of that type already meets.

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 by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4cwMU5DUWJ6JmbuRpT7

…tack in the engine

The manifest service registered a multi-package stack's residual
(top-level items no package body declares, ADR-0130 D4) objects,
connectors and flows only. Every other residual item reached the
metadata service alone, so readers that ask the engine registry, the
metadata save door's managed-content check among them, answered as if
it were not declared.

The residual is now read once per registration, and every residual
item except objects (own path) and picklists (metadata-only by the
objects rule) registers under the stack's manifest id and version
through the judges a body's item passes: view containers are filed
under their object and expanded after the divergent-name judge, a
permission set's packageId is judged by the owner-binding judge, and
residual actions join the late-artifact bound-target check.

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

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

37 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 d70e94ae1db083f2ef136e0a65597b2de09fd35f.

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

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 d70e94ae1db083f2ef136e0a65597b2de09fd35f → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json d70e94ae1db083f2ef136e0a65597b2de09fd35f

⚠️ 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 d70e94ae1db083f2ef136e0a65597b2de09fd35f → 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 69182e3 · 2026-10-30T14:24Z

@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: 69182e333b046d67525ea9494ebb8d55ebf3c85d
Local-runs: none
Scope: packages/objectql/src/plugin.ts, packages/objectql/src/manifest-service-residual-objects.test.ts, packages/objectql/src/manifest-service-residual-security-seal.test.ts + packages/objectql/src/engine.ts (the body seam registerMetadataCollections, resolveMetadataItemName, permissionSetOwnerRegistrationRefusal, stampPackage), packages/objectql/src/registry.ts (registerItem, collectManifestBoundActions), packages/metadata/src/plugin.ts (unclaimedTopLevel, artifactItemSlot, ARTIFACT_FIELD_TO_TYPE, registerUnclaimedTopLevel), packages/metadata-protocol/src/protocol.ts (isSealedManagedItem, isArtifactBacked), packages/spec/src/kernel/metadata-plugin.zod.ts (the security and picklist rows), packages/spec/src/ui/view.zod.ts, packages/spec/src/ui/assembled-views.zod.ts, packages/spec/src/ui/view-container-object.ts, packages/runtime/src/app-plugin.ts (the config door's residual half), packages/plugins/plugin-security/src/packaged-permission-set-lock.ts, .changeset/28093-residual-top-level-items-register.md, docs/adr/0130-release-artifact-as-co-ownership-boundary.md D4.

Inputs: card #28093 (body; triage 6523369904; claim 6523676782; os-dev report 6525127678), PR #28153 (body, file list, net diff against main at the head), the head's check-runs, and the maintainer-only mechanism report (read; nothing from it is quoted here). Read-only: the tree was judged, not run.

Check-runs at 2026-10-30T14:42Z: every gate-carrying context that has concluded is success (Lint & Repo Gates, TypeScript Type Check, Build Core, Governed Surface Queue Guard, Test UI, Live E2E UI, Test Core shards 1-4 and 6-8); Test Core (5/8) is still in_progress, so the aggregate Test Core context has not reported. The verdict below is on the diff; the landing still waits on that shard.

① Derived judgments

Accept-set and public-surface changes the diff implies, each judged:

  1. Residual items of every registry type now reach the engine registry — right. unclaimedTopLevel lists 28 types (ARTIFACT_FIELD_TO_TYPE); the head registers all of them except object (own path, unchanged), picklist (deliberately metadata-only, see ③) and object_extension (never listed). Each is registered after every body, under the residual rule's ownerId/ownerVersion (the stack's manifest.id/version, with the id/version top-level fallback the rule itself uses), stamped on a shallow copy, through registry.registerItem(type, …, 'name', ownerId) — the verb the body seam uses. The artifact's own entries are left as they arrived (pinned).
  2. The managed-content seal now covers a residual security rule — right, and it is the card's security half. isSealedManagedItem is isArtifactBacked && !registryAllowsOverlay, and isArtifactBacked is a registry lookup, so a residual item of a non-overlayable type is sealed exactly when it is in the registry. On main a residual permission or position (the two security types with allowRuntimeCreate: true) fell to the runtime-create tier at the save door; on the head it is refused NOT_OVERRIDABLE. The other four security types fail closed on both trees (NOT_CREATABLE). Stated at rule level only.
  3. New boot-time refusals for residual items — right, and not a narrowing. A residual permission set whose body packageId names another package (ADR-0086 D3), an off-contract action (parsePackageAction), a divergent view container (viewContainerNameRefusal) and a queue or assignment rule on a composition without an ownership-queue service (ADR-0142 D1, assertOwnershipQueueServicePresent reads registry.listItems('queue'|'assignment_rule')) now refuse the boot where the same item in a package body already refused it. Is any composed stack that boots on main now refused? Yes, in exactly those four classes — and in each the item was not accepted on main either: it was dropped from the registry in silence, and a queue so dropped was inert. The divergent container was already refused on main by the config boot's metadata door (registerUnclaimedTopLevel throws the same refusal). The published accept set (packages/spec) is untouched; what changed is that the runtime now enforces what the contract already declared (Prime Directive chore: version packages #10, declared = enforced), which is the triage ruling's second arm ("refuse it loudly at boot"). So yes (widening) covers it and no (narrowing) arm is owed.
  4. The late-artifact bound-action target check now judges the residual's actions with the bodies' — right. collectManifestBoundActions({ actions }, ownerId) tolerates an empty list and an undefined owner.
  5. The arrived set for collision reporting falls back to the residual items' owner when the residual has no objects — right.
  6. No public export, route, schema or spec surface changes. packages/spec is untouched; the PR body's "nothing an author can write changes" holds in the tree.

② Semver level

  • Changeset .changeset/28093-residual-top-level-items-register.md: @objectstack/objectql patch; body carries Clause-②: yes (widening) and Metadata-protocol: none, both line-initial, matching the PR body. The only published package the diff moves is @objectstack/objectql.
  • Clause-②: yes (widening) against scripts/pm/clause2-line.mjs: yes with the (widening) arm is the legal spelling of "the accept set widens"; the arm is the value said out loud. Right — declared residual items that were refused no reader of the registry now register (①.1). The new refusals (①.3) are enforcement of the existing contract, not a narrowing of it, so yes (narrowing) is not owed.
  • Metadata-protocol: none — right: no key, value, type or default an author writes changes.
  • Level per AGENTS.md Post-Task step 3: the level follows the kind alone; none takes patch. A widening with Metadata-protocol: none ships patch. Right. (The older "both take at least minor" sentence in clause2-line.mjs's arm docblock is superseded by ruling A in the same file's header, which AGENTS.md step 3 states.)
  • Title carries no !; no BREAKING banner is owed; no ADR-0087 disposition is owed.

③ Boundary flags

  • open_questions: [] in the os-dev report — none to answer.
  • Dev flag (acceptance note + out_of_scope_findings, "carrier: none"): picklists stay metadata-only. Answered in part: the reason holds (the residual objects rule already judges an object whose field names a residual-only picklist as "listed and not served", pinned on main; registering residual picklists in this PR would move that ruled line), and the seal has no stake in it (picklist is allowEnvironmentOverlay: false, allowRuntimeCreate: false, so a save over one is refused on both trees). Not answered: a residual picklist that no object references is still the one listed type that is neither registered in the engine nor refused, and its absence is loud only through an object that names it — "carrier: none" leaves the triage's "no type is dropped silently" half-answered for one type. Escalated as should-fix Implement ObjectStack protocol specification with Zod schemas and TypeScript interfaces #3 below: file the follow-up card; the carrier is the card, not "none".
  • The PR body says the head is 17 commits behind origin/main and none touches the changed files; not re-measured here (read-only), the merge is the queue's.
  • Governed surfaces: none in the file list (Governed Surface Queue Guard green). Hot file packages/objectql/src/engine.ts untouched, as the claim required; the price is should-fix ✨ Set up Copilot instructions #2.
  • Ablation and run figures in the PR body (41/45 red on main, 60/60 green, 9647 passed) are the dev's; this record relies on the check-runs and the read, not on them.

④ Design judgment

packages/objectql/src/plugin.ts — holds, with two should-fixes. Against the body seam it cites (registerMetadataCollections): the same registerItem verb, the same owner stamp (applyProtection on a copy; the seam's stampPackage mutates in place, so the copy is the right call), the same view container derivation and expansion (deriveViewContainerObject, expandViewContainer), the same divergent-name judge, the same permission owner judge, and the seam's post-registration judges (parsePackageAction, the security-catalog name conflict, the page refusals) all run inside registerItem with ownerId as packageId, so they apply to a residual item unchanged. Break attempts:

  • Double registration of body-declared items: unclaimedTopLevel filters on the slot every body claims (a container on its object and each view it expands into; every other item on its name), so a top-level item a body repeats is never in the residual; the body-repeat pin holds 11 types at the body's owner, once. Holds.
  • Picklists and objects keeping their own path: RESIDUAL_TYPES_WITH_OWN_PATH excludes them from unclaimedTopLevelItems; unclaimedTopLevelObjects is unchanged in behaviour (now fed the one shared answer — the fix(objectql): a flow a composed stack declares only on its top level, beside package bodies, registers with the automation engine at boot #28091 should-fix closed). Holds.
  • Ownership under the stack's manifest id and version: owner read from the one rule's answer, version stamped before registerItem; pinned per type and all-at-once. Holds.
  • Ordering against the automation and security plugins' start(): the manifest service registers in the registering plugin's init() (declared dependency on the engine), before any start(); service-automation's boot pull and plugin-security's classifyPackagedPermissionSet (request-time, registry first) both read after. Later manifest.register calls (install, rehydrate) take the late-artifact path the diff extends. Holds.
  • A stack with no residual items: unclaimedTopLevelItems answers undefined, the call is skipped, the "registered nothing" branch keeps its three-way condition; CONTROL pin holds. A stack without packages[] has no residual by the rule. Holds.
  • AppPlugin as a second registrar: for a packages[] bundle the config door registers the residual into the metadata service only and leaves the engine to the manifest service, so no second engine registration. Holds.
  • Should-fix Add metamodel interfaces for ObjectQL/ObjectUI contract #1 — the residual view branch is wider than the body seam's for a non-container entry. The residual rule lists a views: entry that is not an aggregated container by its name, or by deriveViewContainerObject when it has none. The head then registers it through the generic registerItem('view', stamp(item), 'name', …). The body seam, for the same entry, (a) mints name: itemName when the item has none, and (b) refuses a non-container-shaped entry (isViewContainerShaped, 422 INVALID_METADATA, the views: 数组的三个门形状判断不一致:栈级 schema 只收容器、授权 lint 按记录门映射、运行时注册循环全收 —— declared ≠ enforced 的「运行时更宽」向 #5320 tighten). On the head: a residual ViewItem or flattened overlay registers where a body's is refused; and a residual container-shaped entry with object but no name and no list/form registers under the key the string undefined, not under its object — a slot the residual rule claimed under one key and the engine filed under another. Reachable through strict: false and programmatic stacks only (the class the PR body already names), not a security type, so should-fix: give the non-container branch the body seam's two lines (mint the name from the slot, refuse the non-container shape with the seam's envelope). The docblock's "refuse alike" then becomes true for views.
  • Should-fix ✨ Set up Copilot instructions #2 — the permission refusal envelope is a copy. permissionSetOwnerRegistrationRefusal in engine.ts is module-private, so the head rebuilds its VALIDATION_ERROR/400/issues[] envelope inline. The judge is shared (one comparison, as ADR-0086 D3 requires); the envelope is now two copies that can drift. Export the helper (one word in the hot file) or move it beside viewContainerNameRefusal, and call it from both doors.

packages/objectql/src/manifest-service-residual-objects.test.ts — holds. The new describe pins, per type and all at once, registration under the stack's id and version; the artifact's own entries unstamped; container registration under its object with expansion; the divergent-name refusal in the body seam's words (asserted by calling the one judge, not a copied string); queues and assignment rules registering with the service and refusing without it (the ADR-0142 prescription asserted by regex); the foreign-packageId permission refusal; body-repeat (11 types, owner and single registration); CONTROL. Fixtures: body items are schema-valid where the body is strictly parsed; residual generic items are {name,label} only and register because registerItem logs and tolerates a schema miss — so those pins hold keying and ownership, not schema validity, which is the body seam's behaviour too. Nothing pins the non-container view case of should-fix #1.

packages/objectql/src/manifest-service-residual-security-seal.test.ts — holds, and it is the security pin asked for. Does it exercise the save door's sealed-managed check for every security type with allowEnvironmentOverlay: false? Yes: the population is read from DEFAULT_METADATA_TYPE_REGISTRY (six types today, each with a fixture, and a type that joins without one fails the first case by name); each boots through the manifest service, is read back by getArtifactItem under the stack's id, and a saveMetaItem over it on a real protocol over a memory driver is asserted 403 NOT_OVERRIDABLE with zero stored rows — the same door isSealedManagedItem guards, through isArtifactBacked. The by-name read after the refusal serves the declared set. The CONTROL (an undeclared position saves) shows the refusal is the seal and not a blanket NOT_CREATABLE; for the four allowRuntimeCreate: false types the pin distinguishes NOT_OVERRIDABLE from the NOT_CREATABLE they gave on main, so it is red there for every member. One limit, not a finding: it holds the engine half alone (no MetadataPlugin composed), which is the half the fix moves.

Should-fix #3 (card-level, from ③): file the follow-up card for residual picklists so the one type this PR neither registers nor refuses has a carrier.

No must-fix. No fail-open finding at rule level beyond the one the card names and the head closes.

Implemented-by: claude/project-thread-7q2yv3
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

…container-shape lines

A residual `views:` entry that is not a container is now handled as the body
seam handles one: with no `name` it is filed under the slot the residual rule
gave it, and an entry that is not container-shaped (`isViewContainerShaped`)
refuses the boot 422 INVALID_METADATA in the seam's words.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4cwMU5DUWJ6JmbuRpT7
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head 91a1d30 · 2026-10-30T15:03Z

@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: 91a1d307f2d6a9a12d1c482682d139efe7a6f3b7
Local-runs: none
Scope: packages/objectql/src/plugin.ts, packages/objectql/src/manifest-service-residual-objects.test.ts, packages/objectql/src/manifest-service-residual-security-seal.test.ts + packages/objectql/src/engine.ts (the body seam registerMetadataCollections, resolveMetadataItemName), packages/objectql/src/registry.ts (registerItem), packages/metadata/src/plugin.ts (unclaimedTopLevel, artifactItemSlot), packages/spec/src/ui/assembled-views.zod.ts (isViewContainerShaped), packages/spec/src/ui/view-container-object.ts, .changeset/28093-residual-top-level-items-register.md. The rest of the first record's scope was re-read only where this delta touches it.

This record supersedes 6525505738 (head 69182e33), a PASS with three should-fixes. Delta reviewed: 69182e333b046d67525ea9494ebb8d55ebf3c85d..91a1d307f2d6a9a12d1c482682d139efe7a6f3b7 — the merge 0ca4c535 and the fix 91a1d307 — with the merge's effect on plugin.ts judged through the net diff against main, not the merge commit's own diff. Inputs as before (card #28093 with every comment; PR body, file list, net diff; the head's check-runs; the maintainer-only report, read and not quoted). Read-only.

Check-runs at 2026-10-30T15:08Z: concluded and success: Governed Surface Queue Guard, Test UI, Check Changeset, Check PR Size, PR-body guards, Dogfood Regression Gate, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests. Still in_progress: Build Core, Test Core (all 8 shards), Type Check (workspace, source gates, consumer gates, debt ledger), Live E2E UI, and the remaining Lint · legs — so Lint & Repo Gates, TypeScript Type Check, Build Core, Test Core and Live E2E UI have not reported on this head. The verdict below is on the diff; the landing waits on those.

① Derived judgments

The merge, judged by the tree:

  1. 0ca4c535 is a plain merge. Its tree is byte-identical to the automatic merge of its two parents (69182e33 and 48c77807, the origin/main it took in); nothing was resolved by hand. Between the first record's base (27e0bd58) and the merge-base (48c77807) no commit on main touched packages/objectql/src/plugin.ts, engine.ts, registry.ts or packages/metadata/src/plugin.ts; 6a648ac2 (the engine's empty-search-fields refusal, engine.ts only) was already in 27e0bd58 and so already under the first record. The net plugin.ts delta against main after the merge differs from the one before it by exactly the fix commit's lines (interdiff read), and the net file list is still the same four files. Nothing the first record judged in ① moves; the first record's ①.1–①.6 stand.

The fix, judged by the tree (registerUnclaimedTopLevelItems, the generic branch after the container branch and the permission judge):
2. A residual non-container view entry with no name is filed under its slot — right. name = item.name || deriveViewContainerObject(item) for type === 'view', toRegister = { ...item, name } when it differs: the key the residual rule claimed (artifactItemSlot's object fallback) and the key the engine files under now agree, and it is the body seam's own derivation order (resolveMetadataItemName: own name first, then the object). The copy keeps the artifact's own entry unstamped and unnamed (pinned). For a non-view item name is item.name, which the residual rule guarantees, so the generic branch is unchanged for every other type.
3. A residual views: entry that is not container-shaped is refused — right. isViewContainerShaped(toRegister) — the same classifier, judged on the same name-minted item as the seam judges it — and the seam's INVALID_METADATA / 422 / httpStatus envelope with the seam's words, with manifest as the source label (the word the residual rule itself uses for the config door). A residual ViewItem or flattened overlay now refuses the boot where a body's did; the #5320 "runtime wider than schema" hole the first record found is closed for the residual. Reachable only through strict: false and programmatic stacks, as before; a new boot refusal in the same class as the four the first record judged — enforcement of what the body seam already declared, not a narrowing.
4. Accept-set direction unchanged. Still yes (widening) with no (narrowing) arm owed: the fix removes a leniency the head itself had introduced over main (on main the residual was not registered at all), so against main nothing that booted is newly refused by it except that strict-refused class.

② Semver level

  • Changeset: still @objectstack/objectql patch; Clause-②: yes (widening) and Metadata-protocol: none line-initial in both the changeset and the PR body (head 91a1d307 is the PR's head). The body gained the one clause naming the 422 refusal of a non-container views: entry — right, that is what the diff now does.
  • Level per AGENTS.md Post-Task step 3 unchanged: the kind is none, so patch. Right. packages/spec untouched; no public surface moves; no BREAKING banner or ADR-0087 disposition owed.

③ Boundary flags

④ Design judgment

packages/objectql/src/plugin.ts — holds. The delta's one method now runs, for a view: the container branch (name judge, file under object, expand) or, for everything else, name-from-slot then the container-shape judge then registerItem; the body seam runs name judge, name-from-slot, shape judge, registerItem, then expansion — the same judges on the same inputs, in an order that only differs in where the container branch returns. Break attempts on the fix: a residual entry with name: '' and no object has no slot, so the residual rule never lists it (the seam's warn and skip is therefore unreachable here, not missing); a non-view item with no name is likewise never listed; a view with a name that is also container-shaped but not an aggregated container ({ name, object }) is filed under its own name as the seam files it; the three first-record should-fix shapes now each take the seam's branch. The permission branch, stamping, ownership and ordering judgments of the first record are unchanged by the delta (interdiff). The merge brings no change to this file.

packages/objectql/src/manifest-service-residual-objects.test.ts — holds. The three new pins assert the two halves of the fix: the nameless container-shaped entry registers under acme_note with the stack's owner and version and the entry itself stays unnamed; the flattened overlay and the ViewItem each refuse with { code: 'INVALID_METADATA', status: 422, httpStatus: 422 } and the seam's opening sentence under the manifest label and the stack's id. The ViewItem case carries a name, the overlay does not, so both name paths of the shape refusal are pinned. Nothing in the merge touches this file beyond the PR's own hunks.

packages/objectql/src/manifest-service-residual-security-seal.test.ts — unchanged in the delta; the first record's judgment stands (the save door's sealed-managed check exercised for every security type with allowEnvironmentOverlay: false, population read from the type registry).

No must-fix. One should-fix, carried on #28160 (extend its scope to the second copied text). No fail-open finding at rule level beyond the one the card names and the head closes.

Implemented-by: claude/project-thread-7q2yv3
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

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

维护者速读

  • 改了什么:合成栈(带 packages[])里只在顶层声明、没有包体认领的“残留”项,manifest 服务以前只把对象、连接器、流程注册进 ObjectQL 注册表;现在其余类型都按包体同类项的同一路径注册,归属栈自己的 id 与版本。没有 name 的残留视图按对象归档,形状不是视图容器的残留 views: 项按 body seam 的同一措辞拒绝(422)。
  • 为什么改:实测 main 上 28 种残留类型有 26 种从没进注册表,只问注册表的读者把它们当成未声明。安全层面(规则级):两种残留的顶层安全规则没有像包体里的同一规则那样被封印为托管内容,现在已注册并封印。
  • 风险与回滚:注册的东西变多(Clause-②: yes (widening));包体已有的判官同样作用于残留项,残留项若本身不合规会像包体一样在开机时被拒绝。picklist 仍只进元数据服务(另立 objectql: a composed stack's residual top-level picklist that no object names is neither registered nor refused #28159)。回滚即 revert 本 PR。
  • 先看:packages/objectql/src/plugin.ts(unclaimedTopLevelItems、registerUnclaimedTopLevelItems)。
  • 两分钟验证:pnpm --filter @objectstack/objectql exec vitest run src/manifest-service-residual-objects.test.ts src/manifest-service-residual-security-seal.test.ts,全绿。
  • 需要你做的:无(非受管;契约复审 PASS;席位按本任落地授权落地)。

席位意见:ACCEPT。

席位核对

Head-sha: 91a1d307f2d6a9a12d1c482682d139efe7a6f3b7

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 15:30
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit f9a1363 Oct 30, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-7q2yv3 branch October 30, 2026 16:20
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

objectql: a composed stack's residual top-level permissions (and other registry-read types) are never registered, like flows were before #28048

1 participant