@usefragments/core 1.10.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
  2. package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
  3. package/dist/chunk-7ULGH74M.js +66 -0
  4. package/dist/chunk-7ULGH74M.js.map +1 -0
  5. package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
  6. package/dist/chunk-BMPYIUZE.js.map +1 -0
  7. package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
  8. package/dist/chunk-ML5S6QNU.js.map +1 -0
  9. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  10. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  11. package/dist/chunk-PWIJMOI4.js +202 -0
  12. package/dist/chunk-PWIJMOI4.js.map +1 -0
  13. package/dist/chunk-RYFULE43.js +578 -0
  14. package/dist/chunk-RYFULE43.js.map +1 -0
  15. package/dist/codes/index.d.ts +2 -2
  16. package/dist/codes/index.js +3 -2
  17. package/dist/compiled-types/index.d.ts +327 -2
  18. package/dist/compiled-types/index.js +1 -1
  19. package/dist/generate/index.d.ts +3 -2
  20. package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
  21. package/dist/governance-telemetry.d.ts +6 -0
  22. package/dist/governance-telemetry.js +1 -1
  23. package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
  24. package/dist/index.d.ts +3280 -1305
  25. package/dist/index.js +2414 -350
  26. package/dist/index.js.map +1 -1
  27. package/dist/manifest.d.ts +228 -0
  28. package/dist/manifest.js +24 -0
  29. package/dist/manifest.js.map +1 -0
  30. package/dist/preview/index.js +45 -1
  31. package/dist/preview/index.js.map +1 -1
  32. package/dist/preview-runtime.d.ts +1 -2
  33. package/dist/preview-runtime.js +150 -14
  34. package/dist/preview-runtime.js.map +1 -1
  35. package/dist/react-types.d.ts +1 -2
  36. package/dist/registry.d.ts +1412 -203
  37. package/dist/registry.js +30 -3
  38. package/dist/schemas/index.d.ts +1 -1
  39. package/dist/schemas/index.js +3 -2
  40. package/dist/storyAdapter.d.ts +1 -2
  41. package/dist/storyAdapter.js +11 -49
  42. package/dist/storyAdapter.js.map +1 -1
  43. package/dist/test-utils.d.ts +3 -2
  44. package/dist/topology/index.d.ts +1 -1
  45. package/dist/topology/index.js +1 -1
  46. package/package.json +8 -2
  47. package/src/__tests__/contract-parser.test.ts +318 -277
  48. package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
  49. package/src/__tests__/preview-runtime.test.tsx +30 -8
  50. package/src/__tests__/schema.test.ts +191 -14
  51. package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
  52. package/src/analysis-plan/coverage.ts +181 -0
  53. package/src/analysis-plan/digest.ts +141 -0
  54. package/src/analysis-plan/index.ts +34 -0
  55. package/src/analysis-plan/types.ts +207 -0
  56. package/src/approved-contract-tokens.test.ts +39 -0
  57. package/src/approved-contract-tokens.ts +18 -0
  58. package/src/codes/__tests__/codes.test.ts +13 -0
  59. package/src/codes/codes.ts +40 -0
  60. package/src/compiled-types/index.ts +640 -39
  61. package/src/compiled-types/parse.test.ts +145 -4
  62. package/src/component-contract.ts +95 -53
  63. package/src/composition.ts +7 -13
  64. package/src/constants.ts +3 -6
  65. package/src/contract/hash.test.ts +20 -0
  66. package/src/contract/hash.ts +66 -9
  67. package/src/contract/index.ts +24 -1
  68. package/src/contract/manifest.test.ts +94 -0
  69. package/src/contract/manifest.ts +68 -0
  70. package/src/contract/preimage.test.ts +219 -1
  71. package/src/contract/preimage.ts +326 -6
  72. package/src/contract/stamp.test.ts +3 -0
  73. package/src/contract/stamp.ts +1 -1
  74. package/src/contract-parser.ts +54 -30
  75. package/src/defineFragment.test.ts +476 -91
  76. package/src/defineFragment.ts +204 -114
  77. package/src/domain-ids.test.ts +35 -0
  78. package/src/domain-ids.ts +61 -0
  79. package/src/evaluation/evaluate.test.ts +522 -0
  80. package/src/evaluation/evaluate.ts +690 -0
  81. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
  82. package/src/evaluation/index.ts +58 -0
  83. package/src/evaluation/receipt.ts +753 -0
  84. package/src/evaluation/types.ts +406 -0
  85. package/src/facts/builders.ts +2 -0
  86. package/src/facts/compile.ts +29 -6
  87. package/src/facts/fact-index.ts +13 -3
  88. package/src/facts/fact-integrity-v1.test.ts +172 -0
  89. package/src/facts/facts.test.ts +15 -0
  90. package/src/facts/ids.ts +46 -3
  91. package/src/facts/index.ts +14 -1
  92. package/src/facts/integrity.ts +134 -0
  93. package/src/facts/types.ts +36 -0
  94. package/src/governance-integrity.test.ts +1 -0
  95. package/src/governance-integrity.ts +5 -3
  96. package/src/governance-telemetry.ts +8 -0
  97. package/src/governance.ts +70 -8
  98. package/src/index.ts +230 -37
  99. package/src/preview/validation.test.ts +62 -0
  100. package/src/preview/validation.ts +48 -2
  101. package/src/preview-runtime.tsx +227 -20
  102. package/src/registry-install-plan.ts +200 -109
  103. package/src/registry-shards.test.ts +263 -0
  104. package/src/registry.ts +237 -0
  105. package/src/repository-binding.test.ts +50 -0
  106. package/src/repository-binding.ts +96 -0
  107. package/src/rules/families.test.ts +36 -0
  108. package/src/rules/finding.ts +7 -2
  109. package/src/rules/index.ts +17 -1
  110. package/src/rules/rule-config.test.ts +66 -0
  111. package/src/rules/rule-config.ts +73 -0
  112. package/src/rules/rules.test.ts +26 -0
  113. package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
  114. package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
  115. package/src/schema.ts +293 -113
  116. package/src/schemas/index.ts +1 -1
  117. package/src/storyAdapter.test.ts +68 -12
  118. package/src/storyAdapter.ts +44 -75
  119. package/src/topology/resolve-area.ts +1 -1
  120. package/src/types.ts +258 -40
  121. package/dist/chunk-3LLRNCPX.js.map +0 -1
  122. package/dist/chunk-RANPUC6C.js +0 -72
  123. package/dist/chunk-RANPUC6C.js.map +0 -1
  124. package/dist/chunk-XN3LSDPY.js.map +0 -1
  125. package/dist/chunk-YF65VYRY.js.map +0 -1
  126. package/src/fragment-types.ts +0 -214
  127. package/src/react-create-element.test.ts +0 -22
  128. package/src/react-create-element.ts +0 -12
  129. package/src/storyFilters.test.ts +0 -350
  130. package/src/storyFilters.ts +0 -253
