Skip to content

decision(objectql): a navigationContributions[].group that names no group in the target app is silently RELOCATED to the top level — refuse, warn, or leave to the consumer? #14553

Description

@hotlong

Part of #14122 (measured by #14454 item 2, pinned in PR #14549).

What was measured

SchemaRegistry.applyNavContributions (packages/objectql/src/registry.ts, the else branch under if (c.group)): when a package contributes navigation items into a group id that the target app does not declare, the items are appended at the app's top level. Nothing throws, nothing carries an ADR-0112 code/status, and there is no console.warn / console.error. The only trace is one this.log(...) line, gated at logLevel info/debug. At OS_REGISTRY_LOG=warn — the level the objectql test config selects (#13517) — the relocation is completely silent while still happening.

The authoring door cannot catch it either: navigationContributions appears in no validateCrossReferences rule, and the group id names a node in an app the contributing package does not own.

Pins (record today's behaviour, no runtime change): packages/objectql/src/registry-nav-contribution-group-semantics.test.tsPROPOSITION 2 (DOES NOT HOLD AS POSED), PROPOSITION 2 (visibility), PROPOSITION 2 (authoring door). Ablation on #14549 confirmed the pins go red when the branch drops items instead of relocating.

Why it matters

#14122 §4 feared a vanishing contribution. A relocated one is worse for the named consumer (hotcrm's 17-node navigation conversion, hotcrm#1449): the entry is present, a smoke test passes, and the information architecture has silently changed. A typo'd group id is exactly the error an AI author emits.

Options

Option Cost What it buys
A Refuse at install: reject the contribution with an ADR-0112 envelope when the target app declares no such group Introduces an ordering constraint — registerAppNavContribution deliberately does not require the target app to exist yet, and the merge is a read-time fold precisely so registration order does not matter; also breaks any package contributing into an optional group The loudest signal, before any user sees a moved menu
B Keep relocating, upgrade the trace to console.warn (or a real diagnostic) so it survives OS_REGISTRY_LOG=warn One-line change to an existing log call; no new refusal, no ordering constraint The failure is noticed at the point it happens; still not a gate
C No platform change; hotcrm's conversion asserts its own merged nav tree equals the pre-split tree A test in the consumer repo Catches the regression for the one measured consumer only

Four-axis reading (PM seat, for the maintainer to confirm or overrule)

  1. 平台长期一致性 — A is the contract-first shape in the abstract, but it trades away the order-independence the read-time fold was designed for; B keeps the design and fixes the observability gap.
  2. 实测业务拉力 — the only measured consumer (hotcrm 17-node conversion) needs the mis-aim to be noticed; B delivers that at the point of failure, C delivers it in the consumer's own suite.
  3. AI agent 抗错性 — a typo'd group id today yields a silently different IA; B makes it visible without inventing a refusal an author must learn to suppress.
  4. 创业阶段范围纪律 — B changes one log call, C adds one consumer test; A adds a new refusal surface and a registration-order rule.

Recommendation: B now, C alongside in hotcrm. If the maintainer wants the gate rather than the signal, A is the shape — and it should then also cover the app-does-not-exist case, which is equally unchecked today.

Not release-gating for the ADR-0130 chain: the behaviour is unchanged from before the epic and no packaged product depends on navigationContributions yet.

Activity

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