@usefragments/core 1.11.0 → 2.0.1

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 (47) hide show
  1. package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
  2. package/dist/chunk-MZ2FS7U4.js.map +1 -0
  3. package/dist/{chunk-RPSEABY3.js → chunk-QPOKQ5H6.js} +30 -34
  4. package/dist/chunk-QPOKQ5H6.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 +145 -6026
  11. package/dist/index.js +298 -860
  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/canonical-source.ts +41 -0
  24. package/src/codes/__tests__/codes.test.ts +4 -0
  25. package/src/codes/codes.ts +10 -0
  26. package/src/domain-ids.test.ts +9 -26
  27. package/src/domain-ids.ts +0 -45
  28. package/src/evaluation/evaluate.ts +17 -7
  29. package/src/evaluation/evaluation-v2-receipt-v1.test.ts +20 -8
  30. package/src/evaluation/index.ts +1 -1
  31. package/src/facts/builders.ts +4 -0
  32. package/src/facts/fact-index.ts +4 -0
  33. package/src/facts/facts.test.ts +70 -0
  34. package/src/facts/types.ts +4 -0
  35. package/src/governance.ts +18 -0
  36. package/src/identity/usage.test.ts +64 -0
  37. package/src/identity/usage.ts +7 -1
  38. package/src/index.ts +15 -79
  39. package/src/rules/components-prefer-library.ts +110 -33
  40. package/src/topology/resolve-area.ts +1 -1
  41. package/src/types.ts +0 -3
  42. package/dist/chunk-3LLRNCPX.js.map +0 -1
  43. package/dist/chunk-RPSEABY3.js.map +0 -1
  44. package/src/feature-plan/digest.ts +0 -217
  45. package/src/feature-plan/feature-plan-v1.test.ts +0 -529
  46. package/src/feature-plan/index.ts +0 -65
  47. package/src/feature-plan/types.ts +0 -628
