@usefragments/core 1.7.0 → 1.8.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 (35) hide show
  1. package/dist/chunk-MZ4SW3TP.js +375 -0
  2. package/dist/chunk-MZ4SW3TP.js.map +1 -0
  3. package/dist/{chunk-WNMWKUYG.js → chunk-UUREQ4HD.js} +318 -6
  4. package/dist/chunk-UUREQ4HD.js.map +1 -0
  5. package/dist/codes/index.d.ts +1 -1
  6. package/dist/codes/index.js +1 -2
  7. package/dist/compiled-types/index.d.ts +1 -1
  8. package/dist/generate/index.d.ts +1 -1
  9. package/dist/governance-telemetry.d.ts +316 -0
  10. package/dist/governance-telemetry.js +11 -0
  11. package/dist/governance-telemetry.js.map +1 -0
  12. package/dist/{index-hZAlYCli.d.ts → index-Cxk3SOQP.d.ts} +1005 -1005
  13. package/dist/index.d.ts +529 -477
  14. package/dist/index.js +78 -37
  15. package/dist/index.js.map +1 -1
  16. package/dist/react-types.d.ts +1 -1
  17. package/dist/registry.d.ts +146 -146
  18. package/dist/schemas/index.d.ts +1 -1
  19. package/dist/schemas/index.js +3 -1
  20. package/dist/test-utils.d.ts +1 -1
  21. package/package.json +5 -1
  22. package/src/component-contract.ts +12 -0
  23. package/src/contract-parser.ts +2 -0
  24. package/src/governance-telemetry.test.ts +18 -0
  25. package/src/governance-telemetry.ts +398 -0
  26. package/src/index.ts +17 -0
  27. package/src/rules/index.ts +5 -1
  28. package/src/rules/tokens-css-vars-must-be-defined.test.ts +66 -1
  29. package/src/rules/tokens-css-vars-must-be-defined.ts +88 -16
  30. package/src/schemas/index.ts +4 -0
  31. package/src/schemas/normalize-finding.test.ts +50 -0
  32. package/dist/chunk-WNMWKUYG.js.map +0 -1
  33. package/dist/chunk-ZHS52OT4.js +0 -317
  34. package/dist/chunk-ZHS52OT4.js.map +0 -1
  35. package/dist/{governance-D9KtH-vg.d.ts → governance-eEzCyfes.d.ts} +154 -154
@@ -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-hZAlYCli.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-Cxk3SOQP.js';
@@ -24,7 +24,9 @@ import {
24
24
  suppressionDirectiveSchema,
25
25
  validatorResultSchema,
26
26
  violationSchema
27
- } from "../chunk-ZHS52OT4.js";
27
+ } from "../chunk-UUREQ4HD.js";
28
+ import "../chunk-JNBFJ34I.js";
29
+ import "../chunk-EIYNNS77.js";
28
30
  import {
29
31
  legacySeverityLevelSchema,
30
32
  severityLevelSchema,
@@ -1,4 +1,4 @@
1
- import { C as CompiledBlock, b as CompiledFragment } from './governance-D9KtH-vg.js';
1
+ import { C as CompiledBlock, b as CompiledFragment } from './governance-eEzCyfes.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.7.0",
3
+ "version": "1.8.0",
4
4
  "license": "MIT",
5
5
  "description": "Core types, schemas, and runtime API for Fragments component definitions",
6
6
  "author": "Conan McNicholl",
@@ -64,6 +64,10 @@
64
64
  "types": "./dist/package-identity.d.ts",
65
65
  "import": "./dist/package-identity.js"
66
66
  },
