Skip to content

[finding] the nested-plugin seam does not expand an aggregated views container — a nested plugin's per-view items never reach the registry, so getViewsByObject() / GET /meta/view?object= answer with the container alone #7163

Description

@os-zhuang

Out-of-scope finding measured while closing #7049 / PR #7153. Recorded per Prime Directive #10, unassigned. That card was scoped to which collections the two registration seams enumerate; this is a difference in what the two seams do with a collection they both enumerate, so it was deliberately not folded in — #7049's brief said to file rather than widen, and the seam difference is real behaviour change owing its own verification.

The fact

packages/objectql/src/engine.ts reaches the registration seam from two entry points. Since #7049 both read one METADATA_ARRAY_KEYS, so views is enumerated by both. But only the manifest seam expands an aggregated view container:

if (key === 'views' && isAggregatedViewContainer(toRegister)) {
    for (const vi of expandViewContainer(itemName, toRegister)) {
        this._registry.registerItem('view', vi, 'name' as any, id);
    }
}

The nested-plugin loop in registerPlugin() has no equivalent. "Object has-many View" (ADR-0017) says a defineView document aggregates an object's views and must ALSO be expanded into independent ViewItems registered under <object>.<key> — that expansion is what getViewsByObject() and GET /meta/view?object= consume, and it is what carries the per-view package layer the view switcher and Studio read.

The measurement

On PR #7153's branch (the divergence is identical on origin/main @ 3e8e669), one aggregated container registered through each seam, reading registry.listItems('view') back:

container = { list: { data: { object: 'account' }, name: 'all_accounts' },
              form: { data: { object: 'account' } } }

via manifest      ->  [ 'account', 'account.all_accounts', 'account.form' ]
via nested plugin ->  [ 'account' ]

Same document, same package id, same collection key — two of the three registry entries simply are not created on the nested path.

Impact

A package shipping its views through manifest.plugins[] registers the container and nothing else. Readers that ask for the container by object name still find it (which is why this is silent); readers that go through the expanded per-view items — the view switcher, Studio's per-view package attribution, GET /meta/view?object= — see an object with no views. No refusal and no diagnostic, the same silent-under-registration shape as #7049 one layer in.

Frequency is not measured and none is claimed: how many published packages ship an aggregated views: container from a nested plugin rather than from the top-level manifest is unknown.

Class

Observation-class with a demonstrated missing value, filed for triage to grade. Two shapes are plausible and they differ in cost:

  1. Share the loop body, not just the enumeration — extract one registerMetadataCollections(source, ownerId, opts) both seams call, with the remaining seam differences (the per-key debug line, the warn-on-nameless-item) as named parameters rather than accidental drift. Closes this and makes the next body-level divergence unrepresentable, the way ObjectQL's two collection-registration copies diverge: jobs / emailTemplates / tools / skills register from a manifest but NOT from a nested plugin — a package shipping them via a nested plugin registers nothing, stamps no ADR-0010 provenance #7049 did for the enumeration.
  2. Add the expansion to the nested loop — small, and it is the one-instance shape ObjectQL's two collection-registration copies diverge: jobs / emailTemplates / tools / skills register from a manifest but NOT from a nested plugin — a package shipping them via a nested plugin registers nothing, stamps no ADR-0010 provenance #7049 explicitly argues against, since it leaves the two loop bodies undiffed exactly as the two lists were.

The other two measured body differences are recorded here so the next reader does not have to re-derive them: the manifest seam emits a per-key debug line and warns on an item with no derivable name; the nested seam does neither, so a nameless nested item is dropped in total silence.

Related

#7049 / PR #7153 (the enumeration half of the same two-seam divergence, and the source of this measurement), #6242 (the enumeration sweep), #5870 (the capabilities precedent for patching one seam at a time), ADR-0017 (Object has-many View), ADR-0010 (provenance envelope).

Activity

  1. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Triage: finding (domain:engine-core stays — packages/objectql/src/engine.ts).

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 10, 2026

    @claude
    Contributor

    Claim: PM loop wave 7 (seat #6019)
    Session: session_01MwoubC3jL271FYt9rGXwxb
    Branch: claude/issue-7163-nested-plugin-views-expansion
    Worktree: objectstack-7163
    Domain: domain:engine-core

    Race re-read: comments re-read, no competing session ID; assignees measured EMPTY via REST immediately before assigning. pm:queue → pm:dispatched in the same write.

    Surface freed by PR #7423's merge (2ef18070e) — engine.ts serial queue, this card is next; #7373 queues behind it.


    Generated by Claude Code

  3. claude commented on Aug 10, 2026

    @claude
    Contributor

    ACCEPT — PR #7452. Ready + queued.

    Verified by reading the body and diff:

    1. ✅ The direction was measured, not picked — four independent measurements all pointing the same way: ADR-0017 §3.2 scopes the dual-read (container + expanded ViewItems) to the loader, not one entry point; the sibling loader (metadata/src/plugin.ts artifact/HMR path) already expands — with the inline comment in ui/view.zod.ts saying the expansion was pushed into spec precisely "so the two loaders cannot drift"; ~51 shipping stacks (crm/todo/showcase/qa) ride the manifest seam's expansion; and zero in-tree nested plugins carry a views collection. One direction breaks every shipping consumer, the other breaks nobody — the nested seam catches up. The user-visible mechanism is nailed too: only expanded items carry viewKind, getViewsByObject() filters on it, so an unexpanded container is invisible to GET /meta/view, the view switcher and Studio attribution, with no diagnostic.
    2. ✅ The fix removes the class, not the instance: both seams now run one shared registerMetadataCollections, extending ObjectQL's two collection-registration copies diverge: jobs / emailTemplates / tools / skills register from a manifest but NOT from a nested plugin — a package shipping them via a nested plugin registers nothing, stamps no ADR-0010 provenance #7049's own reasoning from the list to the body — a divergence that cannot be written down cannot be re-introduced by the next hand patch. What legally varies (object read, package id, debug label) is parameterized.
    3. ✅ Pins: the parity pin (both seams, same container, same registered identities — the card's whole point), the control (non-aggregated views identical through both), expanded per-view identities, and the second body-sharing delta (nameless-item warn now fires on the nested seam too) is disclosed in the changeset rather than slipped in silently.
    4. ✅ Changeset minor, behaviour deltas named; ⛔ no packages/spec touch — no declaration needed.

    CI fully green. Auto-merge armed; issue closes on merge. #7373/#7413 queue behind this for engine.ts.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions