@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
@@ -1,13 +1,14 @@
1
1
  import type {
2
2
  FragmentDefinition,
3
3
  FragmentDefinitionV2,
4
- CompiledFragment,
4
+ FragmentDefinitionV3,
5
+ FragmentDefinitionV3Body,
5
6
  FragmentComponent,
6
7
  BlockDefinition,
7
- CompiledBlock,
8
8
  AIMetadata,
9
9
  FragmentGenerated,
10
10
  } from "./types.js";
11
+ import type { CompiledBlock, CompiledFragment } from "./compiled-types/index.js";
11
12
  import type {
12
13
  GovernedFragmentDefinition,
13
14
  ResolvedGovernedFragmentDefinition,
@@ -15,15 +16,23 @@ import type {
15
16
  import {
16
17
  fragmentDefinitionSchema,
17
18
  fragmentDefinitionV2Schema,
19
+ fragmentDefinitionV3BodySchema,
18
20
  governedFragmentDefinitionSchema,
19
21
  blockDefinitionSchema,
20
22
  } from "./schema.js";
21
23
  import { resolveComponentGovernance } from "./governance.js";
22
24
 
25
+ /**
26
+ * True only when an explicit govern function or governance record array is
27
+ * present. Optional-key sniffing (`"govern" in def`) is intentionally avoided —
28
+ * that was the optional-key type/runtime lie.
29
+ */
23
30
  function isGovernedDefinition<T>(
24
31
  def: FragmentDefinition<T> | FragmentDefinitionV2<T> | GovernedFragmentDefinition<T>
25
32
  ): def is GovernedFragmentDefinition<T> {
26
- return "govern" in def || "governance" in def;
33
+ if (!def || typeof def !== "object") return false;
34
+ const record = def as Record<string, unknown>;
35
+ return typeof record.govern === "function" || Array.isArray(record.governance);
27
36
  }
28
37
 
29
38
  /**
@@ -33,9 +42,75 @@ function isGovernedDefinition<T>(
33
42
  function isV2Definition<T>(
34
43
  def: FragmentDefinition<T> | FragmentDefinitionV2<T> | GovernedFragmentDefinition<T>
35
44
  ): def is FragmentDefinitionV2<T> {
45
+ if (!def || typeof def !== "object") return false;
36
46
  return !isGovernedDefinition(def) && ("guidance" in def || "examples" in def);
37
47
  }
38
48
 
49
+ function rejectInvalidGovernanceDiscriminants(definition: unknown): void {
50
+ if (!definition || typeof definition !== "object") return;
51
+
52
+ const record = definition as Record<string, unknown>;
53
+ const hasOwn = (key: "govern" | "governance") =>
54
+ Object.prototype.hasOwnProperty.call(record, key);
55
+ const errors: { path: string[]; message: string }[] = [];
56
+
57
+ if (hasOwn("govern") && typeof record.govern !== "function") {
58
+ errors.push({ path: ["govern"], message: "Expected a governance authoring function" });
59
+ }
60
+ if (hasOwn("governance") && !Array.isArray(record.governance)) {
61
+ errors.push({ path: ["governance"], message: "Expected an array of governance records" });
62
+ }
63
+
64
+ if (errors.length > 0) {
65
+ const name =
66
+ record.meta && typeof record.meta === "object" && "name" in record.meta
67
+ ? String((record.meta as Record<string, unknown>).name)
68
+ : "unknown";
69
+ throwInvalidFragment(name, "governed", errors);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Internal marker carried across the compiler's jiti boundary.
75
+ *
76
+ * The class deliberately stays private: callers should consume Core's public
77
+ * schemas, while the compiler needs a non-message-based way to distinguish a
78
+ * Core authoring refusal from an unrelated exception thrown by component code.
79
+ */
80
+ class InvalidFragmentDefinitionError extends Error {
81
+ readonly code = "FUI9010";
82
+ readonly source = "@usefragments/core/defineFragment";
83
+ readonly api: "v1" | "v2" | "governed" | "v3" | "unknown";
84
+ readonly issues: readonly string[];
85
+
86
+ constructor(
87
+ name: string,
88
+ api: "v1" | "v2" | "governed" | "v3" | "unknown",
89
+ errors: { path: (string | number)[]; message: string }[]
90
+ ) {
91
+ const issues = errors
92
+ .map((error) => `${error.path.join(".") || "(root)"}: ${error.message}`)
93
+ .sort();
94
+ super(
95
+ [
96
+ `${InvalidFragmentDefinitionError.name}: Invalid fragment definition for "${name}":`,
97
+ ...issues.map((issue) => ` - ${issue}`),
98
+ ].join("\n")
99
+ );
100
+ this.name = InvalidFragmentDefinitionError.name;
101
+ this.api = api;
102
+ this.issues = issues;
103
+ }
104
+ }
105
+
106
+ function throwInvalidFragment(
107
+ name: string,
108
+ api: "v1" | "v2" | "governed" | "v3" | "unknown",
109
+ errors: { path: (string | number)[]; message: string }[]
110
+ ) {
111
+ throw new InvalidFragmentDefinitionError(name, api, errors);
112
+ }
113
+
39
114
  /**
40
115
  * Normalize a v2 definition to v1 shape for downstream compatibility.
41
116
  * The build pipeline, compiler, and validators all work with v1 internally.
@@ -52,13 +127,14 @@ export function normalizeToV1<T>(def: FragmentDefinitionV2<T>): FragmentDefiniti
52
127
  };
53
128
  }
54
129
 
55
- // Map _provenance → _generated (narrow source type, drop extended fields)
130
+ // Map _provenance → _generated (drop v2-only extended fields)
56
131
  let generated: FragmentGenerated | undefined;
57
132
  if (def._provenance) {
58
133
  generated = {
59
- source: def._provenance.source === "scan" ? "ai" : def._provenance.source,
134
+ source: def._provenance.source,
60
135
  sourceFile: def._provenance.sourceFile,
61
136
  confidence: def._provenance.confidence,
137
+ verified: def._provenance.verified,
62
138
  timestamp: def._provenance.timestamp,
63
139
  };
64
140
  }
@@ -76,78 +152,53 @@ export function normalizeToV1<T>(def: FragmentDefinitionV2<T>): FragmentDefiniti
76
152
  };
77
153
  }
78
154
 
79
- /**
80
- * Define a fragment for a component.
81
- *
82
- * This is the main API for creating fragment documentation.
83
- * It provides runtime validation and type safety.
84
- *
85
- * Accepts both v1 and v2 shapes:
86
- * - v1: `{ usage, variants, ai, _generated }`
87
- * - v2: `{ guidance, examples, composition, _provenance }`
88
- *
89
- * @example v1
90
- * ```tsx
91
- * import { defineFragment } from '@usefragments/core';
92
- * import { Button } from './Button';
93
- *
94
- * export default defineFragment({
95
- * component: Button,
96
- * meta: {
97
- * name: 'Button',
98
- * description: 'Primary action trigger',
99
- * category: 'actions',
100
- * },
101
- * usage: {
102
- * when: ['User needs to trigger an action'],
103
- * whenNot: ['Navigation without side effects'],
104
- * },
105
- * props: {
106
- * variant: {
107
- * type: 'enum',
108
- * values: ['primary', 'secondary'],
109
- * default: 'primary',
110
- * description: 'Visual style',
111
- * },
112
- * },
113
- * variants: [
114
- * {
115
- * name: 'Default',
116
- * description: 'Default button',
117
- * render: () => <Button>Click me</Button>,
118
- * },
119
- * ],
120
- * });
121
- * ```
122
- *
123
- * @example v2
124
- * ```tsx
125
- * import { defineFragment } from '@usefragments/core';
126
- * import { Card } from './Card';
127
- *
128
- * export default defineFragment({
129
- * component: Card,
130
- * meta: { name: 'Card', description: 'Content container', category: 'layout' },
131
- * guidance: {
132
- * when: ['Grouping related content'],
133
- * whenNot: ['Full-page layouts'],
134
- * },
135
- * props: { ... },
136
- * examples: [{ name: 'Default', description: 'Basic card', render: () => <Card>...</Card> }],
137
- * composition: { pattern: 'compound', subComponents: ['Header', 'Body', 'Footer'] },
138
- * });
139
- * ```
140
- */
141
- export function defineFragment<TProps>(
142
- definition: GovernedFragmentDefinition<TProps>
143
- ): ResolvedGovernedFragmentDefinition<TProps>;
144
- export function defineFragment<TProps>(
145
- definition: FragmentDefinition<TProps>
146
- ): FragmentDefinition<TProps>;
147
- export function defineFragment<TProps>(
148
- definition: FragmentDefinitionV2<TProps>
149
- ): FragmentDefinitionV2<TProps>;
150
- export function defineFragment<TProps>(
155
+ function defineFragmentV3<TProps>(
156
+ component: FragmentComponent<TProps>,
157
+ definition: FragmentDefinitionV3Body<TProps>
158
+ ): FragmentDefinitionV3<TProps> {
159
+ const result = fragmentDefinitionV3BodySchema.safeParse(definition);
160
+ if (!result.success) {
161
+ throwInvalidFragment(
162
+ definition?.meta?.name || "unknown",
163
+ "v3",
164
+ result.error.errors.map((e) => ({ path: e.path, message: e.message }))
165
+ );
166
+ }
167
+
168
+ const governance =
169
+ typeof definition.govern === "function" || Array.isArray(definition.governance)
170
+ ? resolveComponentGovernance({
171
+ component,
172
+ meta: {
173
+ name: definition.meta.name,
174
+ description: definition.meta.purpose,
175
+ category: definition.meta.category,
176
+ status: definition.meta.status,
177
+ tags: definition.meta.tags,
178
+ since: definition.meta.since,
179
+ dependencies: definition.meta.dependencies,
180
+ figma: definition.meta.figma,
181
+ figmaProps: definition.meta.figmaProps,
182
+ },
183
+ guidance: {
184
+ when: definition.guidance.when,
185
+ whenNot: definition.guidance.whenNot,
186
+ guidelines: definition.guidance.guidelines,
187
+ accessibility: definition.guidance.accessibility,
188
+ },
189
+ govern: definition.govern,
190
+ governance: definition.governance,
191
+ } as GovernedFragmentDefinition<TProps>)
192
+ : [];
193
+
194
+ return {
195
+ ...definition,
196
+ component,
197
+ governance,
198
+ };
199
+ }
200
+
201
+ function defineFragmentOneArg<TProps>(
151
202
  definition:
152
203
  | FragmentDefinition<TProps>
153
204
  | FragmentDefinitionV2<TProps>
@@ -156,23 +207,30 @@ export function defineFragment<TProps>(
156
207
  | FragmentDefinition<TProps>
157
208
  | FragmentDefinitionV2<TProps>
158
209
  | ResolvedGovernedFragmentDefinition<TProps> {
159
- // Validate at runtime in development
160
- if (process.env.NODE_ENV !== "production") {
161
- const governed = isGovernedDefinition(definition);
162
- const v2 = isV2Definition(definition);
163
- const schema = governed
164
- ? governedFragmentDefinitionSchema
165
- : v2
166
- ? fragmentDefinitionV2Schema
167
- : fragmentDefinitionSchema;
168
- const result = schema.safeParse(definition);
169
- if (!result.success) {
170
- const name = definition.meta?.name || "unknown";
171
- const errors = result.error.errors
172
- .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
173
- .join("\n");
174
- throw new Error(`Invalid fragment definition for "${name}":\n${errors}`);
175
- }
210
+ if (!definition || typeof definition !== "object") {
211
+ throwInvalidFragment("unknown", "unknown", [
212
+ { path: [], message: "Expected a fragment definition object" },
213
+ ]);
214
+ }
215
+
216
+ // Reserved governance keys may not fall through to the permissive legacy
217
+ // schemas merely because their values are malformed.
218
+ rejectInvalidGovernanceDiscriminants(definition);
219
+
220
+ const governed = isGovernedDefinition(definition);
221
+ const v2 = isV2Definition(definition);
222
+ const schema = governed
223
+ ? governedFragmentDefinitionSchema
224
+ : v2
225
+ ? fragmentDefinitionV2Schema
226
+ : fragmentDefinitionSchema;
227
+ const result = schema.safeParse(definition);
228
+ if (!result.success) {
229
+ throwInvalidFragment(
230
+ definition?.meta?.name || "unknown",
231
+ governed ? "governed" : v2 ? "v2" : "v1",
232
+ result.error.errors.map((e) => ({ path: e.path, message: e.message }))
233
+ );
176
234
  }
177
235
 
178
236
  if (isGovernedDefinition(definition)) {
@@ -186,6 +244,40 @@ export function defineFragment<TProps>(
186
244
  return definition;
187
245
  }
188
246
 
247
+ /**
248
+ * Define a fragment for a component.
249
+ *
250
+ * **v3 (arity discriminant):** `defineFragment(Component, definition)` — the
251
+ * only v3 entry. Optional-key sniffing is not used.
252
+ *
253
+ * **v1 / v2 / governed (one argument):** preserved. Governed definitions require
254
+ * an actual `govern` function or `governance` array (not merely an optional key).
255
+ */
256
+ export function defineFragment<C extends FragmentComponent<any>>(
257
+ component: C,
258
+ definition: FragmentDefinitionV3Body<C extends FragmentComponent<infer P> ? P : unknown>
259
+ ): FragmentDefinitionV3<C extends FragmentComponent<infer P> ? P : unknown>;
260
+ export function defineFragment<TProps>(
261
+ definition: GovernedFragmentDefinition<TProps>
262
+ ): ResolvedGovernedFragmentDefinition<TProps>;
263
+ export function defineFragment<TProps>(
264
+ definition: FragmentDefinition<TProps>
265
+ ): FragmentDefinition<TProps>;
266
+ export function defineFragment<TProps>(
267
+ definition: FragmentDefinitionV2<TProps>
268
+ ): FragmentDefinitionV2<TProps>;
269
+ export function defineFragment(componentOrDefinition: unknown, maybeDefinition?: unknown): unknown {
270
+ if (arguments.length >= 2) {
271
+ return defineFragmentV3(
272
+ componentOrDefinition as FragmentComponent,
273
+ maybeDefinition as FragmentDefinitionV3Body
274
+ );
275
+ }
276
+ return defineFragmentOneArg(
277
+ componentOrDefinition as FragmentDefinition | FragmentDefinitionV2 | GovernedFragmentDefinition
278
+ );
279
+ }
280
+
189
281
  /**
190
282
  * Compile a fragment definition to JSON-serializable format.
191
283
  * Used for generating fragments.json for AI consumption.
@@ -206,12 +298,20 @@ export function compileFragment(
206
298
  relations: definition.relations,
207
299
  variants: definition.examples ?? [],
208
300
  contract: definition.contract,
301
+ ai: definition.composition
302
+ ? {
303
+ compositionPattern: definition.composition.pattern,
304
+ subComponents: definition.composition.subComponents,
305
+ requiredChildren: definition.composition.requiredChildren,
306
+ commonPatterns: definition.composition.commonPatterns,
307
+ }
308
+ : undefined,
209
309
  _generated: definition._provenance
210
310
  ? {
211
- source:
212
- definition._provenance.source === "scan" ? "ai" : definition._provenance.source,
311
+ source: definition._provenance.source,
213
312
  sourceFile: definition._provenance.sourceFile,
214
313
  confidence: definition._provenance.confidence,
314
+ verified: definition._provenance.verified,
215
315
  timestamp: definition._provenance.timestamp,
216
316
  }
217
317
  : undefined,
@@ -249,24 +349,19 @@ export function compileFragment(
249
349
  * wire together for common use cases.
250
350
  */
251
351
  export function defineBlock(definition: BlockDefinition): BlockDefinition {
252
- if (process.env.NODE_ENV !== "production") {
253
- const result = blockDefinitionSchema.safeParse(definition);
254
- if (!result.success) {
255
- const errors = result.error.errors
256
- .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
257
- .join("\n");
258
- throw new Error(`Invalid block definition for "${definition.name || "unknown"}":\n${errors}`);
259
- }
352
+ const result = blockDefinitionSchema.safeParse(definition);
353
+ if (!result.success) {
354
+ const errors = result.error.errors
355
+ .map((e) => `${e.path.join(".") || "(root)"}: ${e.message}`)
356
+ .sort()
357
+ .map((issue) => ` - ${issue}`)
358
+ .join("\n");
359
+ throw new Error(`Invalid block definition for "${definition.name || "unknown"}":\n${errors}`);
260
360
  }
261
361
 
262
362
  return definition;
263
363
  }
264
364
 
265
- /**
266
- * @deprecated Use defineBlock instead
267
- */
268
- export const defineRecipe = defineBlock;
269
-
270
365
  /**
271
366
  * Compile a block definition to JSON-serializable format.
272
367
  */
@@ -282,11 +377,6 @@ export function compileBlock(definition: BlockDefinition, filePath: string): Com
282
377
  };
283
378
  }
284
379
 
285
- /**
286
- * @deprecated Use compileBlock instead
287
- */
288
- export const compileRecipe = compileBlock;
289
-
290
380
  /**
291
381
  * Type helper for extracting props type from a component
292
382
  */
@@ -0,0 +1,35 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ analysisPlanIdFromDigest,
4
+ evaluationReceiptIdFromDigest,
5
+ mintEvaluationReceiptId,
6
+ parseAnalysisPlanId,
7
+ parseEvaluationReceiptId,
8
+ } from "./domain-ids.js";
9
+
10
+ const DIGEST = "0123456789abcdef".repeat(4);
11
+
12
+ describe("V1 domain IDs", () => {
13
+ it("retains the full digest in content-derived IDs", () => {
14
+ expect(analysisPlanIdFromDigest(DIGEST)).toBe(`apl_${DIGEST}`);
15
+ expect(evaluationReceiptIdFromDigest(DIGEST)).toBe(`evr_${DIGEST}`);
16
+ expect(mintEvaluationReceiptId(DIGEST)).toBe(`evr_${DIGEST}`);
17
+ });
18
+
19
+ it.each([
20
+ [parseAnalysisPlanId, `apl_${DIGEST}`],
21
+ [parseEvaluationReceiptId, `evr_${DIGEST}`],
22
+ ] as const)("round-trips a valid ID", (parse, value) => {
23
+ expect(parse(value)).toBe(value);
24
+ });
25
+
26
+ it.each([`apl_${DIGEST.slice(0, -1)}`, `apl_${DIGEST.toUpperCase()}`, `evr_${DIGEST}extra`])(
27
+ "rejects truncated, uppercase, or extended IDs: %s",
28
+ (value) => {
29
+ expect(() => {
30
+ if (value.startsWith("apl_")) parseAnalysisPlanId(value);
31
+ else if (value.startsWith("evr_")) parseEvaluationReceiptId(value);
32
+ }).toThrow();
33
+ }
34
+ );
35
+ });
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+
3
+ const FULL_DIGEST_PATTERN = /^[0-9a-f]{64}$/u;
4
+
5
+ export declare const analysisPlanIdBrand: unique symbol;
6
+ export declare const evaluationReceiptIdBrand: unique symbol;
7
+
8
+ export type DigestHex = string;
9
+ export type AnalysisPlanId = `apl_${string}` & { readonly [analysisPlanIdBrand]: void };
10
+ export type EvaluationReceiptId = `evr_${string}` & {
11
+ readonly [evaluationReceiptIdBrand]: void;
12
+ };
13
+ export const digestHexStringSchema = z
14
+ .string()
15
+ .regex(FULL_DIGEST_PATTERN, "Expected 64 lowercase hexadecimal characters");
16
+ export const digestHexSchema = digestHexStringSchema;
17
+
18
+ export const analysisPlanIdStringSchema = z
19
+ .string()
20
+ .regex(/^apl_[0-9a-f]{64}$/u, "Invalid analysis plan ID");
21
+ export const analysisPlanIdSchema = analysisPlanIdStringSchema.transform(
22
+ (value) => value as AnalysisPlanId
23
+ );
24
+
25
+ export const evaluationReceiptIdSchema = z
26
+ .string()
27
+ .regex(/^evr_[0-9a-f]{64}$/u, "Invalid evaluation receipt ID")
28
+ .transform((value) => value as EvaluationReceiptId);
29
+ export const evaluationReceiptIdStringSchema = z
30
+ .string()
31
+ .regex(/^evr_[0-9a-f]{64}$/u, "Invalid evaluation receipt ID");
32
+
33
+ export function parseDigestHex(value: string): DigestHex {
34
+ return digestHexSchema.parse(value);
35
+ }
36
+
37
+ export function analysisPlanIdFromDigest(digest: string): AnalysisPlanId {
38
+ return analysisPlanIdSchema.parse(`apl_${parseDigestHex(digest)}`);
39
+ }
40
+
41
+ export function evaluationReceiptIdFromDigest(digest: string): EvaluationReceiptId {
42
+ return evaluationReceiptIdSchema.parse(`evr_${parseDigestHex(digest)}`);
43
+ }
44
+
45
+ /**
46
+ * Mint a Cloud evaluation receipt id from a 64-hex digest (kernel
47
+ * `resultDigest` on the ingest envelope; receipt-semantic digest on the
48
+ * adoption envelope). Same `evr_` + 64 hex contract as
49
+ * `evaluationReceiptIdFromDigest`.
50
+ */
51
+ export function mintEvaluationReceiptId(resultDigest: string): EvaluationReceiptId {
52
+ return evaluationReceiptIdFromDigest(resultDigest);
53
+ }
54
+
55
+ export function parseAnalysisPlanId(value: string): AnalysisPlanId {
56
+ return analysisPlanIdSchema.parse(value);
57
+ }
58
+
59
+ export function parseEvaluationReceiptId(value: string): EvaluationReceiptId {
60
+ return evaluationReceiptIdSchema.parse(value);
61
+ }