@@ -108,4 +108,4 @@ function resolveArea(repoRelPath, topology) {
108
108
  export {
109
109
  resolveArea
110
110
  };
111
- //# sourceMappingURL=chunk-3LLRNCPX.js.map
111
+ //# sourceMappingURL=chunk-MZ2FS7U4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/topology/resolve-area.ts"],"sourcesContent":["/**\n * Topology — map a repo-relative file path to its product area.\n *\n * This is the DRY keystone of the topology layer: the ONLY area-matching\n * implementation in the monorepo. Consumers (CLI scan, Cloud managed-editor\n * preview, cross-repo reconciliation) import it; none reimplements glob/area\n * logic. Pure and browser-safe — no Node, no parser deps.\n *\n * See `docs/fragments-v1/ARCHITECTURE.md §1`.\n */\n\nexport type AreaCriticality = \"low\" | \"medium\" | \"high\" | \"revenue\" | \"regulated\";\n\nexport interface Area {\n id: string;\n name: string;\n criticality: AreaCriticality;\n owners: string[];\n /** Globs matched against repo-relative file paths in v0. */\n files: string[];\n /** Stored for later route-topology adapters; NOT matched in v0. */\n routes?: string[];\n /** Tie-break: higher wins. Defaults to declaration order. */\n priority?: number;\n}\n\nexport interface Topology {\n /** Bump signals \"re-tag\" to consumers that cache. */\n version: number;\n /** Whether area file globs are matched relative to app.path or repo root. Defaults to \"app\". */\n base?: \"app\" | \"repo\";\n /** Array (not a keyed record) so precedence is deterministic. */\n areas: Area[];\n}\n\nexport interface AreaMatch {\n areaId: string;\n areaName: string;\n criticality: AreaCriticality;\n owners: string[];\n /** The winning pattern — surfaced in reports for transparency. */\n matchedGlob: string;\n}\n\n/**\n * Compile a minimal glob to an anchored RegExp. We intentionally do NOT depend\n * on picomatch: core stays lean, the surface we need is small, and we want\n * exact control over Next.js route-group parens (`(marketing)` is a literal\n * path segment here, not an extglob group).\n *\n * Semantics:\n * - `/**` at a segment boundary (end-of-glob or before `/`) → \"this directory\n * and everything under it\": the leading slash is optional, so\n * `src/app/checkout/**` matches `src/app/checkout` AND any descendant.\n * - a bare/leading `**` → matches across path separators (`.*`). Note a\n * leading globstar-then-slash does NOT match the root (use a lone `**`\n * for \"everything\").\n * - `*` → matches within a single segment (`[^/]*`)\n * - `?` → a single non-separator char\n * - `{a,b}` → alternation `(?:a|b)`\n * - every other regex-significant char (`.`, `(`, `)`, `+`, …) is literal\n *\n * Throws on a malformed pattern (e.g. an unbalanced `{`). Callers compile via\n * `compile()`, which catches the throw so a config typo degrades one glob to a\n * non-match instead of aborting the whole scan.\n */\nfunction globToRegExp(glob: string): RegExp {\n let out = \"\";\n let braceDepth = 0;\n let i = 0;\n while (i < glob.length) {\n const ch = glob[i];\n\n // `/**` at a segment boundary → optional slash + anything-below. Matches\n // the directory itself (`a/**` ⇒ `a`) and every descendant.\n if (\n ch === \"/\" &&\n glob[i + 1] === \"*\" &&\n glob[i + 2] === \"*\" &&\n (glob[i + 3] === undefined || glob[i + 3] === \"/\")\n ) {\n out += \"(?:/.*)?\";\n i += 3; // consume `/**`; a trailing `/` (in `a/**/b`) is handled next pass\n continue;\n }\n\n // Bare/leading globstar — crosses path separators.\n if (ch === \"*\" && glob[i + 1] === \"*\") {\n out += \".*\";\n i += 2;\n continue;\n }\n\n switch (ch) {\n case \"*\":\n out += \"[^/]*\"; // single segment\n break;\n case \"?\":\n out += \"[^/]\";\n break;\n case \"{\":\n braceDepth += 1;\n out += \"(?:\";\n break;\n case \"}\":\n if (braceDepth > 0) {\n braceDepth -= 1;\n out += \")\";\n } else {\n out += \"\\\\}\";\n }\n break;\n case \",\":\n out += braceDepth > 0 ? \"|\" : \",\";\n break;\n // Regex-significant chars kept literal (parens cover route groups).\n case \".\":\n case \"(\":\n case \")\":\n case \"+\":\n case \"^\":\n case \"$\":\n case \"|\":\n case \"[\":\n case \"]\":\n case \"\\\\\":\n out += `\\\\${ch}`;\n break;\n default:\n out += ch;\n }\n i += 1;\n }\n return new RegExp(`^${out}$`);\n}\n\ninterface CompiledArea {\n area: Area;\n matchers: { glob: string; re: RegExp }[];\n}\n\n/**\n * Cache compiled matchers per topology object. Consumers pass the same\n * `topology` for every finding in a run, so this turns N×globs recompiles into\n * one. A reloaded config is a new object → fresh entry, so `version` bumps need\n * no manual invalidation.\n */\nconst compiledCache = new WeakMap<Topology, CompiledArea[]>();\n\nfunction compile(topology: Topology): CompiledArea[] {\n const cached = compiledCache.get(topology);\n if (cached) return cached;\n\n // Order by (priority desc, declaration order). Decorate-sort-undecorate keeps\n // the sort stable across engines.\n const ordered = topology.areas\n .map((area, index) => ({ area, index }))\n .sort((a, b) => {\n const pa = a.area.priority ?? 0;\n const pb = b.area.priority ?? 0;\n if (pa !== pb) return pb - pa;\n return a.index - b.index;\n })\n .map(({ area }) => ({\n area,\n matchers: area.files.flatMap((glob) => {\n try {\n return [{ glob, re: globToRegExp(glob) }];\n } catch {\n // A malformed glob (e.g. unbalanced `{`) compiles to an invalid\n // RegExp. Skip it rather than aborting the entire scan — topology\n // config is user-authored, so one typo must not take down\n // `fragments check`. Its files simply fall through to \"Unassigned\".\n if (typeof console !== \"undefined\") {\n console.warn(\n `[topology] ignoring invalid glob in area \"${area.id}\": ${JSON.stringify(glob)}`\n );\n }\n return [];\n }\n }),\n }));\n\n compiledCache.set(topology, ordered);\n return ordered;\n}\n\n/** Normalize to a POSIX, repo-relative path before matching. */\nfunction normalizePath(repoRelPath: string): string {\n return repoRelPath.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\").replace(/^\\/+/, \"\");\n}\n\n/**\n * Resolve the product area for a repo-relative path. First match by\n * `(priority desc, declaration order)`. Returns `null` when nothing matches —\n * the caller buckets that as the explicit \"Unassigned\" area (never dropped;\n * dropping unmatched evidence would make coverage lie).\n */\nexport function resolveArea(repoRelPath: string, topology: Topology): AreaMatch | null {\n const path = normalizePath(repoRelPath);\n for (const { area, matchers } of compile(topology)) {\n for (const { glob, re } of matchers) {\n if (re.test(path)) {\n return {\n areaId: area.id,\n areaName: area.name,\n criticality: area.criticality,\n owners: area.owners,\n matchedGlob: glob,\n };\n }\n }\n }\n return null;\n}\n"],"mappings":"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","names":[]}
@@ -571,6 +571,16 @@ var CODES = [
571
571
  lifecycle: "experimental",
572
572
  fixAvailable: false,
573
573
  evidenceRequired: false
574
+ }),
575
+ code({
576
+ code: "FUI9012",
577
+ ruleId: "contract/write-not-entitled",
578
+ category: "system",
579
+ defaultSeverity: "critical",
580
+ title: "Contract write requires a paid workspace",
581
+ lifecycle: "experimental",
582
+ fixAvailable: false,
583
+ evidenceRequired: false
574
584
  })
