spec: dspack v0.4 — required-props rule type + component categories (PR-12)#11
Conversation
…PR-12) The v0.4 delta (M3 plan Phase 1; ADR-M3-1/ADR-M3-2 as confirmed 2026-07-03): - required-props rule type (4th typed rule): 'a component must carry named content DIRECTLY' — requiredText for the node's own text field, requiredProps for directly-present props (oneOf optional); component accepts sub-component ids (the one type that does); optional 'within' scope with an existence clause mirroring v0.3's requiredProps.on semantics. Driven by the measured projection gap: 78/78 gate failures across three model families (dspack-gen docs/findings.md + addendum). - component categories: contract-defined registry (no baked-in taxonomy), categories[] membership on componentEntry and subComponentDescriptor, forbiddenCategories on forbidden-composition. Categories are contract metadata, never surface vocabulary — S2 unchanged. - shadcn contract v0.4 (2.1.0): interactive/overlay registry; memberships on 5 components + 10 sub-components; rule.trigger-carries-label (required-props); rule.alertdialog-no-nested-overlays (forbiddenCategories — ADR-M3-3's second deletion-shaped rule, flagged in the PR for review). - spec/dspack-v0.4.md (delta over v0.3, normative semantics), spec/migration-v0.3-to-v0.4.md, README updates. - validate.mjs: v0.4 schema registration, category referential checks, required-props reference resolution (componentOrSub + within), back-compat strip for v0.4; 4 new negative fixtures. Schema is strictly additive: 48 added leaf paths, 0 removed (v0.3 → v0.4 structural diff in the PR body). validate + negative fixtures green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces the dspack v0.4 spec delta and schema, adding a contract-defined component categories registry (plus membership metadata) and a new typed governance rule, required-props, along with validation-script updates, updated documentation, an updated shadcn/ui example, and new negative fixtures.
Changes:
- Add v0.4 spec + migration guide documenting
required-propssemantics and category-based selection viaforbiddenCategories. - Add v0.4 JSON Schema with new leaf paths for categories and the new rule type.
- Update validation tooling + example/fixtures/docs to recognize and exercise v0.4.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/README.md | Updates spec index to point to v0.4 and the v0.3→v0.4 migration guide. |
| spec/migration-v0.3-to-v0.4.md | New migration guide documenting v0.4’s additive changes and worked examples. |
| spec/dspack-v0.4.md | New v0.4 delta spec defining categories + required-props and forbiddenCategories. |
| scripts/validate.mjs | Extends validation to v0.4 (schema mapping + governance/category consistency checks). |
| schema/README.md | Documents the new v0.4 schema as the current draft schema. |
| schema/dspack.v0.4.schema.json | New v0.4 JSON Schema including categories and required-props. |
| README.md | Updates project-level README to describe v0.4 as the current draft and adds milestone entry. |
| fixtures/negative/rule-unknown-category-ref.dspack.json | New negative fixture: rule references an unregistered category id. |
| fixtures/negative/rule-required-props-unknown-within.dspack.json | New negative fixture: required-props.within does not resolve. |
| fixtures/negative/rule-required-props-empty.dspack.json | New negative fixture: required-props missing both requiredText and requiredProps. |
| fixtures/negative/component-unknown-category-ref.dspack.json | New negative fixture: component membership references an unregistered category id. |
| examples/shadcn-ui.dspack.json | Updates shadcn/ui example to v0.4 and demonstrates categories + new rules. |
| examples/README.md | Updates examples docs to reflect v0.3 governance blocks and v0.4 additions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Back-compat guarantee: a v0.3+ document minus governance blocks stays valid. | ||
| if (GOVERNANCE_VERSIONS.has(doc?.dspack) && errors.length === 0) { | ||
| const stripped = { ...doc }; | ||
| delete stripped.intents; | ||
| delete stripped.rules; |
There was a problem hiding this comment.
Fixed in 929338b: the 0.4 strip now also removes the categories registry and the membership fields on components and sub-component descriptors, so the stripped document is the pre-governance core shape and the check exercises the real additive guarantee. Comment label updated to "version's additive blocks removed".
| const ok = | ||
| kind === "requiredSubComponents" || kind === "on" | ||
| ? resolvesToSub | ||
| : kind === "component" | ||
| ? resolvesToComponent |
There was a problem hiding this comment.
Declining this one, with reasoning: the require/forbid → component-or-sub resolution is pre-existing v0.3 validator behavior (byte-identical at the m2 tag), and v0.3 rule semantics are frozen (spec §5.5) — tightening it here would be a v0.3 behavior change smuggled into the v0.4 PR. It is also semantically coherent: §5.3 matching is by node id over the surface, sub-component nodes match, and the reference evaluator (dspack-gen rules.ts) implements exactly that, so forbidding e.g. a specific sub-component from ever appearing is a legitimate rule. The schema field description's "Component IDs" prose is loose against §5's referential-integrity text ("component and sub-component references … including composition.subComponents ids"); if that ambiguity should be settled, it belongs in a v0.3 errata/clarification note, not in this delta. Happy to file it as a follow-up issue if the maintainer wants it on the record.
| - `components.<id>.categories: string[]` — on a component entry. | ||
| - `composition.subComponents[].categories: string[]` — on a sub-component descriptor. |
There was a problem hiding this comment.
Fixed in 929338b: §3 now states membership arrays MUST be non-empty when present (omit rather than declare []), matching the schema's minItems: 1.
…ision - validate.mjs: the back-compat strip for 0.4 documents now also removes the categories registry and the categories membership fields on components and sub-component descriptors, so the check exercises the actual 'v0.2 shape + newer dspack version is valid' guarantee instead of passing v0.4 features through (Copilot comment 1). - spec v0.4 §3: membership arrays are non-empty when present (minItems: 1), stated in the spec, not only the schema (Copilot comment 3). - Copilot comment 2 (require/forbid accepting sub-component ids) is declined in-thread: pre-existing v0.3 validator behavior (byte-identical at m2), frozen semantics, and the reference evaluator matches ids over all nodes — a sub-component id in require/forbid is semantically coherent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
What
The dspack v0.4 delta — M3 plan Phase 1, implementing ADR-M3-1 (
required-propsas the fourth typed rule, THE v0.4 headline) and ADR-M3-2 (component categories, same revision, supporting cast) as confirmed at M3 planning (2026-07-03). Strictly additive: 48 added schema leaf paths, 0 removed; a valid v0.3 document with"dspack": "0.4"validates against the v0.4 schema.Why (the evidence)
78/78 emitter-gate failures across three local model families share one signature: a governance-clean surface whose trigger-button label sits where no v0.3 rule can require it to be and no protocol projection can lift it from (
docs/findings.md+ gpt-oss addendum, dspack-gen#21; m2-report, dspack-gen#22).required-propsmakes that a lintable, repairable S3 finding. Categories lift the enumerate-ids ceiling (v0.3 §9) that the Astryx contract (ADR-M3-5, 160+ components) would otherwise hit immediately.Hand-review surface (the milestone's core review)
spec/dspack-v0.4.md— normative semantics, especially §4.1 (required-props): the within-existence clause (everywithinnode must contain ≥1componentnode — mirrors v0.3'srequiredProps.onsemantics; closes the trigger-with-no-button hole) and the deliberate distinction fromrequired-composition.requiredProps(that field ison-scoped +oneOf-required; the new type checks the node's OWN fields, presence-only allowed,textreachable). Naming note for review: the field namerequiredPropsappears in both types with different entry shapes — spelled out in §4.1; happy to rename the new type's field (e.g.props) if you prefer.schema/dspack.v0.4.schema.json— generated as a programmatic transform of v0.3 (diff is exactly the additions):categoryEntry, top-levelcategories,categories[]on componentEntry/subComponentDescriptor, type enum +required-propsif/then block,forbiddenCategorieson forbidden-composition.examples/shadcn-ui.dspack.json(2.0.0 → 2.1.0) — surgical diff (63+/18−, no reformat churn):interactive/overlayregistry; memberships on 5 components + 10 sub-components (incl.dropdown-menu-triggeret al. — the agenda's exact non-enumerable case);rule.trigger-carries-label(required-props, anchoredbuttonwithinalert-dialog-trigger; the existing worked example already complies — trigger button carriestext: "Delete account"directly);rule.alertdialog-no-nested-overlays(forbiddenCategories, anchored onalert-dialog— forbidden-composition anchors accept component ids only, caught by the validator when I first anchored it on the sub-component).rule.alertdialog-no-nested-overlaysis ADR-M3-3's second deletion-shaped rule for the repair-shape deconfound (and the category form used in anger in the shipped contract). It has independent design grounds (no stacked modals — focus containment) but is not named in the PR-12 line of the phase plan. Attribution note: the pre-registered v0.4-effect check (PR-15) is signature-based and robust to this rule; organic firings are expected to be rare.Acceptance (in CI, validate.yml)
npm run validate— 5 schemas compile; shadcn example valid incl. new category/required-props consistency checks + the v0.4 back-compat stripnpm run validate -- --fixtures negative— 16/16 rejected (4 new v0.4 fixtures: empty required-props, unknownwithin, unregistered category in a rule, unregistered category on a component)Consumer consequence (expected, scheduled)
The contract sha changes: byte-match drift checks in dspack-gen / dspack-emit / ds-mcp will fail on their next runs until the PR-13/14 syncs land (PR-14b also adds v0.4 to ds-mcp's loader version allowlist). This is the planned Phase 1 sequence, not a surprise.
🤖 Generated with Claude Code