@usefragments/core 1.11.0 → 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 (39) hide show
  1. package/dist/{chunk-RPSEABY3.js → chunk-BMPYIUZE.js} +23 -33
  2. package/dist/chunk-BMPYIUZE.js.map +1 -0
  3. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  4. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  5. package/dist/codes/index.d.ts +1 -1
  6. package/dist/codes/index.js +1 -1
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/generate/index.d.ts +1 -1
  9. package/dist/{governance-BAsy1k2H.d.ts → governance-hOPXGbbs.d.ts} +3 -3
  10. package/dist/index.d.ts +133 -6026
  11. package/dist/index.js +179 -806
  12. package/dist/index.js.map +1 -1
  13. package/dist/preview-runtime.d.ts +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/schemas/index.js +1 -1
  16. package/dist/storyAdapter.d.ts +1 -1
  17. package/dist/test-utils.d.ts +1 -1
  18. package/dist/topology/index.d.ts +1 -1
  19. package/dist/topology/index.js +1 -1
  20. package/package.json +1 -1
  21. package/src/approved-contract-tokens.test.ts +39 -0
  22. package/src/approved-contract-tokens.ts +18 -0
  23. package/src/codes/__tests__/codes.test.ts +4 -0
  24. package/src/codes/codes.ts +10 -0
  25. package/src/domain-ids.test.ts +9 -26
  26. package/src/domain-ids.ts +0 -45
  27. package/src/evaluation/evaluate.ts +17 -7
  28. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +20 -8
  29. package/src/evaluation/index.ts +1 -1
  30. package/src/governance.ts +18 -0
  31. package/src/index.ts +10 -79
  32. package/src/topology/resolve-area.ts +1 -1
  33. package/src/types.ts +0 -3
  34. package/dist/chunk-3LLRNCPX.js.map +0 -1
  35. package/dist/chunk-RPSEABY3.js.map +0 -1
  36. package/src/feature-plan/digest.ts +0 -217
  37. package/src/feature-plan/feature-plan-v1.test.ts +0 -529
  38. package/src/feature-plan/index.ts +0 -65
  39. package/src/feature-plan/types.ts +0 -628