575
585
  ];
576
586
  var byCode = new Map(
@@ -589,7 +599,6 @@ import { z as z2 } from "zod";
589
599
  // src/domain-ids.ts
590
600
  import { z } from "zod";
591
601
  var FULL_DIGEST_PATTERN = /^[0-9a-f]{64}$/u;
592
- var WORKFLOW_ENTROPY_PATTERN = /^[0-9a-f]{32}$/u;
593
602
  var digestHexStringSchema = z.string().regex(FULL_DIGEST_PATTERN, "Expected 64 lowercase hexadecimal characters");
594
603
  var digestHexSchema = digestHexStringSchema;
595
604
  var analysisPlanIdStringSchema = z.string().regex(/^apl_[0-9a-f]{64}$/u, "Invalid analysis plan ID");
@@ -598,14 +607,6 @@ var analysisPlanIdSchema = analysisPlanIdStringSchema.transform(
598
607
  );
599
608
  var evaluationReceiptIdSchema = z.string().regex(/^evr_[0-9a-f]{64}$/u, "Invalid evaluation receipt ID").transform((value) => value);
600
609
  var evaluationReceiptIdStringSchema = z.string().regex(/^evr_[0-9a-f]{64}$/u, "Invalid evaluation receipt ID");
601
- var featurePlanIdStringSchema = z.string().regex(/^fpl_[0-9a-f]{32}$/u, "Invalid feature plan ID");
602
- var featurePlanIdSchema = featurePlanIdStringSchema.transform(
603
- (value) => value
604
- );
605
- var featureRevisionIdStringSchema = z.string().regex(/^fpr_[0-9a-f]{64}$/u, "Invalid feature revision ID");
606
- var featureRevisionIdSchema = featureRevisionIdStringSchema.transform(
607
- (value) => value
608
- );
609
610
  function parseDigestHex(value) {
610
611
  return digestHexSchema.parse(value);
611
612
  }
@@ -618,27 +619,12 @@ function evaluationReceiptIdFromDigest(digest) {
618
619
  function mintEvaluationReceiptId(resultDigest) {
619
620
  return evaluationReceiptIdFromDigest(resultDigest);
620
621
  }
621
- function featureRevisionIdFromDigest(digest) {
622
- return featureRevisionIdSchema.parse(`fpr_${parseDigestHex(digest)}`);
623
- }
624
- function featurePlanIdFromEntropy(entropyHex) {
625
- if (!WORKFLOW_ENTROPY_PATTERN.test(entropyHex)) {
626
- throw new TypeError("Feature plan entropy must be exactly 128-bit lowercase hexadecimal");
627
- }
628
- return featurePlanIdSchema.parse(`fpl_${entropyHex}`);
629
- }
630
622
  function parseAnalysisPlanId(value) {
631
623
  return analysisPlanIdSchema.parse(value);
632
624
  }
633
625
  function parseEvaluationReceiptId(value) {
634
626
  return evaluationReceiptIdSchema.parse(value);
635
627
  }
636
- function parseFeaturePlanId(value) {
637
- return featurePlanIdSchema.parse(value);
638
- }
639
- function parseFeatureRevisionId(value) {
640
- return featureRevisionIdSchema.parse(value);
641
- }
642
628
 
643
629
  // src/facts/integrity.ts
644
630
  var CANONICAL_FACT_MAX_CONFLICTS_V1 = 1e3;
@@ -875,6 +861,7 @@ function makeComponentDefinitionFact(input) {
875
861
  componentKey: input.componentKey,
876
862
  renderRoot: input.renderRoot,
877
863
  propSurface: [...input.propSurface],
864
+ ...input.location ? { location: { ...input.location } } : {},
878
865
  ...input.stylingChannel ? { stylingChannel: { ...input.stylingChannel } } : {}
879
866
  };
880
867
  }
@@ -1218,7 +1205,8 @@ function makeUsageComponentFact(input) {
1218
1205
  }),