@@ -1540,6 +1540,32 @@ describe("runRules — Phase 5 vertical slice", () => {
1540
1540
  }
1541
1541
  });
1542
1542
 
1543
+ it("never runs a rule whose governance_rule_config fact is disabled", () => {
1544
+ // `govern.rules["styles/no-raw-spacing"] = { enabled: false }` compiles to a
1545
+ // rule-config fact. Enablement is honoured INSIDE runRules so the engine scan
1546
+ // and the kernel's facts-mode evaluation agree on which rules exist — a
1547
+ // post-filter in one evaluator is how a disabled rule blocked `--ci`.
1548
+ const ix = emptyIndexWithGovernance();
1549
+ ix.add(makeGovernanceRuleConfigFact({ ruleId: "styles/no-raw-spacing", enabled: false }));
1550
+ addButtonUsage(ix, {
1551
+ file: "apps/checkout/page.tsx",
1552
+ nodePath: "0/0",
1553
+ line: 42,
1554
+ column: 8,
1555
+ props: [{ prop: "variant", value: "secondary" }],
1556
+ inlineStyles: [
1557
+ { property: "padding", valueKind: "number", value: "13" },
1558
+ { property: "color", valueKind: "static", value: "#2563eb" },
1559
+ ],
1560
+ text: "Continue",
1561
+ });
1562
+
1563
+ const ruleIds = runRules(ix)
1564
+ .map((f) => f.ruleId)
1565
+ .sort();
1566
+ expect(ruleIds).toEqual(["components/forbidden-prop-value", "styles/no-raw-color"]);
1567
+ });
1568
+
1543
1569
  it("emits stable fingerprints across runs", () => {
1544
1570
  function buildIndex(): FactIndex {
1545
1571
  const ix = emptyIndexWithGovernance();
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
3
  import {
4
+ compileGlobalGovernanceFacts,
4
5
  FactIndex,
5
6
  makeContractTokenFact,
6
7
  makeStyleCssVarsMustBeDefinedFact,
@@ -9,6 +10,7 @@ import {
9
10
  import type { Fact } from "../index.js";
10
11
  import {
11
12
  contractTokenReferenceCensus,
13
+ emptyVocabularyConfigFile,
12
14
  ruleTokensCssVarsMustBeDefined,
13
15
  } from "./tokens-css-vars-must-be-defined.js";
14
16
 
@@ -121,10 +123,45 @@ describe("ruleTokensCssVarsMustBeDefined", () => {
121
123
  expect(ruleTokensCssVarsMustBeDefined(ix)).toHaveLength(0);
122
124
  });
123
125
 
124
- it("is INERT when the contract vocabulary is empty (no token definitions)", () => {
126
+ it("does not compile an activation fact from a rules-map enablement alone", () => {
127
+ const facts = compileGlobalGovernanceFacts({
128
+ rules: { "tokens/css-vars-must-be-defined": { enabled: true, severity: "error" } },
129
+ });
130
+ expect(facts.some((fact) => fact.kind === "style_css_vars_must_be_defined")).toBe(false);
131
+ });
132
+
133
+ it("emits one config-level inert finding when the contract vocabulary is empty", () => {
125
134
  const ix = index([POLICY, decl(".b", "var(--fui-color-brand)")]);
126
135
 
127
- expect(ruleTokensCssVarsMustBeDefined(ix)).toHaveLength(0);
136
+ const findings = ruleTokensCssVarsMustBeDefined(ix);
137
+ expect(findings).toHaveLength(1);
138
+ expect(findings[0]).toMatchObject({
139
+ ruleId: "tokens/css-vars-must-be-defined",
140
+ code: "FUI2018",
141
+ severity: "serious",
142
+ message: "token sources resolved to 0 tokens — rule inert",
143
+ location: { file: "your fragments config", line: 0, column: 0 },
144
+ attributes: { inert: true, tokenCount: 0, source: "config" },
145
+ });
146
+ });
147
+
148
+ it("points the inert finding at the resolved config path when the policy fact carries it", () => {
149
+ const policy = makeStyleCssVarsMustBeDefinedFact({
150
+ severity: "warn",
151
+ configPath: "fragments.config.json",
152
+ });
153
+ const findings = ruleTokensCssVarsMustBeDefined(index([policy]));
154
+ expect(findings).toHaveLength(1);
155
+ expect(findings[0].location.file).toBe("fragments.config.json");
156
+ });
157
+
158
+ it("keeps ordinary drift findings when the vocabulary is non-empty", () => {
159
+ const ix = index([POLICY, ...vocabulary(), decl(".b", "var(--fui-color-brand)")]);
160
+
161
+ const findings = ruleTokensCssVarsMustBeDefined(ix);
162
+ expect(findings).toHaveLength(1);
163
+ expect(findings[0].code).toBe("FUI2015");
164
+ expect(findings[0].attributes).toMatchObject({ tokenName: "--fui-color-brand" });
128
165
  });
129
166
 
130
167
  // #33 — CSS function names are case-insensitive; VAR()/Var() must be detected.
@@ -307,3 +344,15 @@ describe("contractTokenReferenceCensus", () => {
307
344
  expect(census).toEqual({ checkedSites: 0 });
308
345
  });
309
346
  });
347
+
348
+ describe("emptyVocabularyConfigFile", () => {
349
+ it("uses the resolved filename when a path is present", () => {
350
+ expect(emptyVocabularyConfigFile("fragments.config.json")).toBe("fragments.config.json");
351
+ expect(emptyVocabularyConfigFile("apps/docs/fragments.config.ts")).toBe("fragments.config.ts");
352
+ });
353
+
354
+ it("drops the filename when the path is missing", () => {
355
+ expect(emptyVocabularyConfigFile(undefined)).toBe("your fragments config");
356
+ expect(emptyVocabularyConfigFile(null)).toBe("your fragments config");
357
+ });
358
+ });
@@ -3,9 +3,20 @@ import type { FactIndex } from "../facts/index.js";
3
3
  import { makeFinding } from "./finding.js";
4
4
  import type { Finding } from "./types.js";
5
5
  import { contractPrefixFamilies } from "./utils.js";
6
+ import type { StyleCssVarsMustBeDefinedFact } from "../facts/index.js";
6
7
 
7
8
  export const RULE_ID = "tokens/css-vars-must-be-defined";
8
9
  export const RULE_VERSION = "1";
10
+ export const EMPTY_VOCABULARY_CODE = "FUI2018";
11
+ export const EMPTY_VOCABULARY_MESSAGE = "token sources resolved to 0 tokens — rule inert";
12
+ export const EMPTY_VOCABULARY_CONFIG_FALLBACK = "your fragments config";
13
+
14
+ /** Filename of the resolved fragments config, or a filename-free fallback. */
15
+ export function emptyVocabularyConfigFile(configPath?: string | null): string {
16
+ if (!configPath) return EMPTY_VOCABULARY_CONFIG_FALLBACK;
17
+ const base = configPath.replaceAll("\\", "/").replace(/\/+$/u, "").split("/").pop();
18
+ return base && base.length > 0 ? base : EMPTY_VOCABULARY_CONFIG_FALLBACK;
19
+ }
9
20
 
10
21
  // The leading `var(--name` of a reference. The `var(` keyword is matched
11
22
  // case-insensitively (CSS function names are case-insensitive per spec, so
@@ -101,7 +112,7 @@ export function ruleTokensCssVarsMustBeDefined(ix: FactIndex): Finding[] {
101
112
  const policy = ix.policy.cssVarsMustBeDefined();
102
113
  if (!policy) return [];
103
114
  const context = contractVocabularyContext(ix);
104
- if (!context) return [];
115
+ if (!context) return [emptyVocabularyFinding(ix, policy)];
105
116
  const { vocabulary } = context;
106
117
 
107
118
  const findings: Finding[] = [];
@@ -155,6 +166,28 @@ export function ruleTokensCssVarsMustBeDefined(ix: FactIndex): Finding[] {
155
166
  return findings;
156
167
  }
157
168
 
169
+ function emptyVocabularyFinding(ix: FactIndex, policy: StyleCssVarsMustBeDefinedFact): Finding {
170
+ return makeFinding({
171
+ ruleId: RULE_ID,
172
+ ruleVersion: RULE_VERSION,
173
+ severity: "error",
174
+ code: EMPTY_VOCABULARY_CODE,
175
+ message: EMPTY_VOCABULARY_MESSAGE,
176
+ location: {
177
+ file: policy.configPath ?? EMPTY_VOCABULARY_CONFIG_FALLBACK,
178
+ line: 0,
179
+ column: 0,
180
+ },
181
+ evidence: ix.evidence([policy.id]),
182
+ fingerprintIdentity: { reason: "empty-vocabulary" },
183
+ attributes: {
184
+ source: "config",
185
+ inert: true,
186
+ tokenCount: 0,
187
+ },
188
+ });
189
+ }
190
+
158
191
  function sharesContractPrefix(name: string, prefixes: ReadonlySet<string>): boolean {
159
192
  for (const prefix of prefixes) {
160
193
  if (name.startsWith(prefix)) return true;
package/src/schema.ts CHANGED
@@ -1,4 +1,17 @@
1
1
  import { z } from "zod";
2
+ import type {
3
+ FragmentAnnotationsV3,
4
+ FragmentDefinitionV3,
5
+ FragmentDefinitionV3Body,
6
+ FragmentDesignV3,
7
+ FragmentDontExampleV3,
8
+ FragmentGuidanceV3,
9
+ FragmentMatrixV3,
10
+ FragmentMetaV3,
11
+ FragmentPreviewV3,
12
+ FragmentStateV3,
13
+ PropAnnotation,
14
+ } from "./types.js";
2
15
  import { portableRepoPathError } from "./config-paths.js";
3
16
  import { componentGovernanceRecordSchema, governanceConfigSchema } from "./governance.js";
4
17
  import { compositionAuthoringEntrySchema } from "./rules/composition-pattern-schema.js";
@@ -13,10 +26,15 @@ const figmaStringMappingSchema = z.object({
13
26
  figmaProperty: z.string().min(1),
14
27
  });
15
28
 
29
+ const requiredUnknownSchema = z.custom<unknown>(
30
+ (value) => value !== undefined,
31
+ "Expected an explicitly present value"
32
+ );
33
+
16
34
  const figmaBooleanMappingSchema = z.object({
17
35
  __type: z.literal("figma-boolean"),
18
36
  figmaProperty: z.string().min(1),
19
- valueMapping: z.object({ true: z.unknown(), false: z.unknown() }).optional(),
37
+ valueMapping: z.object({ true: requiredUnknownSchema, false: requiredUnknownSchema }).optional(),
20
38
  });
21
39
 
22
40
  const figmaEnumMappingSchema = z.object({
@@ -143,7 +161,7 @@ export const fragmentContractSchema = z.object({
143
161
  a11yRules: z.array(z.string()).optional(),
144
162
  bans: z.array(fragmentBanSchema).optional(),
145
163
  scenarioTags: z.array(z.string()).optional(),
146
- performanceBudget: z.number().positive().optional(),
164
+ performanceBudget: z.number().positive().finite().optional(),
147
165
  compoundChildren: z
148
166
  .record(
149
167
  z.object({
@@ -163,10 +181,21 @@ export const fragmentContractSchema = z.object({
163
181
  /**
164
182
  * Schema for provenance tracking of generated fragments
165
183
  */
184
+ export const fragmentSourceValues = [
185
+ "storybook",
186
+ "manual",
187
+ "ai",
188
+ "scan",
189
+ "extracted",
190
+ "merged",
191
+ "migrated",
192
+ ] as const;
193
+
166
194
  export const fragmentGeneratedSchema = z.object({
167
- source: z.enum(["storybook", "manual", "ai"]),
195
+ source: z.enum(fragmentSourceValues),
168
196
  sourceFile: z.string().optional(),
169
197
  confidence: z.number().min(0).max(1).optional(),
198
+ verified: z.boolean().optional(),
170
199
  timestamp: z.string().datetime().optional(),
171
200
  });
172
201
 
@@ -299,102 +328,92 @@ const topologySchema = z.object({
299
328
  * Type definitions are in types.ts - schema just ensures basic structure.
300
329
  */
301
330
  export const fragmentsConfigSchema = z.object({
302
- app: z
303
- .object({
304
- path: repoRelativePathSchema.optional(),
305
- include: repoRelativePathsSchema.optional(),
306
- exclude: z.array(repoRelativePathSchema).optional(),
307
- })
308
- .optional(),
309
- designSystem: z
310
- .object({
311
- path: repoRelativePathSchema.optional(),
312
- packageName: z.string().min(1).optional(),
313
- components: repoRelativePathsSchema.optional(),
314
- })
315
- .optional(),
316
- include: repoRelativePathsSchema.optional(),
317
- exclude: z.array(repoRelativePathSchema).optional(),
318
- components: repoRelativePathsSchema.optional(),
319
- outFile: repoRelativePathSchema.optional(),
320
- framework: z.enum(["react", "vue", "svelte"]).optional(),
321
- figmaFile: z.string().url().optional(),
322
- figmaToken: z.string().optional(),
323
- screenshots: z.object({}).passthrough().optional(),
324
- service: z.object({}).passthrough().optional(),
325
- registry: z.object({}).passthrough().optional(),
326
- tokens: tokenConfigSchema.optional(),
327
- snippets: z
328
- .object({
329
- mode: z.enum(["warn", "error"]).optional(),
330
- scope: z.enum(["snippet", "snippet+render"]).optional(),
331
- requireFullSnippet: z.boolean().optional(),
332
- allowedExternalModules: z.array(z.string().min(1)).optional(),
333
- })
334
- .optional(),
335
- performance: z
336
- .union([
337
- z.enum(["strict", "standard", "relaxed"]),
338
- z.object({
339
- preset: z.enum(["strict", "standard", "relaxed"]).optional(),
340
- budgets: z
341
- .object({
342
- bundleSize: z.number().positive().optional(),
343
- })
331
+ app: z
332
+ .object({
333
+ path: repoRelativePathSchema.optional(),
334
+ include: repoRelativePathsSchema.optional(),
335
+ exclude: z.array(repoRelativePathSchema).optional(),
336
+ })
337
+ .optional(),
338
+ designSystem: z
339
+ .object({
340
+ path: repoRelativePathSchema.optional(),
341
+ packageName: z.string().min(1).optional(),
342
+ components: repoRelativePathsSchema.optional(),
343
+ })
344
+ .optional(),
345
+ include: repoRelativePathsSchema.optional(),
346
+ exclude: z.array(repoRelativePathSchema).optional(),
347
+ components: repoRelativePathsSchema.optional(),
348
+ outFile: repoRelativePathSchema.optional(),
349
+ framework: z.enum(["react", "vue", "svelte"]).optional(),
350
+ figmaFile: z.string().url().optional(),
351
+ figmaToken: z.string().optional(),
352
+ screenshots: z.object({}).passthrough().optional(),
353
+ service: z.object({}).passthrough().optional(),
354
+ registry: z.object({}).passthrough().optional(),
355
+ tokens: tokenConfigSchema.optional(),
356
+ snippets: z
357
+ .object({
358
+ mode: z.enum(["warn", "error"]).optional(),
359
+ scope: z.enum(["snippet", "snippet+render"]).optional(),
360
+ requireFullSnippet: z.boolean().optional(),
361
+ allowedExternalModules: z.array(z.string().min(1)).optional(),
362
+ })
363
+ .optional(),
364
+ performance: z
365
+ .union([
366
+ z.enum(["strict", "standard", "relaxed"]),
367
+ z.object({
368
+ preset: z.enum(["strict", "standard", "relaxed"]).optional(),
369
+ budgets: z
370
+ .object({
371
+ bundleSize: z.number().positive().optional(),
372
+ })
373
+ .optional(),
374
+ }),
375
+ ])
376
+ .optional(),
377
+ topology: topologySchema.optional(),
378
+ govern: governanceConfigSchema.optional(),
379
+ inspect: z
380
+ .object({
381
+ localCanonical: z
382
+ .record(
383
+ z.string().min(1),
384
+ z.object({
385
+ importPath: z.string().min(1),
386
+ resolves: z
387
+ .array(
388
+ z.object({
389
+ tag: z.string().min(1),
390
+ role: z.string().min(1).optional(),
391
+ inputType: z.string().min(1).optional(),
392
+ })
393
+ )
394
+ .min(1),
395
+ })
396
+ )
344
397
  .optional(),
345
- }),
346
- ])
347
- .optional(),
348
- storybook: z
349
- .object({
350
- exclude: z.array(z.string()).optional(),
351
- include: z.array(z.string()).optional(),
352
- excludeDeprecated: z.boolean().optional(),
353
- excludeTests: z.boolean().optional(),
354
- excludeSvgIcons: z.boolean().optional(),
355
- excludeSubComponents: z.boolean().optional(),
356
- })
357
- .optional(),
358
- topology: topologySchema.optional(),
359
- govern: governanceConfigSchema.optional(),
360
- inspect: z
361
- .object({
362
- localCanonical: z
363
- .record(
364
- z.string().min(1),
365
- z.object({
366
- importPath: z.string().min(1),
367
- resolves: z
368
- .array(
369
- z.object({
370
- tag: z.string().min(1),
371
- role: z.string().min(1).optional(),
372
- inputType: z.string().min(1).optional(),
373
- })
374
- )
375
- .min(1),
376
- })
377
- )
378
- .optional(),
379
- })
380
- .optional(),
381
- recognizedClassHelpers: z.array(z.string().min(1)).optional(),
382
- identity: z
383
- .object({
384
- decisions: z
385
- .array(
386
- z.object({
387
- component: z.string().min(1),
388
- kind: z.enum(["sanction", "reject", "dismiss"]),
389
- canonicalTarget: z.string().min(1).optional(),
390
- reason: z.string().optional(),
391
- decisionId: z.string().min(1).optional(),
392
- })
393
- )
394
- .optional(),
395
- })
396
- .optional(),
397
- });
398
+ })
399
+ .optional(),
400
+ recognizedClassHelpers: z.array(z.string().min(1)).optional(),
401
+ identity: z
402
+ .object({
403
+ decisions: z
404
+ .array(
405
+ z.object({
406
+ component: z.string().min(1),
407
+ kind: z.enum(["sanction", "reject", "dismiss"]),
408
+ canonicalTarget: z.string().min(1).optional(),
409
+ reason: z.string().optional(),
410
+ decisionId: z.string().min(1).optional(),
411
+ })
412
+ )
413
+ .optional(),
414
+ })
415
+ .optional(),
416
+ });
398
417
 
399
418
  // ---------------------------------------------------------------------------
400
419
  // v2 Schemas — alongside v1 (non-breaking)
@@ -415,10 +434,13 @@ export const compositionMetadataSchema = z.object({
415
434
  * Schema for v2 extended provenance tracking
416
435
  */
417
436
  export const fragmentProvenanceSchema = z.object({
418
- source: z.enum(["storybook", "manual", "ai", "scan"]),
437
+ source: z.enum(fragmentSourceValues),
419
438
  sourceFile: z.string().optional(),
420
439
  confidence: z.number().min(0).max(1).optional(),
421
- timestamp: z.string().datetime().optional(),
440
+ verified: z.boolean().optional(),
441
+ frameworkSupport: z.enum(["native", "manual-only"]).optional(),
442
+ sourceHash: z.string().optional(),
443
+ timestamp: z.string().datetime({ offset: true }).optional(),
422
444
  autoFields: z.array(z.string()).optional(),
423
445
  humanFields: z.array(z.string()).optional(),
424
446
  });
@@ -439,21 +461,179 @@ export const fragmentDefinitionV2Schema = z.object({
439
461
  _provenance: fragmentProvenanceSchema.optional(),
440
462
  });
441
463
 
442
- export const governedFragmentDefinitionSchema = z.object({
443
- component: z.any(),
444
- meta: fragmentMetaSchema,
445
- guidance: fragmentUsageSchema,
446
- props: z.record(propDefinitionSchema).optional(),
447
- relations: z.array(componentRelationSchema).optional(),
448
- examples: z.array(fragmentVariantSchema).optional(),
449
- composition: compositionMetadataSchema.optional(),
464
+ export const governedFragmentDefinitionSchema = z
465
+ .object({
466
+ component: z.any(),
467
+ meta: fragmentMetaSchema,
468
+ guidance: fragmentUsageSchema,
469
+ props: z.record(propDefinitionSchema).optional(),
470
+ relations: z.array(componentRelationSchema).optional(),
471
+ examples: z.array(fragmentVariantSchema).optional(),
472
+ composition: compositionMetadataSchema.optional(),
473
+ contract: fragmentContractSchema.optional(),
474
+ _provenance: fragmentProvenanceSchema.optional(),
475
+ govern: z.function().optional(),
476
+ governance: z.array(componentGovernanceRecordSchema).optional(),
477
+ })
478
+ .superRefine((def, ctx) => {
479
+ if (def.govern === undefined && def.governance === undefined) {
480
+ ctx.addIssue({
481
+ code: z.ZodIssueCode.custom,
482
+ message: "Governed definitions require `govern` or `governance`",
483
+ path: ["govern"],
484
+ });
485
+ }
486
+ });
487
+
488
+ const fragmentRenderSchema = z.custom<FragmentStateV3["render"]>(
489
+ (value) => value !== undefined && typeof value !== "string",
490
+ "Expected JSX/render value, not a code string"
491
+ );
492
+
493
+ export const fragmentMetaV3Schema = z
494
+ .object({
495
+ name: z.string().min(1),
496
+ purpose: z.string().min(1),
497
+ category: z.string().min(1),
498
+ status: z.enum(["stable", "beta", "deprecated", "experimental"]).optional(),
499
+ aliases: z.array(z.string().min(1)).optional(),
500
+ tags: z.array(z.string()).optional(),
501
+ since: z.string().optional(),
502
+ dependencies: z
503
+ .array(
504
+ z
505
+ .object({
506
+ name: z.string().min(1),
507
+ version: z.string().min(1),
508
+ reason: z.string().optional(),
509
+ })
510
+ .strict()
511
+ )
512
+ .optional(),
513
+ figma: z.string().url().optional(),
514
+ figmaProps: z.record(figmaPropMappingSchema).optional(),
515
+ })
516
+ .strict() as unknown as z.ZodType<FragmentMetaV3>;
517
+
518
+ export const fragmentStateV3Schema: z.ZodType<FragmentStateV3> = z
519
+ .object({
520
+ render: fragmentRenderSchema,
521
+ note: z.string().optional(),
522
+ canonical: z.boolean().optional(),
523
+ })
524
+ .strict();
525
+
526
+ export const fragmentDontExampleV3Schema: z.ZodType<FragmentDontExampleV3> = z
527
+ .object({
528
+ reason: z.string().min(1),
529
+ bad: z.string().min(1),
530
+ good: fragmentRenderSchema,
531
+ })
532
+ .strict();
533
+
534
+ export const fragmentGuidanceV3Schema: z.ZodType<FragmentGuidanceV3> = z
535
+ .object({
536
+ when: z.array(z.string()),
537
+ whenNot: z.array(z.string()),
538
+ choose: z.record(z.string()).optional(),
539
+ dont: z.array(fragmentDontExampleV3Schema).optional(),
540
+ guidelines: z.array(z.string()).optional(),
541
+ accessibility: z.array(z.string()).optional(),
542
+ })
543
+ .strict();
544
+
545
+ export const fragmentMatrixV3Schema: z.ZodType<FragmentMatrixV3> = z
546
+ .object({
547
+ axes: z.record(z.union([z.literal("auto"), z.array(z.string().min(1)).readonly()])).optional(),
548
+ forced: z.array(z.string().min(1)).optional(),
549
+ worstCase: z.record(z.unknown()).optional(),
550
+ })
551
+ .strict();
552
+
553
+ export const fragmentPreviewV3Schema: z.ZodType<FragmentPreviewV3> = z
554
+ .object({
555
+ providers: z.array(z.unknown()).optional(),
556
+ dynamicRegions: z.array(z.string()).optional(),
557
+ })
558
+ .strict();
559
+
560
+ export const fragmentDesignV3Schema: z.ZodType<FragmentDesignV3> = z
561
+ .object({
562
+ figmaNode: z.string().min(1).optional(),
563
+ })
564
+ .strict();
565
+
566
+ /**
567
+ * Annotate-only prop entries. Forbidden keys (`type`, `default`, `values`,
568
+ * `required`) redefine extracted truth and are rejected.
569
+ */
570
+ export const propAnnotationSchema: z.ZodType<PropAnnotation> = z
571
+ .object({
572
+ description: z.string().optional(),
573
+ controlType: z
574
+ .enum([
575
+ "text",
576
+ "number",
577
+ "range",
578
+ "boolean",
579
+ "select",
580
+ "multi-select",
581
+ "radio",
582
+ "inline-radio",
583
+ "check",
584
+ "inline-check",
585
+ "object",
586
+ "file",
587
+ "color",
588
+ "date",
589
+ ])
590
+ .optional(),
591
+ controlOptions: z.record(z.unknown()).optional(),
592
+ visibility: z.enum(["public", "internal", "hidden"]).optional(),
593
+ constraints: z.array(z.string()).optional(),
594
+ })
595
+ .strict();
596
+
597
+ export const fragmentAnnotationsV3Schema: z.ZodType<FragmentAnnotationsV3> = z
598
+ .object({
599
+ notes: z.array(z.string()).optional(),
600
+ review: z.array(z.string()).optional(),
601
+ })
602
+ .passthrough();
603
+
604
+ /**
605
+ * Schema for the v3 definition body (second argument). Arity — not keys —
606
+ * discriminates v3 at the call site; this schema never accepts authored `code`.
607
+ */
608
+ const fragmentDefinitionV3BodyShape = {
609
+ meta: fragmentMetaV3Schema,
610
+ states: z.record(fragmentStateV3Schema).refine((states) => Object.keys(states).length > 0, {
611
+ message: "At least one state is required",
612
+ }),
613
+ guidance: fragmentGuidanceV3Schema,
614
+ matrix: fragmentMatrixV3Schema.optional(),
615
+ preview: fragmentPreviewV3Schema.optional(),
616
+ design: fragmentDesignV3Schema.optional(),
617
+ annotations: fragmentAnnotationsV3Schema.optional(),
618
+ props: z.record(propAnnotationSchema).optional(),
619
+ relations: z.array(componentRelationSchema.strict()).optional(),
620
+ composition: compositionMetadataSchema.strict().optional(),
450
621
  contract: fragmentContractSchema.optional(),
451
622
  _provenance: fragmentProvenanceSchema.optional(),
452
623
  govern: z.function().optional(),
453
624
  governance: z.array(componentGovernanceRecordSchema).optional(),
454
- });
625
+ };
455
626
 
456
- /**
457
- * @deprecated Use blockDefinitionSchema instead
458
- */
459
- export const recipeDefinitionSchema = blockDefinitionSchema;
627
+ export const fragmentDefinitionV3BodySchema = z
628
+ .object(fragmentDefinitionV3BodyShape)
629
+ .strict() as unknown as z.ZodType<FragmentDefinitionV3Body<unknown>>;
630
+
631
+ export const fragmentDefinitionV3Schema = z
632
+ .object({
633
+ ...fragmentDefinitionV3BodyShape,
634
+ component: z.unknown().refine((component) => component !== undefined, {
635
+ message: "Component is required",
636
+ }),
637
+ governance: z.array(componentGovernanceRecordSchema),
638
+ })
639
+ .strict() as unknown as z.ZodType<FragmentDefinitionV3<unknown>>;
@@ -347,7 +347,7 @@ export const agentErrorEnvelopeSchema = z.object({
347
347
  }),
348
348
  provenance: agentRunProvenanceSchema.optional(),
349
349
  verdict: agentRunVerdictSchema.optional(),
350
- cloudReport: z.enum(["reported", "failed", "skipped"]).optional(),
350
+ cloudReport: z.enum(["accepted", "reported", "failed", "skipped"]).optional(),
351
351
  notices: z.array(z.string()).optional(),
352
352
  });
353
353
  export type AgentErrorEnvelope = z.infer<typeof agentErrorEnvelopeSchema>;