Goal: prevent generators from emitting shell-owned primitives (e.g., navigation) by enforcing the contract before code lands. The same logic can be reused on edge/on-device generation.
contract.shell.owns: array of primitives the shell owns globally (e.g.,["navigation"]).surface.mustNotEmit: per-surface override of primitives that the surface must not generate. If absent, fall back tocontract.shell.owns.- Descriptor primitives: generation (or extraction) should emit
primitives: [{ role, count, sources }]per surface.
- Load the contract.
- For each surface, compute
banList = surface.mustNotEmit || contract.shell?.owns || []. - Scan the generated output into a descriptor with
primitives. - If any
primitive.roleis inbanListwithcount > 0, fail fast withshell-owned-primitive-emitted.
- Script:
tools/check-generation-boundaries.mjs - Usage:
node tools/check-generation-boundaries.mjs \ --contract contracts/surfaces.web.contract.json \ --descriptor out/generated-descriptor.json
- Exit codes:
0pass1invalid input (missing files/fields)2violation detected (details printed)
- Generation time (local/CI): run the checker immediately after generation and before writing/committing output. Add it to generator pipelines or pre-commit hooks.
- CI/CD: existing
interfacectl validatealready enforces the same rule; keep both for fast feedback. - Runtime (edge/on-device): reuse the same ban list from the compiled manifest and reject adaptations that emit banned primitives.
[
{
"surfaceId": "interfacectl-web",
"primitives": [
{ "role": "navigation", "count": 1, "sources": ["app/(shell)/layout.tsx"] }
]
}
]- If both
mustNotEmitandshell.ownsare absent, the checker is a no-op for that surface. - Role naming: use the canonical role string
navigationfor nav bars; if you introduce aliases, normalize them before writing the descriptor.