67
+ "./governance-telemetry": {
68
+ "types": "./dist/governance-telemetry.d.ts",
69
+ "import": "./dist/governance-telemetry.js"
70
+ },
67
71
  "./test-utils": {
68
72
  "types": "./dist/test-utils.d.ts",
69
73
  "import": "./dist/test-utils.js"
@@ -25,6 +25,13 @@ export interface ComponentContract {
25
25
  /** Framework hint (for extraction and documentation) */
26
26
  framework?: 'react' | 'vue' | 'svelte' | 'web-components' | 'angular';
27
27
 
28
+ /** External npm packages required by this component (displayed in docs Setup section) */
29
+ dependencies?: Array<{
30
+ name: string;
31
+ version: string;
32
+ reason?: string;
33
+ }>;
34
+
28
35
  /** Path to the component source file, relative to fragments.config.ts root */
29
36
  sourcePath: string;
30
37
 
@@ -215,6 +222,11 @@ export const componentContractSchema = z.object({
215
222
  tags: z.array(z.string()).optional(),
216
223
  status: z.enum(['stable', 'beta', 'deprecated', 'experimental']).optional(),
217
224
  framework: z.enum(['react', 'vue', 'svelte', 'web-components', 'angular']).optional(),
225
+ dependencies: z.array(z.object({
226
+ name: z.string(),
227
+ version: z.string(),
228
+ reason: z.string().optional(),
229
+ })).optional(),
218
230
  sourcePath: z.string(),
219
231
  exportName: z.string(),
220
232
  propsSummary: z.array(z.string()),
@@ -21,6 +21,7 @@ export interface CompiledContractOutput {
21
21
  category: string;
22
22
  tags?: string[];
23
23
  status?: 'stable' | 'beta' | 'deprecated' | 'experimental';
24
+ dependencies?: Array<{ name: string; version: string; reason?: string }>;
24
25
  figma?: string;
25
26
  figmaProps?: Record<string, unknown>;
26
27
  };
@@ -112,6 +113,7 @@ export function parseComponentContract(
112
113
  category: validated.category,
113
114
  tags: validated.tags,
114
115
  status: validated.status,
116
+ dependencies: validated.dependencies,
115
117
  figma: validated.figma?.nodeUrl,
116
118
  figmaProps: validated.figma?.propMappings as Record<string, unknown> | undefined,
117
119
  },
@@ -0,0 +1,18 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ GOVERNANCE_TELEMETRY_FIELDS,
4
+ GOVERNANCE_TELEMETRY_TOP_LEVEL_FIELDS,
5
+ } from "./governance-telemetry.js";
6
+
7
+ describe("governance telemetry disclosure", () => {
8
+ it("covers every declared top-level wire field", () => {
9
+ expect(
10
+ [...new Set(GOVERNANCE_TELEMETRY_FIELDS.map((field) => field.topLevelField))].sort()
11
+ ).toEqual([...GOVERNANCE_TELEMETRY_TOP_LEVEL_FIELDS].sort());
12
+ });
13
+
14
+ it("keeps field-family paths unique", () => {
15
+ const paths = GOVERNANCE_TELEMETRY_FIELDS.map((field) => field.path);
16
+ expect(new Set(paths).size).toBe(paths.length);
17
+ });
18
+ });
@@ -0,0 +1,398 @@
1
+ /**
2
+ * Public disclosure contract for `fragments-governance-report:v1`.
3
+ *
4
+ * Brace groups enumerate closed sibling fields without hiding additions behind
5
+ * `*`. The three named placeholder fields are the only intentionally open
6
+ * records: configured ignore reasons, configured categories, and rule-specific
7
+ * evidence facts.
8
+ */
9
+ export type GovernanceTelemetryCarriage =
10
+ | "none"
11
+ | "derived"
12
+ | "source-literal"
13
+ | "structured-source";
14
+
15
+ export interface GovernanceTelemetryField {
16
+ path: string;
17
+ type: string;
18
+ purpose: string;
19
+ carriage: GovernanceTelemetryCarriage;
20
+ topLevelField: GovernanceTelemetryTopLevelField;
21
+ }
22
+
23
+ export const GOVERNANCE_TELEMETRY_TOP_LEVEL_FIELDS = [
24
+ "contractHash",
25
+ "schemaVersion",
26
+ "generatedAt",
27
+ "root",
28
+ "hook",
29
+ "git",
30
+ "summary",
31
+ "groups",
32
+ "suppressions",
33
+ "bypassEvents",
34
+ "hookEvents",
35
+ "packageVocabularies",
36
+ "adoption",
37
+ "health",
38
+ "topology",
39
+ "source",
40
+ "gateRung",
41
+ "diffOnly",
42
+ "failOnWarnings",
43
+ ] as const;
44
+
45
+ export type GovernanceTelemetryTopLevelField =
46
+ (typeof GOVERNANCE_TELEMETRY_TOP_LEVEL_FIELDS)[number];
47
+
48
+ export const GOVERNANCE_TELEMETRY_FIELDS = [
49
+ {
50
+ path: "schemaVersion",
51
+ type: '"fragments-governance-report:v1"',
52
+ purpose: "Pins the accepted wire shape and compatibility policy.",
53
+ carriage: "none",
54
+ topLevelField: "schemaVersion",
55
+ },
56
+ {
57
+ path: "generatedAt",
58
+ type: "ISO-8601 string",
59
+ purpose: "Records when the CLI produced the report.",
60
+ carriage: "none",
61
+ topLevelField: "generatedAt",
62
+ },
63
+ {
64
+ path: "root.{workspaceRoot,packageRoot,packageName}",
65
+ type: "string fields",
66
+ purpose: "Identifies the scanned workspace and package roots.",
67
+ carriage: "none",
68
+ topLevelField: "root",
69
+ },
70
+ {
71
+ path: "hook.{installedAt,mode,agents[]}",
72
+ type: "installation metadata",
73
+ purpose: "Describes the local hook installation that produced evidence.",
74
+ carriage: "none",
75
+ topLevelField: "hook",
76
+ },
77
+ {
78
+ path: "git.{baseRef,headSha,branch,repoFullName,pullRequestNumber}",
79
+ type: "Git and provider identifiers",
80
+ purpose: "Attaches results to the correct repository, commit and pull request.",
81
+ carriage: "none",
82
+ topLevelField: "git",
83
+ },
84
+ {
85
+ path: "summary.{filesScanned,rulesActive,presetsLoaded[],groupCount,occurrenceCount,ignoredCount,classnamesScanned,classnamesDynamic,classnamesResolved,totalFindings,errorCount,warnCount,infoCount,passed}",
86
+ type: "counts, verdict and active preset names",
87
+ purpose: "Reports scan volume and result totals without finding rows.",
88
+ carriage: "derived",
89
+ topLevelField: "summary",
90
+ },
91
+ {
92
+ path: "summary.ignoredByReason.{reason}",
93
+ type: "configured reason name → count",
94
+ purpose: "Counts ignored findings by the reason configured by the repository.",
95
+ carriage: "derived",
96
+ topLevelField: "summary",
97
+ },
98
+ {
99
+ path: "summary.countsBySeverity.{error,warn,info}",
100
+ type: "severity counts",
101
+ purpose: "Counts findings in each fixed severity bucket.",
102
+ carriage: "derived",
103
+ topLevelField: "summary",
104
+ },
105
+ {
106
+ path: "summary.countsByCategory.{category}",
107
+ type: "configured category name → count",
108
+ purpose: "Counts findings by the rule categories present in the scan.",
109
+ carriage: "derived",
110
+ topLevelField: "summary",
111
+ },
112
+ {
113
+ path: "summary.integrity.{status,declared,fatalForCi,blockingCapable,summary}",
114
+ type: "governance-integrity verdict",
115
+ purpose: "Explains whether the resolved policy is capable of enforcement.",
116
+ carriage: "derived",
117
+ topLevelField: "summary",
118
+ },
119
+ {
120
+ path: "summary.integrity.families[].{id,armed,reason,remediation}",
121
+ type: "integrity-family status",
122
+ purpose: "Explains the enforcement readiness of each governance family.",
123
+ carriage: "derived",
124
+ topLevelField: "summary",
125
+ },
126
+ {
127
+ path: "summary.integrity.families[].rules[]",
128
+ type: "rule identifiers",
129
+ purpose: "Names the active rules contributing to an integrity family.",
130
+ carriage: "derived",
131
+ topLevelField: "summary",
132
+ },
133
+ {
134
+ path: "summary.integrity.armed[]",
135
+ type: "integrity-family identifiers",
136
+ purpose: "Names the governance families capable of enforcement.",
137
+ carriage: "derived",
138
+ topLevelField: "summary",
139
+ },
140
+ {
141
+ path: "summary.integrity.remediations[]",
142
+ type: "remediation messages",
143
+ purpose: "Explains how to make an inert or degraded policy effective.",
144
+ carriage: "derived",
145
+ topLevelField: "summary",
146
+ },
147
+ {
148
+ path: "summary.integrity.configDiagnostics[].{code,kind,severity,path,message}",
149
+ type: "configuration diagnostics",
150
+ purpose: "Reports inert or conflicting authored governance configuration.",
151
+ carriage: "structured-source",
152
+ topLevelField: "summary",
153
+ },
154
+ {
155
+ path: "summary.integrity.roster.{configured,active,inert}",
156
+ type: "rule roster counts",
157
+ purpose: "Counts configured, active and inert governance rules.",
158
+ carriage: "derived",
159
+ topLevelField: "summary",
160
+ },
161
+ {
162
+ path: "summary.countsByBaselineState.{baselined,new}",
163
+ type: "baseline-state counts",
164
+ purpose: "Separates accepted baseline debt from newly introduced findings.",
165
+ carriage: "derived",
166
+ topLevelField: "summary",
167
+ },
168
+ {
169
+ path: "groups[].{groupId,ruleId,category,severity,packageRoot,filePath}",
170
+ type: "finding group metadata",
171
+ purpose: "Identifies each grouped rule failure and repository-relative path.",
172
+ carriage: "none",
173
+ topLevelField: "groups",
174
+ },
175
+ {
176
+ path: "groups[].message",
177
+ type: "string",
178
+ purpose: "Explains the failure and may quote the offending source literal.",
179
+ carriage: "source-literal",
180
+ topLevelField: "groups",
181
+ },
182
+ {
183
+ path: "groups[].normalizedOffense.kind",
184
+ type: "normalized-offense category",
185
+ purpose: "Names the kind of source offense represented by the group.",
186
+ carriage: "derived",
187
+ topLevelField: "groups",
188
+ },
189
+ {
190
+ path: "groups[].normalizedOffense.raw",
191
+ type: "normalized source offense",
192
+ purpose: "Carries the exact offending source substring.",
193
+ carriage: "source-literal",
194
+ topLevelField: "groups",
195
+ },
196
+ {
197
+ path: "groups[].normalizedOffense.canonical",
198
+ type: "normalized source offense",
199
+ purpose: "Carries the derived canonical representation of the offense.",
200
+ carriage: "derived",
201
+ topLevelField: "groups",
202
+ },
203
+ {
204
+ path: "groups[].occurrences[].occurrenceId",
205
+ type: "stable occurrence identifier",
206
+ purpose: "Identifies one occurrence within a finding group.",
207
+ carriage: "derived",
208
+ topLevelField: "groups",
209
+ },
210
+ {
211
+ path: "groups[].occurrences[].location.{file,line,column,endLine,endColumn}",
212
+ type: "repository-relative location",
213
+ purpose: "Places each occurrence at its exact file/line/column range.",
214
+ carriage: "none",
215
+ topLevelField: "groups",
216
+ },
217
+ {
218
+ path: "groups[].occurrences[].area.{areaId,areaName,criticality,owners[],matchedGlob}",
219
+ type: "area identifiers, owners and criticality",
220
+ purpose: "Carries the configured product-area classification for the file.",
221
+ carriage: "structured-source",
222
+ topLevelField: "groups",
223
+ },
224
+ {
225
+ path: "groups[].occurrences[].evidence[].factId",
226
+ type: "fact identifier",
227
+ purpose: "Links the occurrence to the extracted fact supporting it.",
228
+ carriage: "derived",
229
+ topLevelField: "groups",
230
+ },
231
+ {
232
+ path: "groups[].occurrences[].evidence[].fact.kind",
233
+ type: "fact discriminator",
234
+ purpose: "Names the extracted rule-fact shape.",
235
+ carriage: "none",
236
+ topLevelField: "groups",
237
+ },
238
+ {
239
+ path: "groups[].occurrences[].evidence[].fact.location.{file,line,column,endLine,endColumn}",
240
+ type: "repository-relative location",
241
+ purpose: "Locates the source-backed fact used as evidence.",
242
+ carriage: "none",
243
+ topLevelField: "groups",
244
+ },
245
+ {
246
+ path: "groups[].occurrences[].evidence[].fact.{ruleSpecificField}",
247
+ type: "extensible rule-specific fact fields",
248
+ purpose:
249
+ "Carries rule-specific parsed facts; fields may include selectors, properties and source values.",
250
+ carriage: "structured-source",
251
+ topLevelField: "groups",
252
+ },
253
+ {
254
+ path: "groups[].suggestedReplacement.{kind,from,to,certainty,reason}",
255
+ type: "deterministic replacement",
256
+ purpose: "Carries the deterministic replacement the CLI can apply.",
257
+ carriage: "derived",
258
+ topLevelField: "groups",
259
+ },
260
+ {
261
+ path: "groups[].suggestedReplacement.edit.{file,start,end,replacement}",
262
+ type: "deterministic edit range",
263
+ purpose: "Locates and describes the exact edit the CLI can apply.",
264
+ carriage: "derived",
265
+ topLevelField: "groups",
266
+ },
267
+ {
268
+ path: "suppressions[].{id,kind,file,line,column,code,expiresOn,attachedLine,attachedTo,directive}",
269
+ type: "directive identity, location and attachment metadata",
270
+ purpose: "Reports matched in-source governance directives.",
271
+ carriage: "none",
272
+ topLevelField: "suppressions",
273
+ },
274
+ {
275
+ path: "suppressions[].reason",
276
+ type: "authored directive reason",
277
+ purpose: "Carries the repository-authored reason for the suppression.",
278
+ carriage: "source-literal",
279
+ topLevelField: "suppressions",
280
+ },
281
+ {
282
+ path: "bypassEvents[].{code,file,line}",
283
+ type: "rule code and source location",
284
+ purpose: "Records directives that actually bypassed a finding in this run.",
285
+ carriage: "none",
286
+ topLevelField: "bypassEvents",
287
+ },
288
+ {
289
+ path: "bypassEvents[].reason",
290
+ type: "authored directive reason",
291
+ purpose: "Carries the repository-authored reason for the bypass.",
292
+ carriage: "source-literal",
293
+ topLevelField: "bypassEvents",
294
+ },
295
+ {
296
+ path: "bypassEvents[].area.{areaId,areaName,criticality,matchedGlob}",
297
+ type: "configured product-area metadata",
298
+ purpose: "Classifies the bypass by its configured product area.",
299
+ carriage: "structured-source",
300
+ topLevelField: "bypassEvents",
301
+ },
302
+ {
303
+ path: "hookEvents[].{eventId,kind,ruleId,code,file,mode,agent,timestamp,reasonClass}",
304
+ type: "bounded hook decision receipts",
305
+ purpose: "Reports deny, downgrade, suppression and fail-open decisions.",
306
+ carriage: "none",
307
+ topLevelField: "hookEvents",
308
+ },
309
+ {
310
+ path: "packageVocabularies[].{source,packageName,status,manifestPath,digest,commit}",
311
+ type: "compiled canonical-package vocabulary records",
312
+ purpose: "Publishes package vocabulary proposed by authoritative local discovery.",
313
+ carriage: "structured-source",
314
+ topLevelField: "packageVocabularies",
315
+ },
316
+ {
317
+ path: "packageVocabularies[].roots[].{name,members[]}",
318
+ type: "compiled component roots and members",
319
+ purpose: "Publishes the canonical component vocabulary discovered from the package.",
320
+ carriage: "structured-source",
321
+ topLevelField: "packageVocabularies",
322
+ },
323
+ {
324
+ path: "adoption.{metricVersion,adoptedUsages,ownableUsages}",
325
+ type: "versioned component-identity usage counts",
326
+ purpose: "Records the exact numerator and denominator used for adoption history.",
327
+ carriage: "derived",
328
+ topLevelField: "adoption",
329
+ },
330
+ {
331
+ path: "adoption.shadows[].{componentKey,displayName,canonicalTarget,usageCount}",
332
+ type: "bounded component-identity summaries",
333
+ purpose:
334
+ "Explains which shadow components are moving the adoption curve without uploading source.",
335
+ carriage: "derived",
336
+ topLevelField: "adoption",
337
+ },
338
+ {
339
+ path: "health.{metricVersion,checkedSites,violatingSites}",
340
+ type: "versioned contract-site counts",
341
+ purpose:
342
+ "Records how many sites contract-scope rules inspected and how many distinct sites carried a finding.",
343
+ carriage: "derived",
344
+ topLevelField: "health",
345
+ },
346
+ {
347
+ path: "health.populations.{componentUsages,tokenReferences}",
348
+ type: "per-population site counts",
349
+ purpose: "Names the measured populations behind the checked-site denominator.",
350
+ carriage: "derived",
351
+ topLevelField: "health",
352
+ },
353
+ {
354
+ path: "topology.areaImpact[].{areaId,areaName,beforeOpen,afterOpen,introduced,persistent,resolved}",
355
+ type: "area identifiers and before/after counts",
356
+ purpose: "Reports bounded product-area impact totals.",
357
+ carriage: "derived",
358
+ topLevelField: "topology",
359
+ },
360
+ {
361
+ path: "contractHash",
362
+ type: "64-character FCID",
363
+ purpose: "Pins the exact compiled contract enforced by the run.",
364
+ carriage: "derived",
365
+ topLevelField: "contractHash",
366
+ },
367
+ {
368
+ path: "source",
369
+ type: '"ci"',
370
+ purpose: "Identifies the report producer.",
371
+ carriage: "none",
372
+ topLevelField: "source",
373
+ },
374
+ {
375
+ path: "gateRung",
376
+ type: '"ci" | "cli"',
377
+ purpose: "Identifies the enforcement rung that produced the verdict.",
378
+ carriage: "none",
379
+ topLevelField: "gateRung",
380
+ },
381
+ {
382
+ path: "diffOnly",
383
+ type: "boolean",
384
+ purpose: "States whether the scan was restricted to changed files.",
385
+ carriage: "none",
386
+ topLevelField: "diffOnly",
387
+ },
388
+ {
389
+ path: "failOnWarnings",
390
+ type: "boolean",
391
+ purpose: "States whether warnings were gating for this run.",
392
+ carriage: "none",
393
+ topLevelField: "failOnWarnings",
394
+ },
395
+ ] as const satisfies readonly GovernanceTelemetryField[];
396
+
397
+ export const GOVERNANCE_TELEMETRY_SOURCE_DISCLOSURE =
398
+ "The report carries source text in named fields: offending literals, messages that may quote them, structured rule facts, authored suppression and bypass reasons, and compiled package vocabulary. It never uploads whole files, whole source lines or function bodies.";
package/src/index.ts CHANGED
@@ -7,6 +7,17 @@
7
7
  export { BRAND, DEFAULTS } from "./constants.js";
8
8
  export type { Brand, Defaults } from "./constants.js";
9
9
 
10
+ export {
11
+ GOVERNANCE_TELEMETRY_FIELDS,
12
+ GOVERNANCE_TELEMETRY_SOURCE_DISCLOSURE,
13
+ GOVERNANCE_TELEMETRY_TOP_LEVEL_FIELDS,
14
+ } from "./governance-telemetry.js";
15
+ export type {
16
+ GovernanceTelemetryCarriage,
17
+ GovernanceTelemetryField,
18
+ GovernanceTelemetryTopLevelField,
19
+ } from "./governance-telemetry.js";
20
+
10
21
  // Evidence vocabulary (wire-stable, browser-safe, shared with Inspect).
11
22
  export { EVIDENCE_ORDER, canBlock } from "./evidence.js";
12
23
  export type { EvidenceGrade, InspectClaim, InspectClaimTier } from "./evidence.js";
@@ -726,6 +737,12 @@ export {
726
737
  } from "./rules/index.js";
727
738
  export type { RuleTier, PresetRuleState } from "./rules/index.js";
728
739
 
740
+ // Contract-site health denominator: how many style-declaration sites the
741
+ // token-vocabulary rule actually judged. Consumed by the CLI's per-scan
742
+ // `contract-site-health:v1` metric next to the identity usage index.
743
+ export { contractTokenReferenceCensus } from "./rules/index.js";
744
+ export type { ContractTokenReferenceCensus } from "./rules/index.js";
745
+
729
746
  // Rule families — the ONE expansion of a broad `govern.rules` id (`tokens/hardcoded-values`)
730
747
  // into the concrete rules it stands for. Three copies used to disagree.
731
748
  export {
@@ -225,7 +225,11 @@ export { ruleTailwindOffScaleSpacingToken } from "./tailwind-off-scale-spacing-t
225
225
  export { ruleTailwindRawColorViaToken } from "./tailwind-raw-color-via-token.js";
226
226
  export { ruleTailwindUnknownClass } from "./tailwind-unknown-class.js";
227
227
  export { ruleTokensRequireDualFallback } from "./tokens-require-dual-fallback.js";
228
- export { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
228
+ export {
229
+ ruleTokensCssVarsMustBeDefined,
230
+ contractTokenReferenceCensus,
231
+ } from "./tokens-css-vars-must-be-defined.js";
232
+ export type { ContractTokenReferenceCensus } from "./tokens-css-vars-must-be-defined.js";
229
233
  export { ruleTokensUpstreamDrift } from "./tokens-upstream-drift.js";
230
234
  export { ruleThemeNoThemeCoupledLiteral } from "./theme-no-theme-coupled-literal.js";
231
235
  export { ruleA11yRequiredAccessibleName } from "./a11y-required-accessible-name.js";
@@ -7,7 +7,10 @@ import {
7
7
  makeStyleDeclarationFact,
8
8
  } from "../index.js";
9
9
  import type { Fact } from "../index.js";
10
- import { ruleTokensCssVarsMustBeDefined } from "./tokens-css-vars-must-be-defined.js";
10
+ import {
11
+ contractTokenReferenceCensus,
12
+ ruleTokensCssVarsMustBeDefined,
13
+ } from "./tokens-css-vars-must-be-defined.js";
11
14
 
12
15
  /**
13
16
  * ACCEPTANCE §4 (rule level) + §11 (regression lock).
@@ -242,3 +245,65 @@ describe("ruleTokensCssVarsMustBeDefined", () => {
242
245
  expect(reversed).toHaveLength(2);
243
246
  });
244
247
  });
248
+
249
+ describe("contractTokenReferenceCensus", () => {
250
+ function declAt(line: number, value: string): Fact {
251
+ const file = "src/Card.module.scss";
252
+ return makeStyleDeclarationFact({
253
+ file,
254
+ selector: ".card",
255
+ declarationPath: `.card:${line}`,
256
+ property: "color",
257
+ value,
258
+ location: { file, line, column: 2 },
259
+ });
260
+ }
261
+
262
+ it("is unmeasured (undefined) while the rule is inert — no policy fact or no vocabulary", () => {
263
+ expect(
264
+ contractTokenReferenceCensus(index([...vocabulary(), declAt(1, "var(--fui-color-accent)")]))
265
+ ).toBeUndefined();
266
+ expect(
267
+ contractTokenReferenceCensus(index([POLICY, declAt(1, "var(--fui-color-accent)")]))
268
+ ).toBeUndefined();
269
+ });
270
+
271
+ it("counts distinct judged declaration sites — clean and drifting alike, foreign excluded", () => {
272
+ const census = contractTokenReferenceCensus(
273
+ index([
274
+ POLICY,
275
+ ...vocabulary(),
276
+ declAt(1, "var(--fui-color-accent)"), // clean, judged
277
+ declAt(2, "var(--fui-color-brand)"), // drift, judged
278
+ declAt(3, "var(--swiper-theme-color)"), // foreign, never judged
279
+ declAt(4, "#39594d"), // no reference at all
280
+ ])
281
+ );
282
+ expect(census).toEqual({ checkedSites: 2 });
283
+ });
284
+
285
+ it("counts a site once regardless of how many judged references it carries", () => {
286
+ const census = contractTokenReferenceCensus(
287
+ index([
288
+ POLICY,
289
+ ...vocabulary(),
290
+ declAt(1, "var(--fui-color-accent) var(--fui-space-2) var(--fui-color-brand)"),
291
+ ])
292
+ );
293
+ expect(census).toEqual({ checkedSites: 1 });
294
+ });
295
+
296
+ it("judges only the PRIMARY var() position, exactly like the rule (#17)", () => {
297
+ // The only primary reference is foreign; the in-contract token sits in
298
+ // fallback position and must not create a checked site the rule would
299
+ // never have judged.
300
+ const census = contractTokenReferenceCensus(
301
+ index([
302
+ POLICY,
303
+ ...vocabulary(),
304
+ declAt(1, "var(--swiper-theme-color, var(--fui-color-accent))"),
305
+ ])
306
+ );
307
+ expect(census).toEqual({ checkedSites: 0 });
308
+ });
309
+ });