@@ -1,529 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { featurePlanIdFromEntropy } from "../domain-ids.js";
3
- import {
4
- agentFeatureContextV1Schema,
5
- approvedFeatureManifestV1Schema,
6
- buildApprovedFeatureManifestV1,
7
- buildFeatureProposalV1,
8
- featureProposalV1Schema,
9
- parseApprovedFeatureManifestV1,
10
- parseFeatureProposalV1,
11
- type ApprovedFeatureManifestInputV1,
12
- type FeatureProposalInputV1,
13
- } from "./index.js";
14
-
15
- const A = "a".repeat(64);
16
- const B = "b".repeat(64);
17
- const C = "c".repeat(64);
18
- const D = "d".repeat(64);
19
- const PLAN_ID = featurePlanIdFromEntropy("00112233445566778899aabbccddeeff");
20
-
21
- function proposalInput(overrides: Partial<FeatureProposalInputV1> = {}): FeatureProposalInputV1 {
22
- return {
23
- schemaVersion: 1,
24
- kind: "proposal",
25
- planId: PLAN_ID,
26
- pins: {
27
- bindingId: "binding_1",
28
- grounding: {
29
- sourceCommitId: "head-sha",
30
- analysisPlanDigest: A,
31
- contextSliceDigest: B,
32
- },
33
- authority: {
34
- fcid: C,
35
- contractArtifactDigest: D,
36
- analysisObligationsDigest: A,
37
- },
38
- evidenceDigest: B,
39
- },
40
- placement: {
41
- targetKind: "page",
42
- targetRef: "settings/notifications",
43
- routeRef: "/settings/notifications",
44
- parentSlotRef: "WorkspacePage.content",
45
- evidenceRefIds: ["source-shell"],
46
- },
47
- componentUses: [
48
- {
49
- useId: "switch",
50
- componentId: "Switch",
51
- importPath: "@usefragments/ui",
52
- purpose: "Toggle notification preference",
53
- props: { size: "md", disabled: false },
54
- evidenceRefIds: ["contract-switch", "source-form"],
55
- },
56
- ],
57
- tokenUses: [
58
- {
59
- useId: "section-gap",
60
- tokenId: "--fui-space-3",
61
- role: "gap",
62
- evidenceRefIds: ["contract-space"],
63
- },
64
- ],
65
- patternUses: [
66
- {
67
- useId: "settings-row",
68
- patternId: "cloud.settings-row",
69
- patternRevisionDigest: A,
70
- status: "approved_guidance",
71
- disposition: "reuse",
72
- evidenceRefIds: ["source-form"],
73
- },
74
- ],
75
- layout: {
76
- concerns: [
77
- {
78
- concern: "outer_inline_spacing",
79
- ownerLayer: "application_shell",
80
- ownerRef: "WorkspacePage",
81
- childAction: "consume",
82
- evidenceRefIds: ["source-shell"],
83
- },
84
- {
85
- concern: "child_gap",
86
- ownerLayer: "section",
87
- ownerRef: "NotificationPreferencesSection",
88
- childAction: "own",
89
- tokenRef: "--fui-space-3",
90
- evidenceRefIds: ["contract-space"],
91
- },
92
- ],
93
- },
94
- responsive: [
95
- {
96
- decisionId: "responsive-row",
97
- source: "observed",
98
- breakpointRef: "$fui-breakpoint-md",
99
- behavior: "Stack label and control below md",
100
- evidenceRefIds: ["source-form"],
101
- },
102
- ],
103
- scenarios: [
104
- {
105
- scenarioId: "save-error",
106
- viewportRef: "mobile",
107
- dataState: "error",
108
- interactionState: "saving",
109
- acceptanceIds: ["A2"],
110
- },
111
- ],
112
- gaps: [
113
- {
114
- gapId: "inline-field",
115
- capability: "Inline labelled boolean field",
116
- required: false,
117
- disposition: "local_composition",
118
- reason: "Reuse the existing settings-row composition",
119
- evidenceRefIds: ["source-form"],
120
- },
121
- ],
122
- acceptance: [
123
- {
124
- acceptanceId: "A1",
125
- statement: "Owner saves a preference and reload sees it",
126
- proof: "integration",
127
- required: true,
128
- evidenceRefIds: ["user-intent"],
129
- },
130
- {
131
- acceptanceId: "A2",
132
- statement: "A failed save restores the prior value",
133
- proof: "browser",
134
- required: true,
135
- evidenceRefIds: ["user-intent"],
136
- },
137
- ],
138
- questions: [
139
- {
140
- decisionId: "save-model",
141
- kind: "reuse",
142
- question: "Save immediately or as a section?",
143
- options: [
144
- { optionId: "immediate", label: "Immediately", consequence: "Matches current settings" },
145
- { optionId: "section", label: "Save section", consequence: "Adds dirty state" },
146
- ],
147
- recommendedOptionId: "immediate",
148
- required: true,
149
- evidenceRefIds: ["source-form"],
150
- },
151
- ],
152
- evidenceRefs: [
153
- {
154
- evidenceId: "user-intent",
155
- kind: "user_intent",
156
- authority: "user_asserted",
157
- ref: "intent:settings-notifications",
158
- digest: A,
159
- },
160
- {
161
- evidenceId: "source-shell",
162
- kind: "source",
163
- authority: "source_observed",
164
- ref: "apps/cloud/src/WorkspacePage.tsx:1",
165
- digest: B,
166
- },
167
- {
168
- evidenceId: "source-form",
169
- kind: "source",
170
- authority: "source_observed",
171
- ref: "apps/cloud/src/SettingsRow.tsx:1",
172
- digest: C,
173
- },
174
- {
175
- evidenceId: "contract-switch",
176
- kind: "contract",
177
- authority: "contract_approved",
178
- ref: "component:Switch",
179
- digest: D,
180
- },
181
- {
182
- evidenceId: "contract-space",
183
- kind: "contract",
184
- authority: "contract_approved",
185
- ref: "token:--fui-space-3",
186
- digest: A,
187
- },
188
- ],
189
- ...overrides,
190
- };
191
- }
192
-
193
- function manifestInput(
194
- overrides: Partial<ApprovedFeatureManifestInputV1> = {}
195
- ): ApprovedFeatureManifestInputV1 {
196
- const proposal = buildFeatureProposalV1(proposalInput());
197
- return {
198
- schemaVersion: 1,
199
- kind: "approved_manifest",
200
- planId: proposal.planId,
201
- parentRevisionDigest: proposal.revisionDigest,
202
- proposalRevisionDigest: proposal.revisionDigest,
203
- pins: proposal.pins,
204
- validatedGrounding: { ...proposal.pins.grounding },
205
- placement: proposal.placement,
206
- componentUses: proposal.componentUses,
207
- tokenUses: proposal.tokenUses,
208
- patternUses: proposal.patternUses,
209
- layout: proposal.layout,
210
- responsive: proposal.responsive,
211
- scenarios: proposal.scenarios,
212
- gaps: proposal.gaps,
213
- acceptance: proposal.acceptance,
214
- evidenceRefs: proposal.evidenceRefs,
215
- decisions: [{ decisionId: "save-model", selectedOptionId: "immediate" }],
216
- approval: {
217
- approverUserId: "user_1",
218
- decisionDigest: D,
219
- approvedAt: "2026-09-01T12:00:00.000Z",
220
- },
221
- ...overrides,
222
- };
223
- }
224
-
225
- describe("FeatureProposalV1", () => {
226
- it("has a checked-in stable revision digest", () => {
227
- const proposal = buildFeatureProposalV1(proposalInput());
228
- expect(proposal.revisionDigest).toBe(
229
- "a0e17ecec3c2a88ed421bd1102c1a806be34887f8e053b7236cd5e9551fd0cf7"
230
- );
231
- expect(proposal.revisionId).toBe(`fpr_${proposal.revisionDigest}`);
232
- expect(parseFeatureProposalV1(proposal)).toEqual(proposal);
233
- });
234
-
235
- it("normalizes set-like order but preserves decision-option order", () => {
236
- const first = buildFeatureProposalV1(proposalInput());
237
- const reordered = buildFeatureProposalV1(
238
- proposalInput({
239
- evidenceRefs: [...proposalInput().evidenceRefs].reverse(),
240
- componentUses: proposalInput().componentUses.map((use) => ({
241
- ...use,
242
- evidenceRefIds: [...use.evidenceRefIds].reverse(),
243
- props: { disabled: false, size: "md" },
244
- })),
245
- acceptance: [...proposalInput().acceptance].reverse(),
246
- layout: { concerns: [...proposalInput().layout.concerns].reverse() },
247
- })
248
- );
249
- expect(reordered.revisionDigest).toBe(first.revisionDigest);
250
-
251
- const optionsReordered = buildFeatureProposalV1(
252
- proposalInput({
253
- questions: proposalInput().questions.map((question) => ({
254
- ...question,
255
- options: [...question.options].reverse(),
256
- })),
257
- })
258
- );
259
- expect(optionsReordered.revisionDigest).not.toBe(first.revisionDigest);
260
- });
261
-
262
- it("uses locale-independent Unicode ordering for canonical feature records", () => {
263
- const unicodeEvidence = [
264
- {
265
- evidenceId: "unicode-ä",
266
- kind: "source" as const,
267
- authority: "source_observed" as const,
268
- ref: "src/ä.tsx:1",
269
- digest: A,
270
- },
271
- {
272
- evidenceId: "unicode-z",
273
- kind: "source" as const,
274
- authority: "source_observed" as const,
275
- ref: "src/z.tsx:1",
276
- digest: B,
277
- },
278
- ];
279
- const first = buildFeatureProposalV1(
280
- proposalInput({ evidenceRefs: [...proposalInput().evidenceRefs, ...unicodeEvidence] })
281
- );
282
- const reordered = buildFeatureProposalV1(
283
- proposalInput({
284
- evidenceRefs: [...proposalInput().evidenceRefs, ...unicodeEvidence].reverse(),
285
- })
286
- );
287
- expect(
288
- first.evidenceRefs
289
- .map((evidence) => evidence.evidenceId)
290
- .filter((evidenceId) => evidenceId.startsWith("unicode-"))
291
- ).toEqual(["unicode-z", "unicode-ä"]);
292
- expect(reordered.revisionDigest).toBe(first.revisionDigest);
293
- });
294
-
295
- it("changes identity for authority, layout, and workflow changes", () => {
296
- const first = buildFeatureProposalV1(proposalInput());
297
- expect(
298
- buildFeatureProposalV1(
299
- proposalInput({ pins: { ...proposalInput().pins, evidenceDigest: C } })
300
- ).revisionDigest
301
- ).not.toBe(first.revisionDigest);
302
- expect(
303
- buildFeatureProposalV1(
304
- proposalInput({
305
- layout: {
306
- concerns: proposalInput().layout.concerns.map((row) =>
307
- row.concern === "child_gap"
308
- ? { ...row, ownerLayer: "pattern" as const, ownerRef: "cloud.settings-row" }
309
- : row
310
- ),
311
- },
312
- })
313
- ).revisionDigest
314
- ).not.toBe(first.revisionDigest);
315
- expect(
316
- buildFeatureProposalV1(
317
- proposalInput({ planId: featurePlanIdFromEntropy("ffeeddccbbaa99887766554433221100") })
318
- ).revisionDigest
319
- ).not.toBe(first.revisionDigest);
320
- });
321
-
322
- it("rejects unknown fields, dangling evidence, invalid gaps, and duplicate layout owners", () => {
323
- expect(() =>
324
- featureProposalV1Schema.parse({ ...buildFeatureProposalV1(proposalInput()), extra: true })
325
- ).toThrow();
326
- expect(() =>
327
- buildFeatureProposalV1(
328
- proposalInput({ placement: { ...proposalInput().placement, evidenceRefIds: ["missing"] } })
329
- )
330
- ).toThrow();
331
- expect(() =>
332
- buildFeatureProposalV1(
333
- proposalInput({
334
- componentUses: [
335
- {
336
- ...proposalInput().componentUses[0],
337
- props: { label: "x".repeat(33 * 1024) },
338
- },
339
- ],
340
- })
341
- )
342
- ).toThrow();
343
- expect(() =>
344
- buildFeatureProposalV1(
345
- proposalInput({
346
- gaps: [{ ...proposalInput().gaps[0], disposition: "invent" as never }],
347
- })
348
- )
349
- ).toThrow();
350
- expect(() =>
351
- buildFeatureProposalV1(
352
- proposalInput({
353
- layout: {
354
- concerns: [
355
- ...proposalInput().layout.concerns,
356
- { ...proposalInput().layout.concerns[0], ownerRef: "FeaturePage" },
357
- ],
358
- },
359
- })
360
- )
361
- ).toThrow();
362
- });
363
- });
364
-
365
- describe("ApprovedFeatureManifestV1", () => {
366
- it("pins approved choices and excludes approvedAt from semantic identity", () => {
367
- const manifest = buildApprovedFeatureManifestV1(manifestInput());
368
- expect(manifest.revisionDigest).toBe(
369
- "2b9eb1d40d853cd47412d811526eaaba6aaaa7cb855778d08fa1d4acd930bcaa"
370
- );
371
- expect(parseApprovedFeatureManifestV1(manifest)).toEqual(manifest);
372
- const laterTimestamp = buildApprovedFeatureManifestV1(
373
- manifestInput({
374
- approval: { ...manifestInput().approval, approvedAt: "2026-09-02T12:00:00.000Z" },
375
- })
376
- );
377
- expect(laterTimestamp.revisionDigest).toBe(manifest.revisionDigest);
378
- const differentChoice = buildApprovedFeatureManifestV1(
379
- manifestInput({ decisions: [{ decisionId: "save-model", selectedOptionId: "section" }] })
380
- );
381
- expect(differentChoice.revisionDigest).not.toBe(manifest.revisionDigest);
382
- });
383
-
384
- it("blocks required unresolved/proposed gaps and dangling override decisions", () => {
385
- expect(() =>
386
- buildApprovedFeatureManifestV1(
387
- manifestInput({
388
- gaps: [
389
- {
390
- ...manifestInput().gaps[0],
391
- required: true,
392
- disposition: "unresolved",
393
- },
394
- ],
395
- })
396
- )
397
- ).toThrow();
398
- expect(() =>
399
- buildApprovedFeatureManifestV1(
400
- manifestInput({
401
- gaps: [
402
- {
403
- ...manifestInput().gaps[0],
404
- required: true,
405
- disposition: "proposed_contract_addition",
406
- proposedContractCandidateId: "candidate-field-row",
407
- },
408
- ],
409
- })
410
- )
411
- ).toThrow();
412
- expect(() =>
413
- buildApprovedFeatureManifestV1(
414
- manifestInput({
415
- layout: {
416
- concerns: manifestInput().layout.concerns.map((row) =>
417
- row.concern === "child_gap"
418
- ? { ...row, childAction: "override" as const, overrideDecisionId: "missing" }
419
- : row
420
- ),
421
- },
422
- })
423
- )
424
- ).toThrow();
425
- });
426
-
427
- it("allows compatible re-grounding to a newer source and exact analysis plan", () => {
428
- const advanced = buildApprovedFeatureManifestV1(
429
- manifestInput({
430
- validatedGrounding: {
431
- sourceCommitId: "next-head-sha",
432
- analysisPlanDigest: C,
433
- contextSliceDigest: manifestInput().pins.grounding.contextSliceDigest,
434
- },
435
- })
436
- );
437
- expect(advanced.validatedGrounding).toMatchObject({
438
- sourceCommitId: "next-head-sha",
439
- analysisPlanDigest: C,
440
- });
441
- expect(() =>
442
- buildApprovedFeatureManifestV1(
443
- manifestInput({
444
- validatedGrounding: {
445
- ...manifestInput().validatedGrounding,
446
- contextSliceDigest: D,
447
- },
448
- })
449
- )
450
- ).toThrow(/material context/u);
451
- });
452
-
453
- it.each(["observed", "candidate", "deprecated"] as const)(
454
- "prevents a %s pattern from becoming an approved reusable claim",
455
- (status) => {
456
- expect(() =>
457
- buildApprovedFeatureManifestV1(
458
- manifestInput({
459
- patternUses: [
460
- {
461
- ...manifestInput().patternUses[0],
462
- status,
463
- disposition: "reuse",
464
- },
465
- ],
466
- })
467
- )
468
- ).toThrow(/may be reused/u);
469
- }
470
- );
471
-
472
- it.each(["approved_guidance", "contract_canonical"] as const)(
473
- "allows a %s pattern to be reused",
474
- (status) => {
475
- expect(
476
- buildApprovedFeatureManifestV1(
477
- manifestInput({
478
- patternUses: [{ ...manifestInput().patternUses[0], status, disposition: "reuse" }],
479
- })
480
- ).patternUses[0]
481
- ).toMatchObject({ status, disposition: "reuse" });
482
- }
483
- );
484
-
485
- it("validates the bounded agent projection", () => {
486
- const manifest = buildApprovedFeatureManifestV1(manifestInput());
487
- const context = {
488
- schemaVersion: 1 as const,
489
- planId: manifest.planId,
490
- manifestDigest: manifest.revisionDigest,
491
- pins: manifest.pins,
492
- validatedGrounding: manifest.validatedGrounding,
493
- target: manifest.placement,
494
- componentUses: manifest.componentUses,
495
- tokenUses: manifest.tokenUses,
496
- patternUses: manifest.patternUses,
497
- layout: manifest.layout,
498
- responsive: manifest.responsive,
499
- scenarios: manifest.scenarios,
500
- acceptedGaps: manifest.gaps,
501
- acceptance: manifest.acceptance,
502
- decisions: manifest.decisions,
503
- relevantFiles: ["apps/cloud/src/settings.tsx"],
504
- commands: ["pnpm test"],
505
- };
506
- expect(agentFeatureContextV1Schema.parse(context)).toEqual(context);
507
- expect(() =>
508
- agentFeatureContextV1Schema.parse({
509
- ...context,
510
- commands: ["x".repeat(70 * 1024)],
511
- })
512
- ).toThrow();
513
- expect(() =>
514
- agentFeatureContextV1Schema.parse({
515
- ...context,
516
- relevantFiles: ["/tmp/customer-source.tsx"],
517
- })
518
- ).toThrow();
519
- });
520
-
521
- it("keeps the stored manifest validator strict", () => {
522
- expect(() =>
523
- approvedFeatureManifestV1Schema.parse({
524
- ...buildApprovedFeatureManifestV1(manifestInput()),
525
- actorEmail: "not-in-authority-record@example.com",
526
- })
527
- ).toThrow();
528
- });
529
- });
@@ -1,65 +0,0 @@
1
- export {
2
- AGENT_FEATURE_CONTEXT_MAX_BYTES_V1,
3
- FEATURE_PLAN_MAX_ACCEPTANCE_ITEMS_V1,
4
- FEATURE_PLAN_MAX_DECISIONS_V1,
5
- FEATURE_PLAN_MAX_EVIDENCE_REFS_V1,
6
- FEATURE_PLAN_MAX_GAPS_V1,
7
- FEATURE_PLAN_MAX_SCENARIOS_V1,
8
- FEATURE_PLAN_MAX_TEXT_BYTES_V1,
9
- FEATURE_PLAN_MAX_USES_PER_KIND_V1,
10
- approvedFeatureManifestInputV1Schema,
11
- approvedFeatureManifestV1Schema,
12
- capabilityGapV1Schema,
13
- componentUseDecisionV1Schema,
14
- decisionRequestV1Schema,
15
- decisionResolutionV1Schema,
16
- evidenceRefV1Schema,
17
- featureAcceptanceItemV1Schema,
18
- featurePinsV1Schema,
19
- featureProposalInputV1Schema,
20
- featureProposalV1Schema,
21
- gapDispositionV1Schema,
22
- layoutConcernV1Schema,
23
- layoutOwnershipV1Schema,
24
- patternStatusV1Schema,
25
- patternUseDecisionV1Schema,
26
- placementDecisionV1Schema,
27
- responsiveDecisionV1Schema,
28
- scenarioRequirementV1Schema,
29
- tokenUseDecisionV1Schema,
30
- } from "./types.js";
31
- export type {
32
- AgentFeatureContextV1,
33
- ApprovedFeatureManifestInputV1,
34
- ApprovedFeatureManifestV1,
35
- CapabilityGapV1,
36
- ComponentUseDecisionV1,
37
- DecisionRequestV1,
38
- DecisionResolutionV1,
39
- EvidenceRefV1,
40
- FeatureAcceptanceItemV1,
41
- FeaturePinsV1,
42
- FeatureProposalInputV1,
43
- FeatureProposalV1,
44
- GapDispositionV1,
45
- LayoutConcernV1,
46
- LayoutOwnershipV1,
47
- PatternStatusV1,
48
- PatternUseDecisionV1,
49
- PlacementDecisionV1,
50
- ResponsiveDecisionV1,
51
- ScenarioRequirementV1,
52
- TokenUseDecisionV1,
53
- } from "./types.js";
54
- export {
55
- agentFeatureContextV1Schema,
56
- approvedFeatureManifestRevisionDigestV1,
57
- buildApprovedFeatureManifestV1,
58
- buildFeatureProposalV1,
59
- featureProposalRevisionDigestV1,
60
- normalizeApprovedFeatureManifestInputV1,
61
- normalizeFeatureProposalInputV1,
62
- parseAgentFeatureContextV1,
63
- parseApprovedFeatureManifestV1,
64
- parseFeatureProposalV1,
65
- } from "./digest.js";