@usefragments/core 1.4.0 → 1.5.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 (72) hide show
  1. package/dist/{chunk-CBSNXFOD.js → chunk-AOG4FTV6.js} +146 -6
  2. package/dist/chunk-AOG4FTV6.js.map +1 -0
  3. package/dist/{chunk-57QDBEHQ.js → chunk-ZHS52OT4.js} +3 -9
  4. package/dist/chunk-ZHS52OT4.js.map +1 -0
  5. package/dist/codes/index.d.ts +2 -2
  6. package/dist/codes/index.js +2 -2
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/generate/index.d.ts +1 -1
  9. package/dist/{governance-DxdJV6lx.d.ts → governance-B88uR3Zq.d.ts} +229 -1
  10. package/dist/{index-0lmh0Lbo.d.ts → index-DbkPE46t.d.ts} +48 -8
  11. package/dist/index.d.ts +779 -158
  12. package/dist/index.js +674 -127
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/registry.d.ts +18 -18
  16. package/dist/schemas/index.d.ts +1 -1
  17. package/dist/schemas/index.js +1 -1
  18. package/dist/test-utils.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/agent-format.test.ts +1 -0
  21. package/src/canonical-bridge.ts +46 -0
  22. package/src/canonical-direction.ts +118 -0
  23. package/src/codes/codes.ts +18 -0
  24. package/src/conform.ts +5 -0
  25. package/src/contract/preimage.test.ts +40 -0
  26. package/src/contract/preimage.ts +20 -0
  27. package/src/effective-governance-inputs.test.ts +52 -0
  28. package/src/effective-governance-inputs.ts +106 -0
  29. package/src/facts/builders.ts +70 -2
  30. package/src/facts/compile.ts +20 -2
  31. package/src/facts/fact-index.ts +4 -0
  32. package/src/facts/facts.test.ts +30 -0
  33. package/src/facts/index.ts +6 -0
  34. package/src/facts/types.ts +86 -7
  35. package/src/governance-integrity.test.ts +22 -0
  36. package/src/governance-integrity.ts +10 -4
  37. package/src/governance.test.ts +93 -0
  38. package/src/governance.ts +76 -0
  39. package/src/identity/classify.test.ts +185 -0
  40. package/src/identity/classify.ts +257 -0
  41. package/src/identity/component-key.test.ts +32 -0
  42. package/src/identity/component-key.ts +35 -0
  43. package/src/identity/usage.ts +39 -0
  44. package/src/index.ts +48 -0
  45. package/src/rules/__tests__/fix-emission-invariant.test.ts +35 -4
  46. package/src/rules/components-prefer-library.test.ts +151 -0
  47. package/src/rules/components-prefer-library.ts +46 -4
  48. package/src/rules/components-shadow-component.test.ts +145 -0
  49. package/src/rules/components-shadow-component.ts +81 -0
  50. package/src/rules/fix-availability.ts +2 -0
  51. package/src/rules/index.ts +14 -0
  52. package/src/rules/jsx-preferred-import-path.ts +45 -9
  53. package/src/rules/rules.test.ts +202 -6
  54. package/src/rules/spacing-resolution.ts +2 -2
  55. package/src/rules/styles-no-raw-color.test.ts +6 -1
  56. package/src/rules/styles-no-raw-color.ts +3 -2
  57. package/src/rules/styles-no-raw-dimensions.ts +5 -7
  58. package/src/rules/styles-no-raw-spacing.ts +5 -2
  59. package/src/rules/styles-no-raw-typography.ts +7 -4
  60. package/src/rules/taxonomy.test.ts +19 -1
  61. package/src/rules/tiers.ts +9 -5
  62. package/src/rules/token-candidates.ts +36 -0
  63. package/src/rules/tokens-require-dual-fallback.ts +2 -1
  64. package/src/rules/tokens-upstream-drift.test.ts +111 -0
  65. package/src/rules/tokens-upstream-drift.ts +57 -0
  66. package/src/rules/utils.ts +22 -14
  67. package/src/schema.ts +28 -0
  68. package/src/schemas/index.ts +5 -8
  69. package/src/token-types.ts +71 -1
  70. package/src/types.ts +18 -0
  71. package/dist/chunk-57QDBEHQ.js.map +0 -1
  72. package/dist/chunk-CBSNXFOD.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { ComponentType, ReactNode, JSX } from 'react';
2
- export { F as FragmentDefinition, a as FragmentDefinitionV2 } from './governance-DxdJV6lx.js';
2
+ export { F as FragmentDefinition, a as FragmentDefinitionV2 } from './governance-B88uR3Zq.js';
3
3
  import 'zod';