1219
1206
  kind: "usage_component",
1220
1207
  nodeId: input.nodeId,
1221
- componentId: input.componentId
1208
+ componentId: input.componentId,
1209
+ ...input.definitionKey ? { definitionKey: input.definitionKey } : {}
1222
1210
  };
1223
1211
  }
1224
1212
  function makeUsageImportFact(input) {
@@ -1438,6 +1426,10 @@ function logicalFactForComparison(fact) {
1438
1426
  const { location: _location, sourceNames: _sourceNames, ...logicalFact } = fact;
1439
1427
  return logicalFact;
1440
1428
  }
1429
+ if (fact.kind === "component_definition") {
1430
+ const { location: _location, ...logicalFact } = fact;
1431
+ return { ...logicalFact, componentId: logicalComponentId(fact.componentId) };
1432
+ }
1441
1433
  if (fact.kind === "jsx_import_path_preferred") {
1442
1434
  return {
1443
1435
  ...fact,
@@ -1900,6 +1892,17 @@ var componentGovernanceRecordSchema = z4.discriminatedUnion("kind", [
1900
1892
  a11yRequireNameRecordSchema
1901
1893
  ]);
1902
1894
  var componentGovernanceRecordsSchema = z4.array(componentGovernanceRecordSchema);
1895
+ function parseComponentGovernancePolicyJson(policyJson) {
1896
+ try {
1897
+ const parsed = JSON.parse(policyJson);
1898
+ const record = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
1899
+ const candidate = Array.isArray(parsed) ? parsed : record?.governance ?? record?.rules;
1900
+ const result = componentGovernanceRecordsSchema.safeParse(candidate);
1901
+ return result.success ? result.data : null;
1902
+ } catch {
1903
+ return null;
1904
+ }
1905
+ }
1903
1906
  var componentPolicyMatchSchema = z4.object({
1904
1907
  componentKey: z4.string().min(1).optional(),
1905
1908
  componentId: z4.string().min(1).optional(),
@@ -3061,20 +3064,12 @@ export {
3061
3064
  analysisPlanIdSchema,
3062
3065
  evaluationReceiptIdSchema,
3063
3066
  evaluationReceiptIdStringSchema,
3064
- featurePlanIdStringSchema,
3065
- featurePlanIdSchema,
3066
- featureRevisionIdStringSchema,
3067
- featureRevisionIdSchema,
3068
3067
  parseDigestHex,
3069
3068
  analysisPlanIdFromDigest,
3070
3069
  evaluationReceiptIdFromDigest,
3071
3070
  mintEvaluationReceiptId,
3072
- featureRevisionIdFromDigest,
3073
- featurePlanIdFromEntropy,
3074
3071
  parseAnalysisPlanId,
3075
3072
  parseEvaluationReceiptId,
3076
- parseFeaturePlanId,
3077
- parseFeatureRevisionId,
3078
3073
  CANONICAL_FACT_MAX_CONFLICTS_V1,
3079
3074
  CANONICAL_FACT_MAX_VALUES_PER_CONFLICT_V1,
3080
3075
  CANONICAL_FACT_MAX_PROVENANCE_PER_VALUE_V1,
@@ -3147,6 +3142,7 @@ export {
3147
3142
  globalGovernanceRecordSchema,
3148
3143
  componentGovernanceRecordSchema,
3149
3144
  componentGovernanceRecordsSchema,
3145
+ parseComponentGovernancePolicyJson,
3150
3146
  canonicalBridgeV1Schema,
3151
3147
  governanceConfigSchema,
3152
3148
  markPresetSourcedRules,
@@ -3190,4 +3186,4 @@ export {
3190
3186
  normalizeFinding,
3191
3187
  normalizeViolation
3192
3188
  };
3193
- //# sourceMappingURL=chunk-RPSEABY3.js.map
3189
+ //# sourceMappingURL=chunk-QPOKQ5H6.js.map