@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.
- package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
- package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
- package/dist/chunk-7ULGH74M.js +66 -0
- package/dist/chunk-7ULGH74M.js.map +1 -0
- package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
- package/dist/chunk-BMPYIUZE.js.map +1 -0
- package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
- package/dist/chunk-ML5S6QNU.js.map +1 -0
- package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
- package/dist/chunk-MZ2FS7U4.js.map +1 -0
- package/dist/chunk-PWIJMOI4.js +202 -0
- package/dist/chunk-PWIJMOI4.js.map +1 -0
- package/dist/chunk-RYFULE43.js +578 -0
- package/dist/chunk-RYFULE43.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +3 -2
- package/dist/compiled-types/index.d.ts +327 -2
- package/dist/compiled-types/index.js +1 -1
- package/dist/generate/index.d.ts +3 -2
- package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
- package/dist/governance-telemetry.d.ts +6 -0
- package/dist/governance-telemetry.js +1 -1
- package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
- package/dist/index.d.ts +3280 -1305
- package/dist/index.js +2414 -350
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +228 -0
- package/dist/manifest.js +24 -0
- package/dist/manifest.js.map +1 -0
- package/dist/preview/index.js +45 -1
- package/dist/preview/index.js.map +1 -1
- package/dist/preview-runtime.d.ts +1 -2
- package/dist/preview-runtime.js +150 -14
- package/dist/preview-runtime.js.map +1 -1
- package/dist/react-types.d.ts +1 -2
- package/dist/registry.d.ts +1412 -203
- package/dist/registry.js +30 -3
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +3 -2
- package/dist/storyAdapter.d.ts +1 -2
- package/dist/storyAdapter.js +11 -49
- package/dist/storyAdapter.js.map +1 -1
- package/dist/test-utils.d.ts +3 -2
- package/dist/topology/index.d.ts +1 -1
- package/dist/topology/index.js +1 -1
- package/package.json +8 -2
- package/src/__tests__/contract-parser.test.ts +318 -277
- package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
- package/src/__tests__/preview-runtime.test.tsx +30 -8
- package/src/__tests__/schema.test.ts +191 -14
- package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
- package/src/analysis-plan/coverage.ts +181 -0
- package/src/analysis-plan/digest.ts +141 -0
- package/src/analysis-plan/index.ts +34 -0
- package/src/analysis-plan/types.ts +207 -0
- package/src/approved-contract-tokens.test.ts +39 -0
- package/src/approved-contract-tokens.ts +18 -0
- package/src/codes/__tests__/codes.test.ts +13 -0
- package/src/codes/codes.ts +40 -0
- package/src/compiled-types/index.ts +640 -39
- package/src/compiled-types/parse.test.ts +145 -4
- package/src/component-contract.ts +95 -53
- package/src/composition.ts +7 -13
- package/src/constants.ts +3 -6
- package/src/contract/hash.test.ts +20 -0
- package/src/contract/hash.ts +66 -9
- package/src/contract/index.ts +24 -1
- package/src/contract/manifest.test.ts +94 -0
- package/src/contract/manifest.ts +68 -0
- package/src/contract/preimage.test.ts +219 -1
- package/src/contract/preimage.ts +326 -6
- package/src/contract/stamp.test.ts +3 -0
- package/src/contract/stamp.ts +1 -1
- package/src/contract-parser.ts +54 -30
- package/src/defineFragment.test.ts +476 -91
- package/src/defineFragment.ts +204 -114
- package/src/domain-ids.test.ts +35 -0
- package/src/domain-ids.ts +61 -0
- package/src/evaluation/evaluate.test.ts +522 -0
- package/src/evaluation/evaluate.ts +690 -0
- package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
- package/src/evaluation/index.ts +58 -0
- package/src/evaluation/receipt.ts +753 -0
- package/src/evaluation/types.ts +406 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/compile.ts +29 -6
- package/src/facts/fact-index.ts +13 -3
- package/src/facts/fact-integrity-v1.test.ts +172 -0
- package/src/facts/facts.test.ts +15 -0
- package/src/facts/ids.ts +46 -3
- package/src/facts/index.ts +14 -1
- package/src/facts/integrity.ts +134 -0
- package/src/facts/types.ts +36 -0
- package/src/governance-integrity.test.ts +1 -0
- package/src/governance-integrity.ts +5 -3
- package/src/governance-telemetry.ts +8 -0
- package/src/governance.ts +70 -8
- package/src/index.ts +230 -37
- package/src/preview/validation.test.ts +62 -0
- package/src/preview/validation.ts +48 -2
- package/src/preview-runtime.tsx +227 -20
- package/src/registry-install-plan.ts +200 -109
- package/src/registry-shards.test.ts +263 -0
- package/src/registry.ts +237 -0
- package/src/repository-binding.test.ts +50 -0
- package/src/repository-binding.ts +96 -0
- package/src/rules/families.test.ts +36 -0
- package/src/rules/finding.ts +7 -2
- package/src/rules/index.ts +17 -1
- package/src/rules/rule-config.test.ts +66 -0
- package/src/rules/rule-config.ts +73 -0
- package/src/rules/rules.test.ts +26 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
- package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
- package/src/schema.ts +293 -113
- package/src/schemas/index.ts +1 -1
- package/src/storyAdapter.test.ts +68 -12
- package/src/storyAdapter.ts +44 -75
- package/src/topology/resolve-area.ts +1 -1
- package/src/types.ts +258 -40
- package/dist/chunk-3LLRNCPX.js.map +0 -1
- package/dist/chunk-RANPUC6C.js +0 -72
- package/dist/chunk-RANPUC6C.js.map +0 -1
- package/dist/chunk-XN3LSDPY.js.map +0 -1
- package/dist/chunk-YF65VYRY.js.map +0 -1
- package/src/fragment-types.ts +0 -214
- package/src/react-create-element.test.ts +0 -22
- package/src/react-create-element.ts +0 -12
- package/src/storyFilters.test.ts +0 -350
- package/src/storyFilters.ts +0 -253
package/src/defineFragment.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FragmentDefinition,
|
|
3
3
|
FragmentDefinitionV2,
|
|
4
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
+
}
|