@usefragments/core 1.0.0 → 1.3.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 (59) hide show
  1. package/dist/{chunk-R3X7UBHU.js → chunk-57QDBEHQ.js} +101 -6
  2. package/dist/chunk-57QDBEHQ.js.map +1 -0
  3. package/dist/{chunk-243QYRUF.js → chunk-MLRDNSSA.js} +88 -47
  4. package/dist/chunk-MLRDNSSA.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-BOa3KyiJ.d.ts → governance-DYSirwJu.d.ts} +22 -1
  10. package/dist/index-h_yWj15D.d.ts +6981 -0
  11. package/dist/index.d.ts +113 -97
  12. package/dist/index.js +387 -113
  13. package/dist/index.js.map +1 -1
  14. package/dist/react-types.d.ts +1 -1
  15. package/dist/registry.d.ts +36 -36
  16. package/dist/schemas/index.d.ts +3 -2584
  17. package/dist/schemas/index.js +9 -1
  18. package/dist/test-utils.d.ts +1 -1
  19. package/package.json +1 -1
  20. package/src/agent-format.test.ts +144 -2
  21. package/src/agent-format.ts +34 -7
  22. package/src/codes/__tests__/codes.test.ts +57 -7
  23. package/src/codes/codes.ts +44 -42
  24. package/src/conform-prove.ts +4 -8
  25. package/src/contract/index.ts +3 -0
  26. package/src/contract/preimage.test.ts +50 -0
  27. package/src/contract/preimage.ts +56 -0
  28. package/src/evidence.ts +27 -0
  29. package/src/facts/builders.ts +4 -0
  30. package/src/facts/fact-index.ts +25 -1
  31. package/src/facts/facts.test.ts +49 -0
  32. package/src/facts/types.ts +3 -0
  33. package/src/governance-integrity.test.ts +2 -0
  34. package/src/governance-integrity.ts +7 -1
  35. package/src/index.ts +13 -0
  36. package/src/rules/__tests__/fix-emission-invariant.test.ts +174 -0
  37. package/src/rules/__tests__/tokens-require-dual-fallback.test.ts +90 -0
  38. package/src/rules/components-prefer-library.ts +165 -7
  39. package/src/rules/emit-gate.test.ts +40 -2
  40. package/src/rules/emit-gate.ts +21 -10
  41. package/src/rules/finding.ts +3 -0
  42. package/src/rules/fix-availability.ts +33 -0
  43. package/src/rules/rules.test.ts +180 -24
  44. package/src/rules/spacing-resolution.ts +5 -8
  45. package/src/rules/styles-no-raw-color.ts +32 -23
  46. package/src/rules/styles-no-raw-dimensions.ts +31 -12
  47. package/src/rules/styles-no-raw-spacing.ts +33 -11
  48. package/src/rules/styles-no-raw-typography.ts +37 -15
  49. package/src/rules/taxonomy.test.ts +19 -3
  50. package/src/rules/tiers.ts +1 -1
  51. package/src/rules/tokens-require-dual-fallback.ts +54 -17
  52. package/src/rules/utils.ts +51 -0
  53. package/src/schemas/index.ts +106 -4
  54. package/src/token-types.ts +6 -0
  55. package/src/tokens/__tests__/source-names.test.ts +42 -0
  56. package/src/tokens/design-token-parser.test.ts +24 -5
  57. package/src/tokens/design-token-parser.ts +40 -8
  58. package/dist/chunk-243QYRUF.js.map +0 -1
  59. package/dist/chunk-R3X7UBHU.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import {
2
- ComponentGraphEngine
3
- } from "./chunk-SH4KPIYH.js";
4
1
  import {
5
2
  generateContext
6
3
  } from "./chunk-X34IA4LR.js";
4
+ import {
5
+ ComponentGraphEngine
6
+ } from "./chunk-SH4KPIYH.js";
7
7
  import {
8
8
  resolveArea
9
9
  } from "./chunk-3LLRNCPX.js";
