Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.5.0 — 2026-08-11

Generation-quality release (P3a): the pipeline teaches the model what the emitter will demand, from knowledge the contract already carries.

- **Composition notes reach the system prompt.** Each component vocabulary line now carries the contract's own `composition.notes` (capped at two sentences / 360 chars). Measured on the Gateway corpus: every field-donation failure and tab/radio key mismatch contradicted prose the contract already states verbatim — generation just never saw it.
- **Join-participating sub-components require `id` in the generation schema** (`run/join-id-view.ts`, profile-derived, generation-only — S-gates and the contract untouched). Keyless and prefix-mismatched join items were the dominant residual join failure.
- Dev/CI stack aligned to dspack-emit 0.7 (lockfile): the p05 eval cell and the pipeline emitter-gate test recalibrated to 0.7 semantics (missing-required-prop surfaces now REFUSE at emission and ride the repair loop; the eval golden regenerated accordingly — refusals terminate as failed-gate, never script-exhaustion errors).

# Changelog

## 0.4.0
Expand Down
6 changes: 6 additions & 0 deletions eval/matrix.fake.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,12 @@
}
],
"p05-s3-clean-gate-fail": [
{
"fixture": "../fixtures/eval/text-placement-gate-fail.dsurface.json"
},
{
"fixture": "../fixtures/eval/text-placement-gate-fail.dsurface.json"
},
{
"fixture": "../fixtures/eval/text-placement-gate-fail.dsurface.json"
}
Expand Down
2 changes: 1 addition & 1 deletion fixtures/golden/context/shadcn.destructive-action.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"system": "You generate user interface surfaces for the \"shadcn/ui\" design system. You must respond\nwith a single JSON object conforming to the provided schema — a dspack surface document.\n\n## Component vocabulary\nYou may use only these components (with the listed props and allowed values):\n- button — An interactive element that triggers an action when activated. Built on a native button element with support for Radix UI Slot composition via asChild. Props: variant ∈ {default, destructive, outline, secondary, ghost, link}; size ∈ {default, sm, lg, icon}; disabled; asChild.\n- alert-dialog — A modal dialog that interrupts the user with important content and expects a response. Built on Radix UI AlertDialog. Renders with a required action and a cancel option. The user cannot dismiss it by clicking the overlay or pressing Escape — they must choose an explicit action. Props: open; onOpenChange. Sub-components (used as children): alert-dialog-trigger, alert-dialog-content, alert-dialog-header, alert-dialog-title, alert-dialog-description, alert-dialog-footer, alert-dialog-action, alert-dialog-cancel.\n- dialog — A modal window that appears over the page content. Built on Radix UI Dialog. Can be dismissed by clicking the overlay, pressing Escape, or activating a close button. Props: open; onOpenChange; modal. Sub-components (used as children): dialog-trigger, dialog-content, dialog-header, dialog-title, dialog-description, dialog-footer, dialog-close.\n- card — A container for grouping related content and actions. Provides visual separation through a bordered surface. Props: className. Sub-components (used as children): card-header, card-title, card-description, card-content, card-footer.\n- input — A single-line text input field built on the native <input> element. Supports all standard HTML input types. Props: type; placeholder; disabled.\n- badge — A small label for categorization, status indication, or metadata. Renders as an inline element. Props: variant ∈ {default, secondary, outline, destructive}.\n- dropdown-menu — A menu that appears on activation of a trigger element. Built on Radix UI DropdownMenu. Supports items, checkboxes, radio groups, sub-menus, separators, and keyboard navigation. Props: open; onOpenChange; modal. Sub-components (used as children): dropdown-menu-trigger, dropdown-menu-content, dropdown-menu-item, dropdown-menu-checkbox-item, dropdown-menu-radio-group, dropdown-menu-radio-item, dropdown-menu-label, dropdown-menu-separator, dropdown-menu-group, dropdown-menu-sub, dropdown-menu-sub-trigger, dropdown-menu-sub-content.\n- table — A set of primitives for presenting tabular data with a meaningful row-and-column relationship. These are thin, presentational wrappers over the native table elements — Table renders a <table> inside a horizontally scrollable container, and the sub-components render thead, tbody, tfoot, tr, th, td, and caption. There is no built-in sorting, filtering, pagination, or selection; those are composed on top. Sub-components (used as children): table-header, table-body, table-footer, table-row, table-head, table-cell, table-caption.\n\n## Governance rules in effect (intent: destructive-action)\nThese are hard requirements. Surfaces violating them will be rejected:\n1. [rule.destructive-requires-alertdialog / must] Use alert-dialog for this surface; dialog is forbidden. Why: Dialog can be dismissed by clicking the overlay or pressing Escape, so a user can bypass a destructive confirmation without making a conscious choice. AlertDialog forces an explicit confirm/cancel decision and is announced with greater urgency by screen readers.\n2. [rule.alertdialog-requires-cancel / must] Every alert-dialog must contain alert-dialog-cancel and alert-dialog-title. Why: A confirmation without an explicit cancel action and a title naming the consequence funnels the user toward the destructive action; the title is also required for aria-labelledby.\n3. [rule.button-no-interactive-descendants / must] Never place button or input inside a button. Why: Nested interactive elements create ambiguous click targets and are an accessibility violation: screen readers cannot determine intent and click handling varies across browsers.\n4. [rule.trigger-carries-label / must] Every alert-dialog-trigger must contain non-empty text (its own `text` field or a descendant's). Why: The trigger must present an accessible label: non-empty text somewhere under the trigger. Protocol projections lift the label from the trigger's subtree (preferring a label-bearing button; lifts are audited) — a trigger with no label text anywhere yields a control with no accessible name and an instance downstream emitters must refuse.\n5. [rule.alertdialog-no-nested-overlays / must] never place overlay-category components (alert-dialog, dialog, dropdown-menu) inside alert-dialog. Why: An alert dialog is a single focused interruption. Stacking another overlay (dialog, dropdown menu, another alert dialog) inside it breaks focus containment and dismiss semantics and buries the confirmation decision under a second layer.\n\n## Design intent\nIntent \"destructive-action\": The requested UI performs an irreversible or high-consequence operation: deleting records or accounts, revoking access, removing members.\nRelated pattern \"Destructive Action Confirmation\": Use AlertDialog, not Dialog, for destructive confirmations. The trigger should clearly indicate the destructive nature of the action. Inside the AlertDialog, provide a clear title stating what will happen, a description of the consequences, and two actions: a cancel option and a confirm option. The confirm button MUST use the destructive variant. Place the cancel action before the confirm action in the footer. The description should state specifically what will be affected (e.g., 'This will permanently delete 3 projects and all associated data').\n\nOutput only the JSON object. No commentary.",
"system": "You generate user interface surfaces for the \"shadcn/ui\" design system. You must respond\nwith a single JSON object conforming to the provided schema — a dspack surface document.\n\n## Component vocabulary\nYou may use only these components (with the listed props and allowed values):\n- button — An interactive element that triggers an action when activated. Built on a native button element with support for Radix UI Slot composition via asChild. Props: variant ∈ {default, destructive, outline, secondary, ghost, link}; size ∈ {default, sm, lg, icon}; disabled; asChild.\n- alert-dialog — A modal dialog that interrupts the user with important content and expects a response. Built on Radix UI AlertDialog. Renders with a required action and a cancel option. The user cannot dismiss it by clicking the overlay or pressing Escape — they must choose an explicit action. Props: open; onOpenChange. Sub-components (used as children): alert-dialog-trigger, alert-dialog-content, alert-dialog-header, alert-dialog-title, alert-dialog-description, alert-dialog-footer, alert-dialog-action, alert-dialog-cancel. Composition: AlertDialogContent must contain AlertDialogTitle and AlertDialogDescription for accessibility. AlertDialogAction and AlertDialogCancel must appear in AlertDialogFooter.\n- dialog — A modal window that appears over the page content. Built on Radix UI Dialog. Can be dismissed by clicking the overlay, pressing Escape, or activating a close button. Props: open; onOpenChange; modal. Sub-components (used as children): dialog-trigger, dialog-content, dialog-header, dialog-title, dialog-description, dialog-footer, dialog-close.\n- card — A container for grouping related content and actions. Provides visual separation through a bordered surface. Props: className. Sub-components (used as children): card-header, card-title, card-description, card-content, card-footer.\n- input — A single-line text input field built on the native <input> element. Supports all standard HTML input types. Props: type; placeholder; disabled.\n- badge — A small label for categorization, status indication, or metadata. Renders as an inline element. Props: variant ∈ {default, secondary, outline, destructive}.\n- dropdown-menu — A menu that appears on activation of a trigger element. Built on Radix UI DropdownMenu. Supports items, checkboxes, radio groups, sub-menus, separators, and keyboard navigation. Props: open; onOpenChange; modal. Sub-components (used as children): dropdown-menu-trigger, dropdown-menu-content, dropdown-menu-item, dropdown-menu-checkbox-item, dropdown-menu-radio-group, dropdown-menu-radio-item, dropdown-menu-label, dropdown-menu-separator, dropdown-menu-group, dropdown-menu-sub, dropdown-menu-sub-trigger, dropdown-menu-sub-content.\n- table — A set of primitives for presenting tabular data with a meaningful row-and-column relationship. These are thin, presentational wrappers over the native table elements — Table renders a <table> inside a horizontally scrollable container, and the sub-components render thead, tbody, tfoot, tr, th, td, and caption. There is no built-in sorting, filtering, pagination, or selection; those are composed on top. Sub-components (used as children): table-header, table-body, table-footer, table-row, table-head, table-cell, table-caption. Composition: TableRow must appear inside TableHeader, TableBody, or TableFooter — not directly under Table. Header cells use TableHead (th); data cells use TableCell (td).\n\n## Governance rules in effect (intent: destructive-action)\nThese are hard requirements. Surfaces violating them will be rejected:\n1. [rule.destructive-requires-alertdialog / must] Use alert-dialog for this surface; dialog is forbidden. Why: Dialog can be dismissed by clicking the overlay or pressing Escape, so a user can bypass a destructive confirmation without making a conscious choice. AlertDialog forces an explicit confirm/cancel decision and is announced with greater urgency by screen readers.\n2. [rule.alertdialog-requires-cancel / must] Every alert-dialog must contain alert-dialog-cancel and alert-dialog-title. Why: A confirmation without an explicit cancel action and a title naming the consequence funnels the user toward the destructive action; the title is also required for aria-labelledby.\n3. [rule.button-no-interactive-descendants / must] Never place button or input inside a button. Why: Nested interactive elements create ambiguous click targets and are an accessibility violation: screen readers cannot determine intent and click handling varies across browsers.\n4. [rule.trigger-carries-label / must] Every alert-dialog-trigger must contain non-empty text (its own `text` field or a descendant's). Why: The trigger must present an accessible label: non-empty text somewhere under the trigger. Protocol projections lift the label from the trigger's subtree (preferring a label-bearing button; lifts are audited) — a trigger with no label text anywhere yields a control with no accessible name and an instance downstream emitters must refuse.\n5. [rule.alertdialog-no-nested-overlays / must] never place overlay-category components (alert-dialog, dialog, dropdown-menu) inside alert-dialog. Why: An alert dialog is a single focused interruption. Stacking another overlay (dialog, dropdown menu, another alert dialog) inside it breaks focus containment and dismiss semantics and buries the confirmation decision under a second layer.\n\n## Design intent\nIntent \"destructive-action\": The requested UI performs an irreversible or high-consequence operation: deleting records or accounts, revoking access, removing members.\nRelated pattern \"Destructive Action Confirmation\": Use AlertDialog, not Dialog, for destructive confirmations. The trigger should clearly indicate the destructive nature of the action. Inside the AlertDialog, provide a clear title stating what will happen, a description of the consequences, and two actions: a cancel option and a confirm option. The confirm button MUST use the destructive variant. Place the cancel action before the confirm action in the footer. The description should state specifically what will be affected (e.g., 'This will permanently delete 3 projects and all associated data').\n\nOutput only the JSON object. No commentary.",
"schema": {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
Expand Down
10 changes: 5 additions & 5 deletions fixtures/golden/eval/results.fake.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"matrixSha256": "f2aed86eeed5acd8f862fa7c4eeaa29f1a61480189a04b52e81cef65337e30cf",
"matrixSha256": "4b042d8b0e04dca3fdb61246b72172e02178e6f5d3acbfe66f81c2e8a3c85e7e",
"contract": {
"name": "shadcn/ui",
"dspack": "0.4",
Expand Down Expand Up @@ -374,7 +374,7 @@
"run": 1,
"outcome": "failed-gate",
"exitCode": 3,
"attempts": 1,
"attempts": 3,
"firstAttemptSchemaValid": true,
"firstAttemptViolated": false,
"firstAttemptRuleIds": [],
Expand All @@ -385,7 +385,7 @@
"run": 2,
"outcome": "failed-gate",
"exitCode": 3,
"attempts": 1,
"attempts": 3,
"firstAttemptSchemaValid": true,
"firstAttemptViolated": false,
"firstAttemptRuleIds": [],
Expand Down Expand Up @@ -415,7 +415,7 @@
"run": 1,
"outcome": "failed-gate",
"exitCode": 3,
"attempts": 1,
"attempts": 3,
"firstAttemptSchemaValid": true,
"firstAttemptViolated": false,
"firstAttemptRuleIds": [],
Expand All @@ -426,7 +426,7 @@
"run": 2,
"outcome": "failed-gate",
"exitCode": 3,
"attempts": 1,
"attempts": 3,
"firstAttemptSchemaValid": true,
"firstAttemptViolated": false,
"firstAttemptRuleIds": [],
Expand Down
12 changes: 6 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@aestheticfunction/dspack-gen",
"version": "0.4.0",
"version": "0.5.0",
"description": "Generation + governance pipeline for dspack contracts: prompt/context compiler, surface gates S1–S3, bounded repair, protocol emission, audit reports.",
"type": "module",
"license": "Apache-2.0",
Expand Down Expand Up @@ -66,7 +66,7 @@
"test:pack": "bash scripts/pack-test.sh"
},
"dependencies": {
"@aestheticfunction/dspack-emit": "^0.3.1 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0",
"@aestheticfunction/dspack-emit": "^0.7.0",
"@anthropic-ai/sdk": "^0.109.1",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
Expand Down
23 changes: 23 additions & 0 deletions src/core/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ export function compileContext(
};
}

/**
* First two sentences of a component's composition notes, word-truncated at
* 360 chars. Two sentences because the audited contracts state placement in
* the first and pairing/exclusion rules in the second (tabs' exact-match
* rule, radio's htmlFor pairing); the ceiling keeps a runaway sentence from
* bloating the vocabulary block.
*/
function capCompositionNotes(notes: unknown): string | null {
if (typeof notes !== "string" || !notes.trim()) return null;
const twoSentences = notes.match(/^(?:[^.!?]*[.!?]){1,2}/);
let out = (twoSentences ? twoSentences[0] : notes).trim();
if (out.length > 360) out = `${out.slice(0, 357).replace(/\s+\S*$/, "")}…`;
return out;
}

function fewshotPair(example: ExampleEntry): FewshotMessage[] {
return [
{ role: "user", content: example.prompt ?? example.description ?? example.id },
Expand Down Expand Up @@ -99,6 +114,14 @@ function renderSystemPrompt(
let line = `- ${id} — ${component.description}`;
if (props) line += ` Props: ${props}.`;
if (subs) line += ` Sub-components (used as children): ${subs}.`;
// P3a: the contract's own composition semantics ride the vocabulary line.
// A sub-component LIST without its nesting rules invites plausible-but-
// unprojectable compositions (measured on the Gateway corpus: every field
// donation failure and join key mismatch contradicted prose the contract
// already carries). Capped at two sentences / 360 chars — ambiguity
// removal, not prompt growth.
const notes = capCompositionNotes(component.composition?.notes);
if (notes) line += ` Composition: ${notes}`;
lines.push(line);
}

Expand Down
Loading
Loading