4
4
  import './topology/index.js';
5
5
  import './types-xJ2xyp_G.js';
@@ -216,7 +216,7 @@ declare const registryComponentSchema: z.ZodObject<{
216
216
  componentId: string;
217
217
  name: string;
218
218
  publicRef: string;
219
- tier: "custom" | "utility" | "composition" | "core";
219
+ tier: "core" | "custom" | "utility" | "composition";
220
220
  examples: unknown[];
221
221
  files: {
222
222
  path: string;
@@ -264,7 +264,7 @@ declare const registryComponentSchema: z.ZodObject<{
264
264
  provenance?: Record<string, unknown> | undefined;
265
265
  category?: string | undefined;
266
266
  contract?: unknown;
267
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
267
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
268
268
  examples?: unknown[] | undefined;
269
269
  styleFiles?: string[] | undefined;
270
270
  tokenDependencies?: string[] | undefined;
@@ -498,7 +498,7 @@ declare const registryManifestDraftSchema: z.ZodObject<{
498
498
  componentId: string;
499
499
  name: string;
500
500
  publicRef: string;
501
- tier: "custom" | "utility" | "composition" | "core";
501
+ tier: "core" | "custom" | "utility" | "composition";
502
502
  examples: unknown[];
503
503
  files: {
504
504
  path: string;
@@ -546,7 +546,7 @@ declare const registryManifestDraftSchema: z.ZodObject<{
546
546
  provenance?: Record<string, unknown> | undefined;
547
547
  category?: string | undefined;
548
548
  contract?: unknown;
549
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
549
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
550
550
  examples?: unknown[] | undefined;
551
551
  styleFiles?: string[] | undefined;
552
552
  tokenDependencies?: string[] | undefined;
@@ -611,7 +611,7 @@ declare const registryManifestDraftSchema: z.ZodObject<{
611
611
  componentId: string;
612
612
  name: string;
613
613
  publicRef: string;
614
- tier: "custom" | "utility" | "composition" | "core";
614
+ tier: "core" | "custom" | "utility" | "composition";
615
615
  examples: unknown[];
616
616
  files: {
617
617
  path: string;
@@ -713,7 +713,7 @@ declare const registryManifestDraftSchema: z.ZodObject<{
713
713
  provenance?: Record<string, unknown> | undefined;
714
714
  category?: string | undefined;
715
715
  contract?: unknown;
716
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
716
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
717
717
  examples?: unknown[] | undefined;
718
718
  styleFiles?: string[] | undefined;
719
719
  tokenDependencies?: string[] | undefined;
@@ -971,7 +971,7 @@ declare const registryManifestSchema: z.ZodObject<{
971
971
  componentId: string;
972
972
  name: string;
973
973
  publicRef: string;
974
- tier: "custom" | "utility" | "composition" | "core";
974
+ tier: "core" | "custom" | "utility" | "composition";
975
975
  examples: unknown[];
976
976
  files: {
977
977
  path: string;
@@ -1019,7 +1019,7 @@ declare const registryManifestSchema: z.ZodObject<{
1019
1019
  provenance?: Record<string, unknown> | undefined;
1020
1020
  category?: string | undefined;
1021
1021
  contract?: unknown;
1022
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
1022
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
1023
1023
  examples?: unknown[] | undefined;
1024
1024
  styleFiles?: string[] | undefined;
1025
1025
  tokenDependencies?: string[] | undefined;
@@ -1086,7 +1086,7 @@ declare const registryManifestSchema: z.ZodObject<{
1086
1086
  componentId: string;
1087
1087
  name: string;
1088
1088
  publicRef: string;
1089
- tier: "custom" | "utility" | "composition" | "core";
1089
+ tier: "core" | "custom" | "utility" | "composition";
1090
1090
  examples: unknown[];
1091
1091
  files: {
1092
1092
  path: string;
@@ -1189,7 +1189,7 @@ declare const registryManifestSchema: z.ZodObject<{
1189
1189
  provenance?: Record<string, unknown> | undefined;
1190
1190
  category?: string | undefined;
1191
1191
  contract?: unknown;
1192
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
1192
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
1193
1193
  examples?: unknown[] | undefined;
1194
1194
  styleFiles?: string[] | undefined;
1195
1195
  tokenDependencies?: string[] | undefined;
@@ -1450,7 +1450,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1450
1450
  componentId: string;
1451
1451
  name: string;
1452
1452
  publicRef: string;
1453
- tier: "custom" | "utility" | "composition" | "core";
1453
+ tier: "core" | "custom" | "utility" | "composition";
1454
1454
  examples: unknown[];
1455
1455
  files: {
1456
1456
  path: string;
@@ -1498,7 +1498,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1498
1498
  provenance?: Record<string, unknown> | undefined;
1499
1499
  category?: string | undefined;
1500
1500
  contract?: unknown;
1501
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
1501
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
1502
1502
  examples?: unknown[] | undefined;
1503
1503
  styleFiles?: string[] | undefined;
1504
1504
  tokenDependencies?: string[] | undefined;
@@ -1565,7 +1565,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1565
1565
  componentId: string;
1566
1566
  name: string;
1567
1567
  publicRef: string;
1568
- tier: "custom" | "utility" | "composition" | "core";
1568
+ tier: "core" | "custom" | "utility" | "composition";
1569
1569
  examples: unknown[];
1570
1570
  files: {
1571
1571
  path: string;
@@ -1668,7 +1668,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1668
1668
  provenance?: Record<string, unknown> | undefined;
1669
1669
  category?: string | undefined;
1670
1670
  contract?: unknown;
1671
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
1671
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
1672
1672
  examples?: unknown[] | undefined;
1673
1673
  styleFiles?: string[] | undefined;
1674
1674
  tokenDependencies?: string[] | undefined;
@@ -1773,7 +1773,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1773
1773
  componentId: string;
1774
1774
  name: string;
1775
1775
  publicRef: string;
1776
- tier: "custom" | "utility" | "composition" | "core";
1776
+ tier: "core" | "custom" | "utility" | "composition";
1777
1777
  examples: unknown[];
1778
1778
  files: {
1779
1779
  path: string;
@@ -1886,7 +1886,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1886
1886
  provenance?: Record<string, unknown> | undefined;
1887
1887
  category?: string | undefined;
1888
1888
  contract?: unknown;
1889
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
1889
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
1890
1890
  examples?: unknown[] | undefined;
1891
1891
  styleFiles?: string[] | undefined;
1892
1892
  tokenDependencies?: string[] | undefined;
@@ -1979,7 +1979,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
1979
1979
  componentId: string;
1980
1980
  name: string;
1981
1981
  publicRef: string;
1982
- tier: "custom" | "utility" | "composition" | "core";
1982
+ tier: "core" | "custom" | "utility" | "composition";
1983
1983
  examples: unknown[];
1984
1984
  files: {
1985
1985
  path: string;
@@ -2092,7 +2092,7 @@ declare const registryArtifactSchema: z.ZodEffects<z.ZodObject<{
2092
2092
  provenance?: Record<string, unknown> | undefined;
2093
2093
  category?: string | undefined;
2094
2094
  contract?: unknown;
2095
- tier?: "custom" | "utility" | "composition" | "core" | undefined;
2095
+ tier?: "core" | "custom" | "utility" | "composition" | undefined;
2096
2096
  examples?: unknown[] | undefined;
2097
2097
  styleFiles?: string[] | undefined;
2098
2098
  tokenDependencies?: string[] | undefined;
@@ -1,3 +1,3 @@
1
1
  import 'zod';
2
2
  export { LegacySeverityLevel, Severity, SeverityLevel, legacySeverityLevelSchema, severityLevelSchema, severitySchema } from '../severity.js';
3
- export { b as AGENT_FORMAT_SCHEMA_VERSION, c as AgentErrorEnvelope, A as AgentFormat, d as AgentOutput, Q as FactEvidence, R as FactLocation, F as Finding, a as FindingFix, f as FindingReplaceClassTokenFix, g as FindingReplaceComponentFix, h as FindingReplaceImportFix, i as FindingReplacePropValueFix, j as FindingReplaceStyleValueFix, k as Fix, G as GovernanceVerdict, l as GovernanceVerdictMetadata, S as SuppressionDirective, V as ValidatorResult, n as Violation, o as agentErrorEnvelopeSchema, T as agentFindingSchema, p as agentFormatSchema, q as agentIntegritySchema, r as agentOutputSchema, U as agentPlanSchema, t as factEvidenceSchema, u as factLocationSchema, v as findingFixSchema, w as findingReplaceClassTokenFixSchema, x as findingReplaceComponentFixSchema, y as findingReplaceImportFixSchema, z as findingReplacePropValueFixSchema, B as findingReplaceStyleValueFixSchema, C as findingSchema, D as fixSchema, H as governanceVerdictMetadataSchema, J as governanceVerdictSchema, K as normalizeFinding, L as normalizeSeverity, M as normalizeViolation, N as suppressionDirectiveSchema, O as validatorResultSchema, P as violationSchema } from '../index-0lmh0Lbo.js';
3
+ export { b as AGENT_FORMAT_SCHEMA_VERSION, c as AgentErrorEnvelope, A as AgentFormat, d as AgentOutput, Q as FactEvidence, R as FactLocation, F as Finding, a as FindingFix, f as FindingReplaceClassTokenFix, g as FindingReplaceComponentFix, h as FindingReplaceImportFix, i as FindingReplacePropValueFix, j as FindingReplaceStyleValueFix, k as Fix, G as GovernanceVerdict, l as GovernanceVerdictMetadata, S as SuppressionDirective, V as ValidatorResult, n as Violation, o as agentErrorEnvelopeSchema, T as agentFindingSchema, p as agentFormatSchema, q as agentIntegritySchema, r as agentOutputSchema, U as agentPlanSchema, t as factEvidenceSchema, u as factLocationSchema, v as findingFixSchema, w as findingReplaceClassTokenFixSchema, x as findingReplaceComponentFixSchema, y as findingReplaceImportFixSchema, z as findingReplacePropValueFixSchema, B as findingReplaceStyleValueFixSchema, C as findingSchema, D as fixSchema, H as governanceVerdictMetadataSchema, J as governanceVerdictSchema, K as normalizeFinding, L as normalizeSeverity, M as normalizeViolation, N as suppressionDirectiveSchema, O as validatorResultSchema, P as violationSchema } from '../index-DbkPE46t.js';
@@ -24,7 +24,7 @@ import {
24
24
  suppressionDirectiveSchema,
25
25
  validatorResultSchema,
26
26
  violationSchema
27
- } from "../chunk-57QDBEHQ.js";
27
+ } from "../chunk-ZHS52OT4.js";
28
28
  import {
29
29
  legacySeverityLevelSchema,
30
30
  severityLevelSchema,
@@ -1,4 +1,4 @@
1
- import { C as CompiledBlock, b as CompiledFragment } from './governance-DxdJV6lx.js';
1
+ import { C as CompiledBlock, b as CompiledFragment } from './governance-B88uR3Zq.js';
2
2
  import 'zod';
3
3
  import 'react';
4
4
  import './topology/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@usefragments/core",
3
- "version": "1.4.0",
3
+ "version": "1.5.1",
4
4
  "license": "MIT",
5
5
  "description": "Core types, schemas, and runtime API for Fragments component definitions",
6
6
  "author": "Conan McNicholl",
@@ -232,6 +232,7 @@ describe("buildAgentFormat", () => {
232
232
  scope: { method: "full", gitOk: true },
233
233
  engineVersion: `sha256:${"b".repeat(64)}`,
234
234
  scopeHash: "c".repeat(64),
235
+ inputsHash: "e".repeat(64),
235
236
  inputFilesHash: "d".repeat(64),
236
237
  },
237
238
  verdict: { exitCode: 0, reasons: [], gatingFindingCount: 0 },
@@ -0,0 +1,46 @@
1
+ import type { ContractCanonicalMappingInput } from "./contract/preimage.js";
2
+ import type { CanonicalBridgeV1 } from "./governance.js";
3
+ import { ownedImportMatchesRoot } from "./package-identity-match.js";
4
+
5
+ export function canonicalBridgeUnderlyingKey(input: {
6
+ packageName: string;
7
+ exportName: string;
8
+ }): string {
9
+ return `${input.packageName}\u0000${input.exportName}`;
10
+ }
11
+
12
+ export function canonicalBridgeContractMappings(
13
+ bridges: readonly CanonicalBridgeV1[] | undefined
14
+ ): ContractCanonicalMappingInput[] {
15
+ return (bridges ?? []).map((bridge) => ({
16
+ component: `${bridge.underlying.packageName}#${bridge.underlying.exportName}`,
17
+ canonical: bridge.local.componentKey,
18
+ status: "confirmed",
19
+ importPath: bridge.local.moduleSpecifier,
20
+ bridge: {
21
+ underlyingPackageName: bridge.underlying.packageName,
22
+ underlyingExportName: bridge.underlying.exportName,
23
+ localComponentKey: bridge.local.componentKey,
24
+ localExportName: bridge.local.exportName,
25
+ implementationFiles: [...bridge.local.implementationFiles],
26
+ decisionSource: bridge.decision.source,
27
+ },
28
+ }));
29
+ }
30
+
31
+ export function canonicalBridgeMatchesUnderlyingImport(
32
+ bridge: CanonicalBridgeV1,
33
+ source: string,
34
+ imported: string
35
+ ): boolean {
36
+ if (!ownedImportMatchesRoot(source, bridge.underlying.packageName)) return false;
37
+ if (source === bridge.underlying.packageName) {
38
+ return imported === bridge.underlying.exportName;
39
+ }
40
+ const subpath = source.slice(bridge.underlying.packageName.length + 1);
41
+ const leaf = subpath.split("/").at(-1);
42
+ return (
43
+ leaf === bridge.underlying.exportName &&
44
+ (imported === "default" || imported === bridge.underlying.exportName)
45
+ );
46
+ }
@@ -0,0 +1,118 @@
1
+ import type { CanonicalSource } from "./governance.js";
2
+ import type { FactIndex } from "./facts/index.js";
3
+ import { ownedImportMatchesRoot } from "./package-identity-match.js";
4
+
5
+ export interface CanonicalDirectionConflict {
6
+ packageName: string;
7
+ exportName: string;
8
+ wrapperComponentKey: string;
9
+ wrapperFile: string;
10
+ wrapperExportName: string;
11
+ state: "unresolved" | "sanctioned";
12
+ }
13
+
14
+ /**
15
+ * Project exact, source-backed conflicts between an exported local wrapper and
16
+ * an npm canonical export. Name similarity, prop overlap, and mixed render
17
+ * trees are deliberately insufficient: this projection can deactivate policy.
18
+ */
19
+ export function projectCanonicalDirectionConflicts(
20
+ ix: FactIndex,
21
+ canonicalSources: readonly CanonicalSource[]
22
+ ): CanonicalDirectionConflict[] {
23
+ const npmSources = canonicalSources.filter(
24
+ (source): source is Extract<CanonicalSource, { kind: "npm" }> => source.kind === "npm"
25
+ );
26
+ if (npmSources.length === 0) return [];
27
+
28
+ const identityByComponent = new Map(
29
+ ix.byKind("component_identity").map((identity) => [identity.componentKey, identity])
30
+ );
31
+ const conflicts: CanonicalDirectionConflict[] = [];
32
+
33
+ for (const definition of ix.byKind("component_definition")) {
34
+ if (!definition.exported || definition.renderRoot.resolution !== "canonical") continue;
35
+ const target = npmTarget(definition.renderRoot.canonical, npmSources);
36
+ if (!target) continue;
37
+
38
+ const identity = identityByComponent.get(definition.componentKey);
39
+ if (
40
+ identity?.decisionId &&
41
+ decisionTargetsExport(identity.canonicalTarget, target.exportName)
42
+ ) {
43
+ if (identity.state === "shadow") continue;
44
+ if (identity.state === "variant" && identity.confidence === "confirmed") {
45
+ conflicts.push({
46
+ ...target,
47
+ wrapperComponentKey: definition.componentKey,
48
+ wrapperFile: definition.file,
49
+ wrapperExportName: definition.exportName,
50
+ state: "sanctioned",
51
+ });
52
+ continue;
53
+ }
54
+ }
55
+
56
+ conflicts.push({
57
+ ...target,
58
+ wrapperComponentKey: definition.componentKey,
59
+ wrapperFile: definition.file,
60
+ wrapperExportName: definition.exportName,
61
+ state: "unresolved",
62
+ });
63
+ }
64
+
65
+ return conflicts.sort(
66
+ (left, right) =>
67
+ left.packageName.localeCompare(right.packageName) ||
68
+ left.exportName.localeCompare(right.exportName) ||
69
+ left.wrapperComponentKey.localeCompare(right.wrapperComponentKey)
70
+ );
71
+ }
72
+
73
+ export function canonicalDirectionConflictsTarget(
74
+ conflicts: readonly CanonicalDirectionConflict[],
75
+ importPath: string | undefined,
76
+ exportName: string
77
+ ): boolean {
78
+ if (!importPath) return false;
79
+ return conflicts.some(
80
+ (conflict) =>
81
+ ownedImportMatchesRoot(importPath, conflict.packageName) && conflict.exportName === exportName
82
+ );
83
+ }
84
+
85
+ function npmTarget(
86
+ canonicalTarget: string,
87
+ sources: readonly Extract<CanonicalSource, { kind: "npm" }>[]
88
+ ): { packageName: string; exportName: string } | null {
89
+ const separator = canonicalTarget.lastIndexOf("#");
90
+ if (separator <= 0 || separator === canonicalTarget.length - 1) return null;
91
+ const importPath = canonicalTarget.slice(0, separator);
92
+ const imported = canonicalTarget
93
+ .slice(separator + 1)
94
+ .split(".")
95
+ .at(-1);
96
+ if (!imported) return null;
97
+
98
+ for (const source of sources) {
99
+ if (!ownedImportMatchesRoot(importPath, source.specifier)) continue;
100
+ const subpath = packageSubpath(importPath, source.specifier);
101
+ const exportName =
102
+ imported === "default" ? subpath.split("/").filter(Boolean).at(-1) : imported;
103
+ if (!exportName) continue;
104
+ if (source.exclude?.includes(exportName)) continue;
105
+ if (source.include && !source.include.includes(exportName)) continue;
106
+ return { packageName: source.specifier, exportName };
107
+ }
108
+ return null;
109
+ }
110
+
111
+ function packageSubpath(importPath: string, packageName: string): string {
112
+ return importPath === packageName ? "" : importPath.slice(packageName.length + 1);
113
+ }
114
+
115
+ function decisionTargetsExport(target: string | undefined, exportName: string): boolean {
116
+ if (!target) return true;
117
+ return target === exportName || target.split("#").at(-1)?.split(".").at(-1) === exportName;
118
+ }
@@ -108,6 +108,15 @@ export const CODES = [
108
108
  fixAvailable: false,
109
109
  evidenceRequired: true,
110
110
  }),
111
+ ruleCode({
112
+ code: "FUI1007",
113
+ ruleId: "components/shadow-component",
114
+ category: "canonical-usage",
115
+ defaultSeverity: "moderate",
116
+ title: "Component shadows a canonical primitive",
117
+ lifecycle: "experimental",
118
+ evidenceRequired: true,
119
+ }),
111
120
  code({
112
121
  code: "FUI2001",
113
122
  ruleId: "tokens/require-design-tokens",
@@ -255,6 +264,15 @@ export const CODES = [
255
264
  lifecycle: "experimental",
256
265
  evidenceRequired: true,
257
266
  }),
267
+ ruleCode({
268
+ code: "FUI2017",
269
+ ruleId: "tokens/upstream-drift",
270
+ category: "tokens",
271
+ defaultSeverity: "moderate",
272
+ title: "Local token differs from its declared upstream source",
273
+ lifecycle: "experimental",
274
+ evidenceRequired: true,
275
+ }),
258
276
  ruleCode({
259
277
  code: "FUI3001",
260
278
  ruleId: "a11y/required-accessible-name",
package/src/conform.ts CHANGED
@@ -20,6 +20,11 @@ export interface ConformInput {
20
20
  filename?: string;
21
21
  /** deterministic (default) applies safe edits; none returns findings only. */
22
22
  apply?: "deterministic" | "none";
23
+ /** Explicit request to consolidate a known shadow into its canonical root. */
24
+ shadowConsolidation?: {
25
+ shadow: string;
26
+ canonicalTarget?: string;
27
+ };
23
28
  }
24
29
 
25
30
  export interface ConformLocation {
@@ -162,6 +162,46 @@ describe("§3 proposed mappings are not identity", () => {
162
162
  });
163
163
  });
164
164
 
165
+ describe("confirmed canonical bridge identity", () => {
166
+ const bridgeMapping = {
167
+ component: "@mui/material#Button",
168
+ canonical: "src/components/Button.tsx#Button",
169
+ status: "confirmed" as const,
170
+ importPath: "@/components",
171
+ bridge: {
172
+ underlyingPackageName: "@mui/material",
173
+ underlyingExportName: "Button",
174
+ localComponentKey: "src/components/Button.tsx#Button",
175
+ localExportName: "Button",
176
+ implementationFiles: ["src/components/Button.tsx"],
177
+ decisionSource: "authored" as const,
178
+ },
179
+ };
180
+
181
+ it("moves only canonicalMap when enforcement changes, not when provenance changes", () => {
182
+ const base = projectContractPreimage({ canonicalMappings: [bridgeMapping] });
183
+ const changedEnforcement = projectContractPreimage({
184
+ canonicalMappings: [
185
+ {
186
+ ...bridgeMapping,
187
+ bridge: { ...bridgeMapping.bridge, implementationFiles: ["src/ui/Button.tsx"] },
188
+ },
189
+ ],
190
+ });
191
+ const changedProvenance = projectContractPreimage({
192
+ canonicalMappings: [
193
+ {
194
+ ...bridgeMapping,
195
+ bridge: { ...bridgeMapping.bridge, decisionSource: "migration" as const },
196
+ },
197
+ ],
198
+ });
199
+
200
+ expect(diffContractDomains(base, changedEnforcement)).toEqual(["canonicalMap"]);
201
+ expect(changedProvenance).toEqual(base);
202
+ });
203
+ });
204
+
165
205
  describe("§4 domain diff names what moved", () => {
166
206
  it("confirming a mapping + adding a composition rule changes canonicalMap and policy", () => {
167
207
  const preimageBase = projectContractPreimage(catalogFixtureA);
@@ -87,6 +87,15 @@ export interface ContractCanonicalMappingInput {
87
87
  status: CanonicalMappingStatus;
88
88
  /** Local/offline canonical import target, when the mapping is config-authored. */
89
89
  importPath?: string;
90
+ /** Confirmed package-to-local relation metadata. Absent for legacy mappings. */
91
+ bridge?: {
92
+ underlyingPackageName: string;
93
+ underlyingExportName: string;
94
+ localComponentKey: string;
95
+ localExportName: string;
96
+ implementationFiles: readonly string[];
97
+ decisionSource: "authored" | "migration";
98
+ };
90
99
  /** Explicit raw element or semantic resolutions covered by a local declaration. */
91
100
  resolves?: readonly {
92
101
  tag: string;
@@ -253,6 +262,17 @@ function projectCanonicalMap(
253
262
  importPath: mapping.importPath
254
263
  ? projectV1OwnedImportIdentity(mapping.importPath)
255
264
  : undefined,
265
+ bridge: mapping.bridge
266
+ ? {
267
+ underlyingPackageName: projectV1OwnedImportIdentity(
268
+ mapping.bridge.underlyingPackageName
269
+ ),
270
+ underlyingExportName: mapping.bridge.underlyingExportName,
271
+ localComponentKey: projectV1OwnedComponentId(mapping.bridge.localComponentKey),
272
+ localExportName: mapping.bridge.localExportName,
273
+ implementationFiles: [...mapping.bridge.implementationFiles].sort(),
274
+ }
275
+ : undefined,
256
276
  resolves: mapping.resolves ? sortCanonical(mapping.resolves) : undefined,
257
277
  propMapping: mapping.propMapping
258
278
  ? sortCanonical(
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { compileEffectiveGovernanceInputs } from "./effective-governance-inputs.js";
4
+
5
+ function fixture() {
6
+ return compileEffectiveGovernanceInputs({
7
+ policySource: "config",
8
+ configPath: "./fragments.config.ts",
9
+ configDigest: "a".repeat(64),
10
+ policy: {
11
+ canonicalBridges: [
12
+ {
13
+ underlying: { packageName: "@mui/material", exportName: "Button" },
14
+ local: {
15
+ componentKey: "src/components/Button.tsx#Button",
16
+ moduleSpecifier: "@/components/Button",
17
+ exportName: "Button",
18
+ implementationFiles: ["src/components/Button.tsx"],
19
+ },
20
+ decision: { state: "confirmed", source: "authored" },
21
+ },
22
+ ],
23
+ },
24
+ componentDefinitionFiles: ["src/components/Button.tsx"],
25
+ tokens: { declared: [], loadedDefinitions: 0, diagnostics: [] },
26
+ eligibility: { include: ["src/**/*.tsx"], exclude: ["src/**/*.stories.tsx"] },
27
+ });
28
+ }
29
+
30
+ describe("compileEffectiveGovernanceInputs", () => {
31
+ it("normalizes operational inputs into a separate stable identity", () => {
32
+ const first = fixture();
33
+ const second = fixture();
34
+ expect(first).toEqual(second);
35
+ expect(first.inputsHash).toMatch(/^[a-f0-9]{64}$/u);
36
+ expect(first.config.path).toBe("fragments.config.ts");
37
+ });
38
+
39
+ it("moves inputsHash for config/source state without changing contract identity here", () => {
40
+ const first = fixture();
41
+ const next = compileEffectiveGovernanceInputs({
42
+ policySource: first.policySource,
43
+ configPath: first.config.path,
44
+ configDigest: "b".repeat(64),
45
+ policy: { canonicalBridges: first.components.canonicalBridges },
46
+ componentDefinitionFiles: first.components.definitionFiles,
47
+ tokens: { declared: [], loadedDefinitions: 0, diagnostics: [] },
48
+ eligibility: first.eligibility,
49
+ });
50
+ expect(next.inputsHash).not.toBe(first.inputsHash);
51
+ });
52
+ });
@@ -0,0 +1,106 @@
1
+ import { canonicalPreimage, contractHash } from "./contract/hash.js";
2
+ import type { GovernanceConfig } from "./governance.js";
3
+ import type { TokenCatalog } from "./token-types.js";
4
+
5
+ export const EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA = "effective-governance-inputs:v1" as const;
6
+
7
+ export interface EffectiveGovernanceInputsSourceState {
8
+ declared: readonly string[];
9
+ loadedDefinitions: number;
10
+ diagnostics: readonly string[];
11
+ catalog?: TokenCatalog;
12
+ }
13
+
14
+ export interface CompileEffectiveGovernanceInputsOptions {
15
+ policySource: "config" | "preset" | "fallback" | "none";
16
+ configPath: string | null;
17
+ configDigest: string | null;
18
+ policy: GovernanceConfig | undefined;
19
+ componentDefinitionFiles: readonly string[];
20
+ tokens: EffectiveGovernanceInputsSourceState;
21
+ eligibility: {
22
+ include: readonly string[];
23
+ exclude: readonly string[];
24
+ };
25
+ }
26
+
27
+ export interface EffectiveGovernanceInputs {
28
+ schema: typeof EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA;
29
+ policySource: CompileEffectiveGovernanceInputsOptions["policySource"];
30
+ config: { path: string | null; digest: string | null };
31
+ components: {
32
+ canonicalSources: NonNullable<GovernanceConfig["canonicalSources"]>;
33
+ canonicalBridges: NonNullable<GovernanceConfig["canonicalBridges"]>;
34
+ definitionFiles: string[];
35
+ };
36
+ tokens: {
37
+ declared: string[];
38
+ loadedDefinitions: number;
39
+ diagnostics: string[];
40
+ catalog?: TokenCatalog;
41
+ };
42
+ eligibility: { include: string[]; exclude: string[] };
43
+ inputsHash: string;
44
+ }
45
+
46
+ /**
47
+ * Compile one deterministic, post-authority account of the operational inputs
48
+ * available to governance consumers. This is deliberately distinct from FCID
49
+ * (enforced contract identity) and scopeHash (the files selected for one Run).
50
+ */
51
+ export function compileEffectiveGovernanceInputs(
52
+ options: CompileEffectiveGovernanceInputsOptions
53
+ ): EffectiveGovernanceInputs {
54
+ const projection = {
55
+ schema: EFFECTIVE_GOVERNANCE_INPUTS_SCHEMA,
56
+ policySource: options.policySource,
57
+ config: {
58
+ path: options.configPath ? normalizePath(options.configPath) : null,
59
+ digest: options.configDigest,
60
+ },
61
+ components: {
62
+ canonicalSources: sortRecords(options.policy?.canonicalSources ?? []),
63
+ canonicalBridges: sortRecords(options.policy?.canonicalBridges ?? []),
64
+ definitionFiles: normalizePaths(options.componentDefinitionFiles),
65
+ },
66
+ tokens: {
67
+ declared: normalizePaths(options.tokens.declared),
68
+ loadedDefinitions: options.tokens.loadedDefinitions,
69
+ diagnostics: [...options.tokens.diagnostics].sort(),
70
+ ...(options.tokens.catalog
71
+ ? {
72
+ catalog: {
73
+ ...options.tokens.catalog,
74
+ configuredSources: normalizePaths(options.tokens.catalog.configuredSources),
75
+ parsedSources: normalizePaths(options.tokens.catalog.parsedSources),
76
+ definitions: sortRecords(options.tokens.catalog.definitions),
77
+ diagnostics: sortRecords(options.tokens.catalog.diagnostics),
78
+ references: sortRecords(options.tokens.catalog.references),
79
+ ...(options.tokens.catalog.sourceStates
80
+ ? { sourceStates: sortRecords(options.tokens.catalog.sourceStates) }
81
+ : {}),
82
+ },
83
+ }
84
+ : {}),
85
+ },
86
+ eligibility: {
87
+ include: normalizePaths(options.eligibility.include),
88
+ exclude: normalizePaths(options.eligibility.exclude),
89
+ },
90
+ };
91
+ return { ...projection, inputsHash: contractHash(projection) };
92
+ }
93
+
94
+ function normalizePaths(values: readonly string[]): string[] {
95
+ return [...new Set(values.map(normalizePath))].sort();
96
+ }
97
+
98
+ function normalizePath(value: string): string {
99
+ return value.replace(/\\/gu, "/").replace(/^\.\//u, "");
100
+ }
101
+
102
+ function sortRecords<T>(values: readonly T[]): T[] {
103
+ return [...values].sort((left, right) =>
104
+ canonicalPreimage(left).localeCompare(canonicalPreimage(right))
105
+ );
106
+ }