@@ -109,9 +109,13 @@ import {
109
109
  ownedImportsEqual,
110
110
  resolveComponentGovernance,
111
111
  scaleGovernanceRecordSchema
112
- } from "./chunk-243QYRUF.js";
112
+ } from "./chunk-MLRDNSSA.js";
113
113
  import {
114
+ AGENT_FORMAT_SCHEMA_VERSION,
115
+ agentErrorEnvelopeSchema,
114
116
  agentFormatSchema,
117
+ agentIntegritySchema,
118
+ agentOutputSchema,
115
119
  factEvidenceSchema,
116
120
  factLocationSchema,
117
121
  findingFixSchema,
@@ -130,7 +134,7 @@ import {
130
134
  suppressionDirectiveSchema,
131
135
  validatorResultSchema,
132
136
  violationSchema
133
- } from "./chunk-R3X7UBHU.js";
137
+ } from "./chunk-57QDBEHQ.js";
134
138
  import {
135
139
  OWNED_PACKAGE_IDENTITY_EPOCH,
136
140
  OWNED_PACKAGE_IDENTITY_SOURCE_SHA256,
@@ -234,6 +238,18 @@ var DEFAULTS = {
234
238
  port: 6006
235
239
  };
236
240
 
241
+ // src/evidence.ts
242
+ var EVIDENCE_ORDER = [
243
+ "none",
244
+ "runtime_advisory",
245
+ "provenance_bound",
246
+ "source_backed",
247
+ "receipt_backed"
248
+ ];
249
+ function canBlock(grade) {
250
+ return EVIDENCE_ORDER.indexOf(grade) >= EVIDENCE_ORDER.indexOf("source_backed");
251
+ }
252
+
237
253
  // src/conform-prove.ts
238
254
  var PROVE_MAX_PASSES = 8;
239
255
  var PROVE_DEFAULT_MAX_PASSES = 4;
@@ -382,7 +398,9 @@ async function proveCompliant(input, callbacks, options = {}) {
382
398
 
383
399
  // src/agent-format.ts
384
400
  function buildAgentFormat(input) {
385
- const score = scoreFindings(input.findings);
401
+ const zeroRulesLoaded = input.integrity.rulesLoaded === 0;
402
+ const integrityFailed = !input.integrity.healthy || input.integrity.inert;
403
+ const score = integrityFailed ? 0 : scoreFindings(input.findings);
386
404
  const visibleFindings = uniqueFindings(input.findings).slice(0, input.maxFindings ?? 50);
387
405
  const agentFindings = visibleFindings.map((finding) => ({
388
406
  code: finding.code,
@@ -393,15 +411,15 @@ function buildAgentFormat(input) {
393
411
  severity: finding.severity,
394
412
  level: severityLevel(finding.severity),
395
413
  message: finding.message,
414
+ evidenceGrade: finding.evidenceGrade,
396
415
  evidence: input.includeEvidence ? finding.evidence : [],
397
416
  plan: planForFinding(finding)
398
417
  }));
399
418
  const hasErrors = input.findings.some((finding) => severityLevel(finding.severity) === "error");
400
- return {
401
- schemaVersion: "typestyle.agent.v1",
402
- passed: (input.passed ?? true) && !hasErrors,
403
- score,
404
- summary: agentSummary(input.findings.length, input.filesScanned, score),
419
+ const shared = {
420
+ schemaVersion: AGENT_FORMAT_SCHEMA_VERSION,
421
+ summary: zeroRulesLoaded ? "indeterminate; zero_rules_loaded" : integrityFailed ? `failed with score ${score}; ${input.integrity.reasons.join("; ") || "governance integrity is unhealthy"}` : agentSummary(input.findings.length, input.filesScanned, score),
422
+ integrity: input.integrity,
405
423
  fix_first: agentFindings.filter((finding) => finding.plan.confidence >= 0.8),
406
424
  remaining: agentFindings.filter((finding) => finding.plan.confidence < 0.8),
407
425
  suppression_template: {
@@ -422,7 +440,23 @@ function buildAgentFormat(input) {
422
440
  ...suppression.expiresOn ? { expiresOn: suppression.expiresOn } : {},
423
441
  scope: "line"
424
442
  }))
425
- } : {}
443
+ } : {},
444
+ ...input.provenance ? { provenance: input.provenance } : {},
445
+ ...input.verdict ? { verdict: input.verdict } : {}
446
+ };
447
+ if (zeroRulesLoaded) {
448
+ return {
449
+ ...shared,
450
+ integrity: { ...input.integrity, rulesLoaded: 0 },
451
+ passed: false,
452
+ status: "indeterminate",
453
+ reasons: ["zero_rules_loaded"]
454
+ };
455
+ }
456
+ return {
457
+ ...shared,
458
+ passed: !integrityFailed && (input.passed ?? true) && !hasErrors,
459
+ score
426
460
  };
427
461
  }
428
462
  function uniqueFindings(findings) {
@@ -3320,19 +3354,35 @@ function parsedOutputToDesignTokens(output, filePath) {
3320
3354
  return tokens;
3321
3355
  }
3322
3356
  function scssOutputToDesignTokens(output, filePath) {
3323
- const names = /* @__PURE__ */ new Set();
3324
- const tokens = [];
3357
+ const tokensByName = /* @__PURE__ */ new Map();
3325
3358
  for (const cat of Object.values(output.categories)) {
3326
3359
  for (const t of cat) {
3327
- const cssName = t.name.startsWith("$") ? `--${t.name.slice(1)}` : t.name;
3328
- if (!cssName.startsWith("--") || names.has(cssName)) continue;
3329
- names.add(cssName);
3330
- tokens.push(designTokenFromParsed(cssName, t, filePath));
3360
+ const isScssVariable = t.name.startsWith("$");
3361
+ const cssName = isScssVariable ? `--${t.name.slice(1)}` : t.name;
3362
+ if (!cssName.startsWith("--")) continue;
3363
+ const existing = tokensByName.get(cssName);
3364
+ if (!existing) {
3365
+ tokensByName.set(cssName, {
3366
+ parsed: t,
3367
+ referenceFormat: isScssVariable ? "scss-var" : "css-var",
3368
+ sourceNames: /* @__PURE__ */ new Set([t.name])
3369
+ });
3370
+ } else {
3371
+ existing.sourceNames.add(t.name);
3372
+ if (isScssVariable) {
3373
+ existing.parsed = t;
3374
+ existing.referenceFormat = "scss-var";
3375
+ }
3376
+ }
3331
3377
  }
3332
3378
  }
3333
- return tokens;
3379
+ return [...tokensByName].map(
3380
+ ([name, token]) => designTokenFromParsed(name, token.parsed, filePath, token.referenceFormat, [
3381
+ ...token.sourceNames
3382
+ ])
3383
+ );
3334
3384
  }
3335
- function designTokenFromParsed(name, parsed, filePath) {
3385
+ function designTokenFromParsed(name, parsed, filePath, referenceFormat, sourceNames) {
3336
3386
  return {
3337
3387
  name,
3338
3388
  rawValue: parsed.value ?? "",
@@ -3345,6 +3395,8 @@ function designTokenFromParsed(name, parsed, filePath) {
3345
3395
  level: 1,
3346
3396
  referenceChain: [],
3347
3397
  sourceFile: filePath,
3398
+ referenceFormat,
3399
+ ...sourceNames?.length ? { sourceNames } : {},
3348
3400
  theme: "default",
3349
3401
  selector: ":root",
3350
3402
  description: parsed.description
@@ -4283,12 +4335,37 @@ function makeFinding(input) {
4283
4335
  fingerprint,
4284
4336
  location: input.location,
4285
4337
  evidence: input.evidence,
4338
+ evidenceGrade: input.evidenceGrade ?? "source_backed",
4286
4339
  fix: input.fix,
4287
4340
  attributes: input.attributes
4288
4341
  });
4289
4342
  }
4290
4343
 
4291
4344
  // src/rules/utils.ts
4345
+ function emitFix(fix, proof, ix) {
4346
+ const reasons = [];
4347
+ if (proof.editKind !== fix.kind) reasons.push("edit kind does not match proof");
4348
+ if (!proof.valuePreserving) reasons.push("value-preservation proof missing");
4349
+ if (proof.symbols.length === 0) reasons.push("token-symbol proof missing");
4350
+ const missingSymbols = proof.symbols.filter((symbol) => !ix.tokens.symbolExists(symbol));
4351
+ if (missingSymbols.length > 0) {
4352
+ reasons.push(`absent token symbol: ${missingSymbols.join(", ")}`);
4353
+ }
4354
+ const inapplicableSymbols = proof.symbols.filter((symbol) => {
4355
+ const definition = ix.tokens.definitionForSymbol(symbol);
4356
+ return definition !== void 0 && !isApplicableTokenReference(definition.referenceFormat);
4357
+ });
4358
+ if (inapplicableSymbols.length > 0) {
4359
+ reasons.push(`token reference is not directly applicable: ${inapplicableSymbols.join(", ")}`);
4360
+ }
4361
+ return {
4362
+ fix: { ...fix, deterministic: reasons.length === 0 },
4363
+ ...reasons.length > 0 ? { downgradeReason: reasons.join("; ") } : {}
4364
+ };
4365
+ }
4366
+ function tokenSymbolsInText(value) {
4367
+ return [...new Set(value.match(/\$[A-Za-z0-9_-]+|--[A-Za-z0-9_-]+/g) ?? [])];
4368
+ }
4292
4369
  function indexPropsByNodeId(ix) {
4293
4370
  const out = /* @__PURE__ */ new Map();
4294
4371
  for (const fact of ix.byKind("usage_prop_resolved")) {
@@ -4367,13 +4444,6 @@ function readUsageNode(ix, id) {
4367
4444
  const fact = ix.get(id);
4368
4445
  return fact && fact.kind === "usage_node" ? fact : void 0;
4369
4446
  }
4370
- function cssVariableNames(tokens) {
4371
- const names = /* @__PURE__ */ new Set();
4372
- for (const token of tokens) {
4373
- names.add(token.name.startsWith("--") ? token.name : `--${token.name}`);
4374
- }
4375
- return names;
4376
- }
4377
4447
  function contractPrefixFamilies(names) {
4378
4448
  const families = /* @__PURE__ */ new Set();
4379
4449
  for (const name of names) {
@@ -4994,7 +5064,8 @@ function ruleComponentsPreferLibrary(ix) {
4994
5064
  if (node.element.includes(".")) continue;
4995
5065
  const props = propsByNode.get(node.id) ?? [];
4996
5066
  const textChildren = textByNode.get(node.id) ?? [];
4997
- const imported = importsByFileAndLocal.get(node.file)?.get(node.element);
5067
+ const importsForFile = importsByFileAndLocal.get(node.file);
5068
+ const imported = importsForFile?.get(node.element);
4998
5069
  const nodeInputType = node.element === "input" ? readStaticStringProp(props, "type") : void 0;
4999
5070
  const mapped = findMapping(node, nodeInputType, mappings);
5000
5071
  if (mapped && imported && isMappedCanonicalImport(imported, mapped)) continue;
@@ -5005,9 +5076,11 @@ function ruleComponentsPreferLibrary(ix) {
5005
5076
  const suggestedComponent2 = mapped.name;
5006
5077
  const suggestedImport2 = importPath ?? "the canonical library";
5007
5078
  const propMapping = mapped.propMapping ?? [];
5079
+ const propCompatibility = assessMappedPropCompatibility(props, propMapping);
5008
5080
  const roleMatch = isRoleMatch(node, mapped);
5009
5081
  const precisionTier2 = roleMatch ? "role-reimpl" : "exact-html";
5010
5082
  if (imported && importPath && !ownedImportsEqual(imported.source, importPath) && node.element === suggestedComponent2) {
5083
+ if (!isLocalExactComponentImportRedirectCandidate(imported, suggestedComponent2)) continue;
5011
5084
  const key = `${imported.file}\0${imported.local}\0${imported.source}\0${importPath}`;
5012
5085
  if (seenImportFixes.has(key)) continue;
5013
5086
  seenImportFixes.add(key);
@@ -5015,8 +5088,8 @@ function ruleComponentsPreferLibrary(ix) {
5015
5088
  makeFinding({
5016
5089
  ruleId: RULE_ID2,
5017
5090
  ruleVersion: RULE_VERSION2,
5018
- severity: policy.severity ?? "warn",
5019
- message: `<${node.element}> should import from ${importPath}.`,
5091
+ severity: capAdvisorySeverity(policy.severity),
5092
+ message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5020
5093
  location: imported.location,
5021
5094
  evidence: ix.evidence([node.id, imported.id, policy.id]),
5022
5095
  fingerprintIdentity: {
@@ -5030,14 +5103,16 @@ function ruleComponentsPreferLibrary(ix) {
5030
5103
  title: `Replace import path with "${importPath}"`,
5031
5104
  from: imported.source,
5032
5105
  to: importPath,
5033
- deterministic: true
5106
+ deterministic: false
5034
5107
  },
5035
5108
  attributes: {
5036
5109
  rawValue: imported.source,
5037
5110
  suggestedComponent: suggestedComponent2,
5038
5111
  suggestedImport: importPath,
5039
5112
  canonical: mapped.canonical,
5040
- confidence: mapped.confidence
5113
+ confidence: mapped.confidence,
5114
+ advisory: true,
5115
+ contractProof: "missing"
5041
5116
  }
5042
5117
  })
5043
5118
  );
@@ -5067,7 +5142,11 @@ function ruleComponentsPreferLibrary(ix) {
5067
5142
  title: `Replace <${node.element}> with <${suggestedComponent2}>`,
5068
5143
  from: node.element,
5069
5144
  to: suggestedComponent2,
5070
- deterministic: true
5145
+ // The fact IR cannot prove that it observed every JSX
5146
+ // attribute shape (notably dynamic inline styles). The
5147
+ // conform AST preflight may promote this guided fix only
5148
+ // after it proves the complete prop surface directly.
5149
+ deterministic: false
5071
5150
  }
5072
5151
  } : {},
5073
5152
  attributes: {
@@ -5077,8 +5156,12 @@ function ruleComponentsPreferLibrary(ix) {
5077
5156
  canonical: mapped.canonical,
5078
5157
  confidence: mapped.confidence,
5079
5158
  propMapping,
5159
+ propCompatibility: propCompatibility.complete ? "observed-complete" : "incomplete",
5160
+ ...mapped.conformStatus === "confirmed" ? { canonicalMappingConfirmed: true } : {},
5161
+ ...propCompatibility.unprovenProps.length > 0 ? { unprovenProps: propCompatibility.unprovenProps } : {},
5080
5162
  precisionTier: precisionTier2,
5081
- ...roleMatch ? { matchedRole: node.role } : {}
5163
+ ...roleMatch ? { matchedRole: node.role } : {},
5164
+ ...isRawHtmlAdvisoryTier(precisionTier2) ? { advisory: true } : {}
5082
5165
  }
5083
5166
  })
5084
5167
  );
@@ -5121,6 +5204,47 @@ function ruleComponentsPreferLibrary(ix) {
5121
5204
  const suggestion = findSuggestionSource(suggestedComponent, sources);
5122
5205
  if (!suggestion) continue;
5123
5206
  if (!builtIn && !shouldSuggest(node, imported)) continue;
5207
+ if (node.element === suggestedComponent) {
5208
+ const importPath = canonicalImportPath(suggestion);
5209
+ if (!imported || !importPath || !isLocalExactComponentImportRedirectCandidate(imported, suggestedComponent)) {
5210
+ continue;
5211
+ }
5212
+ const key = `${imported.file}\0${imported.local}\0${imported.source}\0${importPath}`;
5213
+ if (seenImportFixes.has(key)) continue;
5214
+ seenImportFixes.add(key);
5215
+ findings.push(
5216
+ makeFinding({
5217
+ ruleId: RULE_ID2,
5218
+ ruleVersion: RULE_VERSION2,
5219
+ severity: capAdvisorySeverity(policy.severity),
5220
+ message: `<${node.element}> may need to import from ${importPath}; verify that the local component contract is compatible first.`,
5221
+ location: imported.location,
5222
+ evidence: ix.evidence([node.id, imported.id, policy.id]),
5223
+ fingerprintIdentity: {
5224
+ file: imported.file,
5225
+ local: imported.local,
5226
+ from: imported.source,
5227
+ to: importPath
5228
+ },
5229
+ fix: {
5230
+ kind: "replaceImport",
5231
+ title: `Replace import path with "${importPath}"`,
5232
+ from: imported.source,
5233
+ to: importPath,
5234
+ deterministic: false
5235
+ },
5236
+ attributes: {
5237
+ rawValue: imported.source,
5238
+ suggestedComponent,
5239
+ suggestedImport: importPath,
5240
+ suggestedImportSourceKind: suggestion.kind,
5241
+ advisory: true,
5242
+ contractProof: "missing"
5243
+ }
5244
+ })
5245
+ );
5246
+ continue;
5247
+ }
5124
5248
  if (suggestion.kind === "directory") {
5125
5249
  if (node.element === suggestedComponent) continue;
5126
5250
  if (!builtIn) continue;
@@ -5163,7 +5287,10 @@ function ruleComponentsPreferLibrary(ix) {
5163
5287
  title: `Replace ${rawElement} with <${suggestedComponent}>`,
5164
5288
  from: node.element,
5165
5289
  to: suggestedComponent,
5166
- deterministic: true
5290
+ // A canonical source proves that the target exists, not that
5291
+ // this native element's complete prop contract is compatible.
5292
+ // Only explicit canonical mappings carry that proof.
5293
+ deterministic: false
5167
5294
  }
5168
5295
  } : {},
5169
5296
  attributes: {
@@ -5172,6 +5299,7 @@ function ruleComponentsPreferLibrary(ix) {
5172
5299
  suggestedImport,
5173
5300
  suggestedImportSourceKind: suggestion.kind,
5174
5301
  precisionTier,
5302
+ propCompatibility: "unknown",
5175
5303
  ...builtIn?.matchedRole ? { matchedRole: builtIn.matchedRole } : {},
5176
5304
  ...builtIn?.inputType ? { matchedInputType: builtIn.inputType } : {},
5177
5305
  ...isRawHtmlAdvisoryTier(precisionTier) ? { advisory: true } : {}
@@ -5288,6 +5416,39 @@ function shouldSuggest(node, imported) {
5288
5416
  if (!/^[A-Z]/.test(node.element)) return false;
5289
5417
  return imported !== void 0;
5290
5418
  }
5419
+ function assessMappedPropCompatibility(props, mappings) {
5420
+ const byRawProp = new Map(mappings.map((mapping) => [mapping.rawProp, mapping]));
5421
+ const unprovenProps = props.flatMap((prop) => {
5422
+ if (prop.resolution === "spread") return [prop.prop];
5423
+ const mapping = byRawProp.get(prop.prop);
5424
+ if (!mapping || !mappingCanPreserveProp(mapping, prop)) return [prop.prop];
5425
+ return [];
5426
+ });
5427
+ return { complete: unprovenProps.length === 0, unprovenProps };
5428
+ }
5429
+ function mappingCanPreserveProp(mapping, prop) {
5430
+ const renames = mapping.rawProp !== mapping.canonicalProp;
5431
+ const valueMap = mapping.valueMap;
5432
+ const translatesValue = valueMap !== void 0 && Object.keys(valueMap).length > 0;
5433
+ if (renames && (!isIdentifier(mapping.rawProp) || !isIdentifier(mapping.canonicalProp))) {
5434
+ return false;
5435
+ }
5436
+ if (!translatesValue) return true;
5437
+ if (!isIdentifier(mapping.rawProp)) return false;
5438
+ return prop.resolution === "static" && typeof prop.value === "string" && Object.prototype.hasOwnProperty.call(valueMap, prop.value);
5439
+ }
5440
+ function isLocalExactComponentImportRedirectCandidate(usage, componentName) {
5441
+ return isUnaliasedNamedImport(usage, componentName) && isProjectLocalImportSource(usage.source);
5442
+ }
5443
+ function isUnaliasedNamedImport(usage, componentName) {
5444
+ return isSimpleComponentBinding(usage, componentName) && usage.imported === componentName;
5445
+ }
5446
+ function isSimpleComponentBinding(usage, componentName) {
5447
+ return usage.namespace !== true && usage.local === componentName;
5448
+ }
5449
+ function isProjectLocalImportSource(source) {
5450
+ return source.startsWith(".") || source.startsWith("/") || source.startsWith("@/") || source.startsWith("~/");
5451
+ }
5291
5452
  function sameHtmlTag(htmlEquivalent, element) {
5292
5453
  return htmlEquivalent !== void 0 && htmlEquivalent.toLowerCase() === element.toLowerCase();
5293
5454
  }
@@ -5320,6 +5481,7 @@ function isCanonicalMappingOption(value) {
5320
5481
  if (record.htmlType !== void 0 && typeof record.htmlType !== "string") return false;
5321
5482
  if (record.importPath !== void 0 && typeof record.importPath !== "string") return false;
5322
5483
  if (record.confidence !== void 0 && typeof record.confidence !== "number") return false;
5484
+ if (record.conformStatus !== void 0 && record.conformStatus !== "confirmed") return false;
5323
5485
  if (record.declaredHtmlEquivalent !== void 0 && typeof record.declaredHtmlEquivalent !== "boolean") {
5324
5486
  return false;
5325
5487
  }
@@ -5422,6 +5584,11 @@ function sourceLabel(source) {
5422
5584
  if (source.kind === "registry") return source.importPath ?? source.installPath;
5423
5585
  return source.path;
5424
5586
  }
5587
+ function canonicalImportPath(source) {
5588
+ if (source.kind === "npm") return source.specifier;
5589
+ if (source.kind === "registry") return source.importPath;
5590
+ return void 0;
5591
+ }
5425
5592
  function isCanonicalSourceImplementationFile(file, sources) {
5426
5593
  const normalizedFile = normalizePath(file);
5427
5594
  return sources.some((source) => {
@@ -6067,6 +6234,7 @@ function ruleStylesNoRawColor(ix) {
6067
6234
  if (!color) continue;
6068
6235
  if (isExemptColor(color, policy.except)) continue;
6069
6236
  const resolution = resolveColorToken(decl.property, color, colorTokens);
6237
+ const fixEmission = resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution, ix) : void 0;
6070
6238
  const evidenceIds = resolution ? [decl.id, policy.id, resolution.token.id] : [decl.id, policy.id];
6071
6239
  findings.push(
6072
6240
  makeFinding({
@@ -6084,14 +6252,15 @@ function ruleStylesNoRawColor(ix) {
6084
6252
  property: decl.property,
6085
6253
  value: color
6086
6254
  },
6087
- fix: resolution?.kind === "exact" ? buildTokenFix(decl.property, decl.value, color, resolution) : void 0,
6255
+ fix: fixEmission?.fix,
6088
6256
  attributes: {
6089
6257
  property: decl.property,
6090
6258
  rawValue: decl.value,
6091
6259
  color,
6092
6260
  source: "css",
6093
6261
  suggestedToken: resolution?.token.name,
6094
- ...resolution ? { tokenMatch: resolution.kind } : {}
6262
+ ...resolution ? { tokenMatch: resolution.kind } : {},
6263
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6095
6264
  }
6096
6265
  })
6097
6266
  );
@@ -6106,6 +6275,7 @@ function ruleStylesNoRawColor(ix) {
6106
6275
  if (!node) continue;
6107
6276
  const componentEvidenceId = componentByNode.get(node.id)?.id;
6108
6277
  const resolution = resolveColorToken(inline.property, color, colorTokens);
6278
+ const fixEmission = resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution, ix) : void 0;
6109
6279
  const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
6110
6280
  const evidenceIds = resolution ? [...baseEvidence, resolution.token.id] : baseEvidence;
6111
6281
  findings.push(
@@ -6124,14 +6294,15 @@ function ruleStylesNoRawColor(ix) {
6124
6294
  property: inline.property,
6125
6295
  value: color
6126
6296
  },
6127
- fix: resolution?.kind === "exact" ? buildTokenFix(inline.property, inline.value, color, resolution) : void 0,
6297
+ fix: fixEmission?.fix,
6128
6298
  attributes: {
6129
6299
  property: inline.property,
6130
6300
  rawValue: inline.value,
6131
6301
  color,
6132
6302
  source: "jsx",
6133
6303
  suggestedToken: resolution?.token.name,
6134
- ...resolution ? { tokenMatch: resolution.kind } : {}
6304
+ ...resolution ? { tokenMatch: resolution.kind } : {},
6305
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6135
6306
  }
6136
6307
  })
6137
6308
  );
@@ -6215,21 +6386,24 @@ function normalizeColor2(value) {
6215
6386
  }
6216
6387
  return lower;
6217
6388
  }
6218
- function buildTokenFix(property, rawValue, rawColor, match) {
6389
+ function buildTokenFix(property, rawValue, rawColor, match, ix) {
6219
6390
  const reference = tokenReference(match.token.name);
6220
6391
  const value = replaceRawColor(rawValue, rawColor, reference);
6221
- return {
6222
- kind: "replaceStyleValue",
6223
- title: `Replace ${property} with ${value}`,
6224
- property,
6225
- value,
6226
- // Apply automatically only when the swap is unambiguous AND the reference
6227
- // resolves verbatim. An ambiguous match (several tokens share the value,
6228
- // none role-matching) or a token that can't be referenced from CSS (a Sass
6229
- // *map* member, or a DTCG JSON token with no emitted custom property) is a
6230
- // suggestion — applying it would cross roles or write non-compiling code.
6231
- deterministic: !match.ambiguous && isApplicableTokenReference(match.token.referenceFormat)
6232
- };
6392
+ return emitFix(
6393
+ {
6394
+ kind: "replaceStyleValue",
6395
+ title: `Replace ${property} with ${value}`,
6396
+ property,
6397
+ value,
6398
+ deterministic: true
6399
+ },
6400
+ {
6401
+ symbols: [match.token.name],
6402
+ editKind: "replaceStyleValue",
6403
+ valuePreserving: !match.ambiguous
6404
+ },
6405
+ ix
6406
+ );
6233
6407
  }
6234
6408
  function replaceRawColor(rawValue, rawColor, replacement) {
6235
6409
  if (rawValue === rawColor) return replacement;
@@ -6260,6 +6434,7 @@ function ruleStylesNoRawDimensions(ix) {
6260
6434
  if (!resolution) continue;
6261
6435
  const tokenIds = resolutionTokenIds(resolution);
6262
6436
  const evidenceIds = tokenIds.length ? [decl.id, policy.id, ...tokenIds] : [decl.id, policy.id];
6437
+ const fixEmission = dimensionFix(decl.property, resolution, ix);
6263
6438
  findings.push(
6264
6439
  makeFinding({
6265
6440
  ruleId: RULE_ID9,
@@ -6276,12 +6451,13 @@ function ruleStylesNoRawDimensions(ix) {
6276
6451
  property: decl.property,
6277
6452
  value: decl.value
6278
6453
  },
6279
- fix: dimensionFix(decl.property, resolution),
6454
+ fix: fixEmission?.fix,
6280
6455
  attributes: {
6281
6456
  property: decl.property,
6282
6457
  rawValue: decl.value,
6283
6458
  source: "css",
6284
- suggestedToken: resolutionSuggestedToken(resolution)
6459
+ suggestedToken: resolutionSuggestedToken(resolution),
6460
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6285
6461
  }
6286
6462
  })
6287
6463
  );
@@ -6300,6 +6476,7 @@ function ruleStylesNoRawDimensions(ix) {
6300
6476
  const baseEvidence = componentEvidenceId ? [node.id, componentEvidenceId, inline.id, policy.id] : [node.id, inline.id, policy.id];
6301
6477
  const tokenIds = resolutionTokenIds(resolution);
6302
6478
  const evidenceIds = tokenIds.length ? [...baseEvidence, ...tokenIds] : baseEvidence;
6479
+ const fixEmission = dimensionFix(inline.property, resolution, ix);
6303
6480
  findings.push(
6304
6481
  makeFinding({
6305
6482
  ruleId: RULE_ID9,
@@ -6316,12 +6493,13 @@ function ruleStylesNoRawDimensions(ix) {
6316
6493
  property: inline.property,
6317
6494
  value: inline.value
6318
6495
  },
6319
- fix: dimensionFix(inline.property, resolution),
6496
+ fix: fixEmission?.fix,
6320
6497
  attributes: {
6321
6498
  property: inline.property,
6322
6499
  rawValue: inline.value,
6323
6500
  source: "jsx",
6324
- suggestedToken: resolutionSuggestedToken(resolution)
6501
+ suggestedToken: resolutionSuggestedToken(resolution),
6502
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6325
6503
  }
6326
6504
  })
6327
6505
  );
@@ -6366,15 +6544,23 @@ function dimensionMessage(rawValue, property, preferLabel, resolution, inline) {
6366
6544
  }
6367
6545
  return `Raw dimension ${rawValue} on ${where}. Use a ${preferLabel} instead.`;
6368
6546
  }
6369
- function dimensionFix(property, resolution) {
6547
+ function dimensionFix(property, resolution, ix) {
6370
6548
  if (resolution.kind === "exact") {
6371
- return {
6372
- kind: "replaceStyleValue",
6373
- title: `Replace ${property} with ${resolution.fixValue}`,
6374
- property,
6375
- value: resolution.fixValue,
6376
- deterministic: true
6377
- };
6549
+ return emitFix(
6550
+ {
6551
+ kind: "replaceStyleValue",
6552
+ title: `Replace ${property} with ${resolution.fixValue}`,
6553
+ property,
6554
+ value: resolution.fixValue,
6555
+ deterministic: true
6556
+ },
6557
+ {
6558
+ symbols: resolution.tokens.map((token) => token.name),
6559
+ editKind: "replaceStyleValue",
6560
+ valuePreserving: true
6561
+ },
6562
+ ix
6563
+ );
6378
6564
  }
6379
6565
  return void 0;
6380
6566
  }
@@ -6475,7 +6661,7 @@ function resolveSpacingValue(input) {
6475
6661
  let anyOffScale = false;
6476
6662
  let canFixAll = true;
6477
6663
  let reliableSuggestion = true;
6478
- let allApplicableValuePreserving = true;
6664
+ let allValuePreserving = true;
6479
6665
  for (const part of parts) {
6480
6666
  const normalized = normalizeLengthForScale(part.parsed, input.scale);
6481
6667
  if (normalized === null) return null;
@@ -6489,8 +6675,7 @@ function resolveSpacingValue(input) {
6489
6675
  }
6490
6676
  anyViolation = true;
6491
6677
  const reference = tokenReference(exactToken.name);
6492
- const applicable = isApplicableTokenReference(exactToken.referenceFormat) && normalized.deterministicFix;
6493
- if (!applicable) allApplicableValuePreserving = false;
6678
+ if (!normalized.deterministicFix) allValuePreserving = false;
6494
6679
  suggestedParts.push(reference);
6495
6680
  if (!firstViolation) {
6496
6681
  firstViolation = {
@@ -6498,7 +6683,7 @@ function resolveSpacingValue(input) {
6498
6683
  suggestedValue: reference,
6499
6684
  suggestedToken: exactToken.name,
6500
6685
  fixEmittable: true,
6501
- deterministicFix: applicable,
6686
+ deterministicFix: normalized.deterministicFix,
6502
6687
  reason: "token-equivalent",
6503
6688
  matchedToken: exactToken.name,
6504
6689
  assumedRootFontSizePx: normalized.assumedRootFontSizePx,
@@ -6509,7 +6694,7 @@ function resolveSpacingValue(input) {
6509
6694
  }
6510
6695
  anyViolation = true;
6511
6696
  anyOffScale = true;
6512
- allApplicableValuePreserving = false;
6697
+ allValuePreserving = false;
6513
6698
  const nearest = nearestSignedScaleValue(normalized.value, input.allowed);
6514
6699
  const snapsToZero = nearest === 0 && normalized.value !== 0;
6515
6700
  let suggestedPart;
@@ -6542,7 +6727,7 @@ function resolveSpacingValue(input) {
6542
6727
  const joined = suggestedParts.join(" ");
6543
6728
  const changes = joined !== input.raw.trim().replace(/\s+/g, " ");
6544
6729
  const fixEmittable = canFixAll && reliableSuggestion && changes;
6545
- const deterministic = fixEmittable && !anyOffScale && allApplicableValuePreserving;
6730
+ const deterministic = fixEmittable && !anyOffScale && allValuePreserving;
6546
6731
  return {
6547
6732
  ...firstViolation,
6548
6733
  reason: anyOffScale ? "off-scale" : "token-equivalent",
@@ -6606,6 +6791,7 @@ function checkDeclaration(ix, decl, lookupFor) {
6606
6791
  tokens: lookupFor(scale)
6607
6792
  });
6608
6793
  if (!checked) return null;
6794
+ const fixEmission = buildSpacingFix(decl.property, checked, ix);
6609
6795
  return makeFinding({
6610
6796
  ruleId: RULE_ID10,
6611
6797
  ruleVersion: RULE_VERSION11,
@@ -6621,7 +6807,7 @@ function checkDeclaration(ix, decl, lookupFor) {
6621
6807
  property: decl.property,
6622
6808
  value: decl.value
6623
6809
  },
6624
- fix: buildSpacingFix(decl.property, checked),
6810
+ fix: fixEmission?.fix,
6625
6811
  attributes: {
6626
6812
  property: decl.property,
6627
6813
  rawValue: decl.value,
@@ -6635,7 +6821,8 @@ function checkDeclaration(ix, decl, lookupFor) {
6635
6821
  normalizedUnit: scale.unit,
6636
6822
  assumedRootFontSizePx: checked.assumedRootFontSizePx,
6637
6823
  assumedEmBasePx: checked.assumedEmBasePx,
6638
- source: "css"
6824
+ source: "css",
6825
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6639
6826
  }
6640
6827
  });
6641
6828
  }
@@ -6655,6 +6842,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
6655
6842
  bareNumberFix: inline.valueKind === "number"
6656
6843
  });
6657
6844
  if (!checked) return null;
6845
+ const fixEmission = buildSpacingFix(inline.property, checked, ix);
6658
6846
  const node = readUsageNode(ix, inline.nodeId);
6659
6847
  if (!node) return null;
6660
6848
  const componentEvidenceId = componentByNode.get(node.id)?.id;
@@ -6674,7 +6862,7 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
6674
6862
  property: inline.property,
6675
6863
  value: inline.value
6676
6864
  },
6677
- fix: buildSpacingFix(inline.property, checked),
6865
+ fix: fixEmission?.fix,
6678
6866
  attributes: {
6679
6867
  property: inline.property,
6680
6868
  rawValue: inline.value,
@@ -6688,19 +6876,28 @@ function checkInlineStyle(ix, inline, componentByNode, lookupFor) {
6688
6876
  normalizedUnit: scale.unit,
6689
6877
  assumedRootFontSizePx: checked.assumedRootFontSizePx,
6690
6878
  assumedEmBasePx: checked.assumedEmBasePx,
6691
- source: "jsx"
6879
+ source: "jsx",
6880
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6692
6881
  }
6693
6882
  });
6694
6883
  }
6695
- function buildSpacingFix(property, checked) {
6884
+ function buildSpacingFix(property, checked, ix) {
6696
6885
  if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
6697
- return {
6698
- kind: "replaceStyleValue",
6699
- title: `Replace ${property} with ${checked.suggestedValue}`,
6700
- property,
6701
- value: checked.suggestedValue,
6702
- deterministic: checked.deterministicFix
6703
- };
6886
+ return emitFix(
6887
+ {
6888
+ kind: "replaceStyleValue",
6889
+ title: `Replace ${property} with ${checked.suggestedValue}`,
6890
+ property,
6891
+ value: checked.suggestedValue,
6892
+ deterministic: true
6893
+ },
6894
+ {
6895
+ symbols: tokenSymbolsInText(checked.suggestedValue),
6896
+ editKind: "replaceStyleValue",
6897
+ valuePreserving: checked.deterministicFix
6898
+ },
6899
+ ix
6900
+ );
6704
6901
  }
6705
6902
  function spacingMessage(property, value, allowed, unit, checked) {
6706
6903
  if (checked.reason === "token-equivalent" && checked.matchedToken) {
@@ -6729,6 +6926,7 @@ function ruleStylesNoRawTypography(ix) {
6729
6926
  if (decl.property.toLowerCase() !== "font-size") continue;
6730
6927
  const checked = checkFontSize(decl.value, allowed, scale, tokens);
6731
6928
  if (!checked) continue;
6929
+ const fixEmission = buildFix(decl.property, checked, ix);
6732
6930
  findings.push(
6733
6931
  makeFinding({
6734
6932
  ruleId: RULE_ID11,
@@ -6745,7 +6943,7 @@ function ruleStylesNoRawTypography(ix) {
6745
6943
  property: decl.property,
6746
6944
  value: decl.value
6747
6945
  },
6748
- fix: buildFix(decl.property, checked),
6946
+ fix: fixEmission?.fix,
6749
6947
  attributes: {
6750
6948
  property: decl.property,
6751
6949
  rawValue: decl.value,
@@ -6753,7 +6951,8 @@ function ruleStylesNoRawTypography(ix) {
6753
6951
  allowed,
6754
6952
  suggestedValue: checked.suggestedValue,
6755
6953
  matchedToken: checked.matchedToken,
6756
- source: "css"
6954
+ source: "css",
6955
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6757
6956
  }
6758
6957
  })
6759
6958
  );
@@ -6762,8 +6961,15 @@ function ruleStylesNoRawTypography(ix) {
6762
6961
  for (const inline of ix.byKind("usage_inline_style")) {
6763
6962
  if (inline.valueKind === "css-variable") continue;
6764
6963
  if (!FONT_SIZE_PROPERTIES.has(inline.property.toLowerCase())) continue;
6765
- const checked = checkFontSize(inline.value, allowed, scale, tokens, inline.valueKind === "number");
6964
+ const checked = checkFontSize(
6965
+ inline.value,
6966
+ allowed,
6967
+ scale,
6968
+ tokens,
6969
+ inline.valueKind === "number"
6970
+ );
6766
6971
  if (!checked) continue;
6972
+ const fixEmission = buildFix(inline.property, checked, ix);
6767
6973
  const node = readUsageNode(ix, inline.nodeId);
6768
6974
  if (!node) continue;
6769
6975
  const componentEvidenceId = componentByNode.get(node.id)?.id;
@@ -6784,7 +6990,7 @@ function ruleStylesNoRawTypography(ix) {
6784
6990
  property: inline.property,
6785
6991
  value: inline.value
6786
6992
  },
6787
- fix: buildFix(inline.property, checked),
6993
+ fix: fixEmission?.fix,
6788
6994
  attributes: {
6789
6995
  property: inline.property,
6790
6996
  rawValue: inline.value,
@@ -6792,7 +6998,8 @@ function ruleStylesNoRawTypography(ix) {
6792
6998
  allowed,
6793
6999
  suggestedValue: checked.suggestedValue,
6794
7000
  matchedToken: checked.matchedToken,
6795
- source: "jsx"
7001
+ source: "jsx",
7002
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {}
6796
7003
  }
6797
7004
  })
6798
7005
  );
@@ -6822,11 +7029,10 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
6822
7029
  const exactToken = tokens.get(magnitude);
6823
7030
  if (matchesScale(normalized.value, allowed)) {
6824
7031
  if (!exactToken) return null;
6825
- const applicable = isApplicableTokenReference(exactToken.referenceFormat) && normalized.deterministicFix;
6826
7032
  return {
6827
7033
  suggestedValue: tokenReference(exactToken.name),
6828
7034
  fixEmittable: true,
6829
- deterministicFix: applicable,
7035
+ deterministicFix: normalized.deterministicFix,
6830
7036
  reason: "token-equivalent",
6831
7037
  matchedToken: exactToken.name
6832
7038
  };
@@ -6844,15 +7050,23 @@ function checkFontSize(raw, allowed, scale, tokens, bareNumberFix = false) {
6844
7050
  reason: "off-scale"
6845
7051
  };
6846
7052
  }
6847
- function buildFix(property, checked) {
7053
+ function buildFix(property, checked, ix) {
6848
7054
  if (!checked.fixEmittable || checked.suggestedValue === void 0) return void 0;
6849
- return {
6850
- kind: "replaceStyleValue",
6851
- title: `Replace ${property} with ${checked.suggestedValue}`,
6852
- property,
6853
- value: checked.suggestedValue,
6854
- deterministic: checked.deterministicFix
6855
- };
7055
+ return emitFix(
7056
+ {
7057
+ kind: "replaceStyleValue",
7058
+ title: `Replace ${property} with ${checked.suggestedValue}`,
7059
+ property,
7060
+ value: checked.suggestedValue,
7061
+ deterministic: true
7062
+ },
7063
+ {
7064
+ symbols: tokenSymbolsInText(checked.suggestedValue),
7065
+ editKind: "replaceStyleValue",
7066
+ valuePreserving: checked.deterministicFix
7067
+ },
7068
+ ix
7069
+ );
6856
7070
  }
6857
7071
  function typographyMessage(property, value, allowed, unit, checked) {
6858
7072
  if (checked.reason === "token-equivalent" && checked.matchedToken) {
@@ -7526,7 +7740,7 @@ var SASS_FILE = /\.(scss|sass)$/i;
7526
7740
  function ruleTokensRequireDualFallback(ix) {
7527
7741
  const policy = ix.policy.ruleConfig(RULE_ID19);
7528
7742
  if (!policy?.enabled) return [];
7529
- const knownTokenVars = cssVariableNames(ix.tokens.list());
7743
+ const tokensByCssVariable = indexTokensByCssVariable(ix.tokens.list());
7530
7744
  const findings = [];
7531
7745
  for (const decl of ix.byKind("style_declaration")) {
7532
7746
  if (!SASS_FILE.test(decl.file)) continue;
@@ -7536,19 +7750,35 @@ function ruleTokensRequireDualFallback(ix) {
7536
7750
  const tokenName = match[1];
7537
7751
  const hasFallback = match[2] !== void 0;
7538
7752
  if (hasFallback) continue;
7539
- if (!knownTokenVars.has(tokenName) && !tokenName.startsWith("--fui-")) continue;
7753
+ const token = tokensByCssVariable.get(tokenName);
7754
+ if (!token) continue;
7540
7755
  const rawValue = match[0];
7541
7756
  const scssVar = tokenName.replace(/^--/, "$");
7542
- const replacement = `var(${tokenName}, ${scssVar})`;
7543
- const nextValue = decl.value.replace(rawValue, replacement);
7757
+ const hasScssVariable = token.referenceFormat === "scss-var";
7758
+ const nextValue = hasScssVariable ? decl.value.replace(rawValue, `var(${tokenName}, ${scssVar})`) : void 0;
7759
+ const fixEmission = nextValue ? emitFix(
7760
+ {
7761
+ kind: "replaceStyleValue",
7762
+ title: `Add fallback for ${tokenName}`,
7763
+ property: decl.property,
7764
+ value: nextValue,
7765
+ deterministic: true
7766
+ },
7767
+ {
7768
+ symbols: [tokenName, scssVar],
7769
+ editKind: "replaceStyleValue",
7770
+ valuePreserving: true
7771
+ },
7772
+ ix
7773
+ ) : void 0;
7544
7774
  findings.push(
7545
7775
  makeFinding({
7546
7776
  ruleId: RULE_ID19,
7547
7777
  ruleVersion: RULE_VERSION20,
7548
7778
  severity: policy.severity ?? "warn",
7549
- message: `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).`,
7779
+ message: hasScssVariable ? `var(${tokenName}) is missing an SCSS fallback. Use var(${tokenName}, ${scssVar}).` : `var(${tokenName}) is missing a fallback. No matching SCSS variable was found in the token source; add a fallback manually or define ${scssVar}.`,
7550
7780
  location: decl.location,
7551
- evidence: ix.evidence([decl.id, policy.id]),
7781
+ evidence: ix.evidence([decl.id, token.id, policy.id]),
7552
7782
  fingerprintIdentity: {
7553
7783
  file: decl.file,
7554
7784
  selector: decl.selector,
@@ -7556,18 +7786,13 @@ function ruleTokensRequireDualFallback(ix) {
7556
7786
  property: decl.property,
7557
7787
  tokenName
7558
7788
  },
7559
- fix: {
7560
- kind: "replaceStyleValue",
7561
- title: `Add fallback for ${tokenName}`,
7562
- property: decl.property,
7563
- value: nextValue,
7564
- deterministic: true
7565
- },
7789
+ fix: fixEmission?.fix,
7566
7790
  attributes: {
7567
7791
  property: decl.property,
7568
7792
  rawValue: decl.value,
7569
7793
  tokenName,
7570
- suggestedValue: nextValue,
7794
+ ...nextValue ? { suggestedValue: nextValue } : {},
7795
+ ...fixEmission?.downgradeReason ? { downgradeReason: fixEmission.downgradeReason } : {},
7571
7796
  source: "css"
7572
7797
  }
7573
7798
  })
@@ -7576,6 +7801,17 @@ function ruleTokensRequireDualFallback(ix) {
7576
7801
  }
7577
7802
  return findings;
7578
7803
  }
7804
+ function indexTokensByCssVariable(tokens) {
7805
+ const indexed = /* @__PURE__ */ new Map();
7806
+ for (const token of tokens) {
7807
+ const cssVariable = token.name.startsWith("--") ? token.name : token.name.startsWith("$") ? `--${token.name.slice(1)}` : `--${token.name}`;
7808
+ const existing = indexed.get(cssVariable);
7809
+ if (!existing || token.referenceFormat === "scss-var") {
7810
+ indexed.set(cssVariable, token);
7811
+ }
7812
+ }
7813
+ return indexed;
7814
+ }
7579
7815
 
7580
7816
  // src/rules/tokens-css-vars-must-be-defined.ts
7581
7817
  var RULE_ID20 = "tokens/css-vars-must-be-defined";
@@ -7667,6 +7903,8 @@ var BLOCKING_RULE_ALLOWLIST = /* @__PURE__ */ new Set([
7667
7903
  // imported component bypasses its canonical
7668
7904
  ]);
7669
7905
  function gatesCi(finding, failOnWarnings) {
7906
+ if (!canBlock(finding.evidenceGrade ?? "source_backed")) return false;
7907
+ if (finding.attributes?.advisory === true) return false;
7670
7908
  const level = severityLevel(finding.severity);
7671
7909
  return level === "error" || failOnWarnings && level === "warn";
7672
7910
  }
@@ -7674,7 +7912,11 @@ function isDenyEligible(finding, failOnWarnings) {
7674
7912
  if (!BLOCKING_RULE_ALLOWLIST.has(finding.ruleId)) return false;
7675
7913
  if (finding.attributes?.advisory === true) return false;
7676
7914
  if (!gatesCi(finding, failOnWarnings)) return false;
7677
- if (finding.fix?.deterministic === false) return false;
7915
+ if (finding.fix?.deterministic === false) {
7916
+ if (finding.ruleId !== "components/prefer-library" || finding.attributes?.propCompatibility !== "observed-complete") {
7917
+ return false;
7918
+ }
7919
+ }
7678
7920
  return true;
7679
7921
  }
7680
7922
 
@@ -7682,7 +7924,7 @@ function isDenyEligible(finding, failOnWarnings) {
7682
7924
  var RULE_TIER = {
7683
7925
  // ---- Tier A — contract vocabulary -------------------------------------
7684
7926
  "components/prefer-library": "contract",
7685
- // canonical-component bypass (FUI1001/1004)
7927
+ // canonical-component bypass (FUI1004)
7686
7928
  "tokens/css-vars-must-be-defined": "contract",
7687
7929
  // token drift (FUI2015)
7688
7930
  // ---- Tier B — generic hygiene -----------------------------------------
@@ -7947,6 +8189,7 @@ function evaluateGovernanceIntegrity(input) {
7947
8189
  componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
7948
8190
  } else {
7949
8191
  componentsFamily.reason = "components/prefer-library not enabled";
8192
+ componentsFamily.remediation = "add govern.canonicalSources (a directory source, or npm/registry with a non-empty include) or designSystem.path/packageName";
7950
8193
  }
7951
8194
  }
7952
8195
  const cssVarsActive = input.cssVarsActive ?? policyDeclaresCssVars(input.policy);
@@ -8035,6 +8278,29 @@ function evaluateGovernanceIntegrity(input) {
8035
8278
  // src/contract/preimage.ts
8036
8279
  var CONTRACT_DOMAINS = ["components", "tokens", "canonicalMap", "policy"];
8037
8280
  var CONTRACT_PREIMAGE_SCHEMA = "fcid-preimage:v1";
8281
+ var CONTRACT_PREIMAGE_CAPABILITY_HEADER = "X-Fragments-Contract-Preimage";
8282
+ var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/u;
8283
+ function verifiedContractPreimageFromPin(pin) {
8284
+ if (pin.preimageSchema !== CONTRACT_PREIMAGE_SCHEMA || !SHA256_HEX_PATTERN.test(pin.contractHash) || !pin.domainHashes || typeof pin.domainHashes !== "object") {
8285
+ return null;
8286
+ }
8287
+ const keys = Object.keys(pin.domainHashes).sort();
8288
+ const expectedKeys = [...CONTRACT_DOMAINS].sort();
8289
+ if (keys.length !== expectedKeys.length || keys.some((key, index) => key !== expectedKeys[index])) {
8290
+ return null;
8291
+ }
8292
+ const domains = Object.fromEntries(
8293
+ CONTRACT_DOMAINS.map((domain) => [domain, pin.domainHashes?.[domain]])
8294
+ );
8295
+ if (CONTRACT_DOMAINS.some((domain) => !SHA256_HEX_PATTERN.test(domains[domain] ?? ""))) {
8296
+ return null;
8297
+ }
8298
+ const preimage = {
8299
+ schema: CONTRACT_PREIMAGE_SCHEMA,
8300
+ domains
8301
+ };
8302
+ return contractHash(preimage) === pin.contractHash ? preimage : null;
8303
+ }
8038
8304
  function sortCanonical(items) {
8039
8305
  return items.map((item) => ({ item, key: canonicalPreimage(item) })).sort((a, b) => a.key < b.key ? -1 : a.key > b.key ? 1 : 0).map(({ item }) => item);
8040
8306
  }
@@ -8358,15 +8624,18 @@ function readPreferLibraryRule(config) {
8358
8624
  }
8359
8625
  export {
8360
8626
  AGENT_CONTEXT_RELATIVE_PATH,
8627
+ AGENT_FORMAT_SCHEMA_VERSION,
8361
8628
  BLOCKING_RULE_ALLOWLIST,
8362
8629
  BRAND,
8363
8630
  CATALOG_FIXTURE_A_FCID,
8364
8631
  CODES,
8365
8632
  CONTRACT_DOMAINS,
8633
+ CONTRACT_PREIMAGE_CAPABILITY_HEADER,
8366
8634
  CONTRACT_PREIMAGE_SCHEMA,
8367
8635
  DEFAULTS,
8368
8636
  DEFAULT_ENHANCED_STYLE_PROPERTIES,
8369
8637
  DEFAULT_STYLE_PROPERTIES,
8638
+ EVIDENCE_ORDER,
8370
8639
  EXPLAIN_URL_BASE,
8371
8640
  FRAGMENTS_INTERNAL_RULE_IDS,
8372
8641
  FactIndex,
@@ -8388,7 +8657,10 @@ export {
8388
8657
  SEVERITIES,
8389
8658
  SEVERITY_RANK,
8390
8659
  SEVERITY_WEIGHTS,
8660
+ agentErrorEnvelopeSchema,
8391
8661
  agentFormatSchema,
8662
+ agentIntegritySchema,
8663
+ agentOutputSchema,
8392
8664
  aiMetadataSchema,
8393
8665
  analyzeComposition,
8394
8666
  asComponentId,
@@ -8415,6 +8687,7 @@ export {
8415
8687
  byCode,
8416
8688
  byRuleId,
8417
8689
  calculateDeltaE,
8690
+ canBlock,
8418
8691
  canonicalJson,
8419
8692
  canonicalPreimage,
8420
8693
  canonicalizeOwnedComponentId,
@@ -8705,6 +8978,7 @@ export {
8705
8978
  topologyBase,
8706
8979
  usePreviewVariantRuntime,
8707
8980
  validatorResultSchema,
8981
+ verifiedContractPreimageFromPin,
8708
8982
  violationSchema,
8709
8983
  vocabularyRank
8710
8984
  };