@usefragments/core 1.5.1 → 1.6.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.
- package/dist/{chunk-AOG4FTV6.js → chunk-WVFNDPM4.js} +448 -190
- package/dist/chunk-WVFNDPM4.js.map +1 -0
- package/dist/codes/index.d.ts +1 -1
- package/dist/codes/index.js +1 -1
- package/dist/compiled-types/index.d.ts +1 -1
- package/dist/generate/index.d.ts +1 -1
- package/dist/{governance-B88uR3Zq.d.ts → governance-DxFipN5V.d.ts} +654 -22
- package/dist/index.d.ts +678 -40
- package/dist/index.js +534 -38
- package/dist/index.js.map +1 -1
- package/dist/react-types.d.ts +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/package.json +1 -1
- package/src/__tests__/policy-exclude.test.ts +180 -0
- package/src/canonical-bridge.ts +69 -1
- package/src/canonical-direction.test.ts +118 -0
- package/src/canonical-direction.ts +43 -2
- package/src/codes/__tests__/codes.test.ts +14 -1
- package/src/codes/codes.ts +60 -0
- package/src/config.ts +20 -0
- package/src/facts/builders.ts +35 -0
- package/src/facts/compile.ts +135 -21
- package/src/facts/fact-index.ts +22 -2
- package/src/facts/facts.test.ts +19 -19
- package/src/facts/index.ts +9 -6
- package/src/facts/types.ts +45 -9
- package/src/governance-integrity.test.ts +277 -1
- package/src/governance-integrity.ts +616 -0
- package/src/governance.test.ts +20 -1
- package/src/governance.ts +131 -0
- package/src/index.ts +45 -2
- package/src/policy-exclude.ts +113 -0
- package/src/rules/families.test.ts +69 -0
- package/src/rules/families.ts +52 -0
- package/src/rules/index.ts +6 -0
- package/src/rules/jsx-preferred-import-path.ts +29 -11
- package/src/rules/rules.test.ts +125 -1
- package/src/rules/styles-no-raw-color.ts +13 -4
- package/src/rules/styles-no-raw-dimensions.ts +13 -4
- package/src/rules/styles-no-raw-spacing.test.ts +48 -0
- package/src/rules/styles-no-raw-spacing.ts +15 -7
- package/src/rules/styles-no-raw-typography.ts +13 -4
- package/src/rules/utils.ts +39 -0
- package/src/types.ts +21 -3
- package/dist/chunk-AOG4FTV6.js.map +0 -1
package/src/facts/types.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
|
|
15
15
|
import type { GovernanceSeverity } from "../governance.js";
|
|
16
16
|
import type { IdentityConfidence, IdentityState } from "../identity/classify.js";
|
|
17
|
+
import type { PolicyExclude } from "../policy-exclude.js";
|
|
17
18
|
|
|
18
19
|
// ---------------------------------------------------------------------------
|
|
19
20
|
// Branded IDs
|
|
@@ -121,34 +122,42 @@ export interface ScaleValueFact extends BaseFact {
|
|
|
121
122
|
value: number;
|
|
122
123
|
}
|
|
123
124
|
|
|
124
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Path excludes carried by a compiled policy fact. Present only when the authoring
|
|
127
|
+
* record declared them, so fact shapes are byte-identical for configs without
|
|
128
|
+
* excludes. Never part of the fact's `factId` inputs: scoping a policy off a path
|
|
129
|
+
* must not move fact ids or the finding fingerprints Cloud and baselines dedupe on.
|
|
130
|
+
*/
|
|
131
|
+
type PolicyFactExcludes = { exclude?: PolicyExclude[] };
|
|
132
|
+
|
|
133
|
+
export interface StyleRawColorForbiddenFact extends BaseFact, PolicyFactExcludes {
|
|
125
134
|
kind: "style_raw_color_forbidden";
|
|
126
135
|
except: string[];
|
|
127
136
|
prefer: "token" | "css-variable";
|
|
128
137
|
severity: GovernanceSeverity;
|
|
129
138
|
}
|
|
130
139
|
|
|
131
|
-
export interface StyleRawDimensionForbiddenFact extends BaseFact {
|
|
140
|
+
export interface StyleRawDimensionForbiddenFact extends BaseFact, PolicyFactExcludes {
|
|
132
141
|
kind: "style_raw_dimension_forbidden";
|
|
133
142
|
appliesTo: string[];
|
|
134
143
|
prefer: "token" | "css-variable";
|
|
135
144
|
severity: GovernanceSeverity;
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
export interface StylePropertyScaleFact extends BaseFact {
|
|
147
|
+
export interface StylePropertyScaleFact extends BaseFact, PolicyFactExcludes {
|
|
139
148
|
kind: "style_property_scale";
|
|
140
149
|
property: string;
|
|
141
150
|
scale: string;
|
|
142
151
|
severity: GovernanceSeverity;
|
|
143
152
|
}
|
|
144
153
|
|
|
145
|
-
export interface StyleFontSizeScaleFact extends BaseFact {
|
|
154
|
+
export interface StyleFontSizeScaleFact extends BaseFact, PolicyFactExcludes {
|
|
146
155
|
kind: "style_font_size_scale";
|
|
147
156
|
scale: string;
|
|
148
157
|
severity: GovernanceSeverity;
|
|
149
158
|
}
|
|
150
159
|
|
|
151
|
-
export interface StyleCssVarsMustBeDefinedFact extends BaseFact {
|
|
160
|
+
export interface StyleCssVarsMustBeDefinedFact extends BaseFact, PolicyFactExcludes {
|
|
152
161
|
kind: "style_css_vars_must_be_defined";
|
|
153
162
|
severity: GovernanceSeverity;
|
|
154
163
|
}
|
|
@@ -166,18 +175,18 @@ export interface ContractTokenFact extends BaseFact {
|
|
|
166
175
|
name: string;
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
export interface JsxUnknownPropsForbiddenFact extends BaseFact {
|
|
178
|
+
export interface JsxUnknownPropsForbiddenFact extends BaseFact, PolicyFactExcludes {
|
|
170
179
|
kind: "jsx_unknown_props_forbidden";
|
|
171
180
|
severity: GovernanceSeverity;
|
|
172
181
|
}
|
|
173
182
|
|
|
174
|
-
export interface JsxInlineStyleForbiddenRawFact extends BaseFact {
|
|
183
|
+
export interface JsxInlineStyleForbiddenRawFact extends BaseFact, PolicyFactExcludes {
|
|
175
184
|
kind: "jsx_inline_style_forbidden_raw";
|
|
176
185
|
property: string;
|
|
177
186
|
severity: GovernanceSeverity;
|
|
178
187
|
}
|
|
179
188
|
|
|
180
|
-
export interface JsxImportPathPreferredFact extends BaseFact {
|
|
189
|
+
export interface JsxImportPathPreferredFact extends BaseFact, PolicyFactExcludes {
|
|
181
190
|
kind: "jsx_import_path_preferred";
|
|
182
191
|
from: string;
|
|
183
192
|
to: string;
|
|
@@ -194,7 +203,7 @@ export interface JsxImportPathPreferredFact extends BaseFact {
|
|
|
194
203
|
};
|
|
195
204
|
}
|
|
196
205
|
|
|
197
|
-
export interface JsxComponentPreferredFact extends BaseFact {
|
|
206
|
+
export interface JsxComponentPreferredFact extends BaseFact, PolicyFactExcludes {
|
|
198
207
|
kind: "jsx_component_preferred";
|
|
199
208
|
from: ComponentId;
|
|
200
209
|
to: ComponentId;
|
|
@@ -343,6 +352,9 @@ export interface UsageInlineStyleFact extends BaseFact {
|
|
|
343
352
|
*/
|
|
344
353
|
valueKind: "static" | "number" | "css-variable" | "dynamic-raw";
|
|
345
354
|
value: string;
|
|
355
|
+
/** Set when `value` was resolved through a hop rather than authored inline.
|
|
356
|
+
* Additive — the fact's identity key is unchanged. */
|
|
357
|
+
valueFrom?: StyleValueProvenance;
|
|
346
358
|
}
|
|
347
359
|
|
|
348
360
|
export interface UsageTextChildFact extends BaseFact {
|
|
@@ -511,6 +523,27 @@ export interface TailwindTokenResolvedFact extends BaseFact {
|
|
|
511
523
|
* selector chain. Identity is `{ file, selector, declarationPath, property }`
|
|
512
524
|
* so the ID stays stable when other lines move.
|
|
513
525
|
*/
|
|
526
|
+
/**
|
|
527
|
+
* How a style value reached the fact when the authored text at the use site was
|
|
528
|
+
* not the value itself. Present only for `const-binding` today: the extractor
|
|
529
|
+
* followed exactly one hop to a same-file, immutable, statically-initialized
|
|
530
|
+
* `const` (`const c = '#FFC107'; sx={{ color: c }}`).
|
|
531
|
+
*
|
|
532
|
+
* Two consequences for rules, both load-bearing:
|
|
533
|
+
* - The declaration line is the evidence a reader needs — the use site only
|
|
534
|
+
* shows an identifier.
|
|
535
|
+
* - **No deterministic fix may be attached.** The authored text at the finding's
|
|
536
|
+
* location is the identifier, so any value replacement would be an edit the
|
|
537
|
+
* user never wrote.
|
|
538
|
+
*/
|
|
539
|
+
export interface StyleValueProvenance {
|
|
540
|
+
kind: "const-binding";
|
|
541
|
+
/** Identifier the use site referenced. */
|
|
542
|
+
name: string;
|
|
543
|
+
/** Where the binding was declared. */
|
|
544
|
+
location: FactLocation;
|
|
545
|
+
}
|
|
546
|
+
|
|
514
547
|
export interface StyleDeclarationFact extends BaseFact {
|
|
515
548
|
kind: "style_declaration";
|
|
516
549
|
file: string;
|
|
@@ -527,6 +560,9 @@ export interface StyleDeclarationFact extends BaseFact {
|
|
|
527
560
|
* flagging ad hoc custom properties in product code.
|
|
528
561
|
*/
|
|
529
562
|
declaredTokenSource?: boolean;
|
|
563
|
+
/** Set when `value` was resolved through a hop rather than authored inline.
|
|
564
|
+
* Additive — the fact's identity key is unchanged. */
|
|
565
|
+
valueFrom?: StyleValueProvenance;
|
|
530
566
|
}
|
|
531
567
|
|
|
532
568
|
export type UnsupportedStyleReason =
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import type { GovernanceConfig } from "./governance.js";
|
|
3
|
+
import type { GovernanceConfig, GovernanceSeverity } from "./governance.js";
|
|
4
4
|
import {
|
|
5
|
+
collidingRecordDiagnostic,
|
|
6
|
+
collidingRecordDiagnostics,
|
|
7
|
+
configRecordShape,
|
|
8
|
+
overriddenRecordSeverityDiagnostic,
|
|
9
|
+
detectOrphanGovernanceScales,
|
|
10
|
+
detectUnconsumedConfigKeys,
|
|
5
11
|
evaluateGovernanceIntegrity,
|
|
6
12
|
hasEffectiveComponentVocabulary,
|
|
7
13
|
isEffectiveCanonicalSource,
|
|
8
14
|
type GovernanceIntegrityInput,
|
|
9
15
|
} from "./governance-integrity.js";
|
|
16
|
+
import {
|
|
17
|
+
makeStyleFontSizeScaleFact,
|
|
18
|
+
makeStylePropertyScaleFact,
|
|
19
|
+
makeStyleRawColorForbiddenFact,
|
|
20
|
+
makeTokenDefinitionFact,
|
|
21
|
+
} from "./facts/index.js";
|
|
10
22
|
import { customerDefaultRuleStates } from "./rules/presets.js";
|
|
23
|
+
import { fragmentsConfigSchema } from "./schema.js";
|
|
11
24
|
|
|
12
25
|
function evaluate(
|
|
13
26
|
overrides: Partial<GovernanceIntegrityInput>
|
|
@@ -66,6 +79,245 @@ describe("hasEffectiveComponentVocabulary", () => {
|
|
|
66
79
|
});
|
|
67
80
|
});
|
|
68
81
|
|
|
82
|
+
describe("inert config diagnostics", () => {
|
|
83
|
+
it("reports stripped, passthrough, unknown-rule, and rule-field keys in stable order", () => {
|
|
84
|
+
const authored = {
|
|
85
|
+
styles: { spacing: true },
|
|
86
|
+
screenshots: { threshold: 0.1, renderer: "chromium" },
|
|
87
|
+
tokens: { include: ["tokens.css"], mystery: true },
|
|
88
|
+
govern: {
|
|
89
|
+
scales: {
|
|
90
|
+
space: { kind: "scale", unit: "px", values: [0, 4, 8], source: "legacy" },
|
|
91
|
+
},
|
|
92
|
+
rules: {
|
|
93
|
+
"styles/no-raw-color": { enabled: true, exclude: ["vendor/**"], scope: "app" },
|
|
94
|
+
"styles/not-a-real-rule": { enabled: true },
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
const parsed = fragmentsConfigSchema.parse(authored);
|
|
99
|
+
|
|
100
|
+
expect(detectUnconsumedConfigKeys(authored, parsed)).toMatchObject([
|
|
101
|
+
{ code: "FUI9004", path: "govern.rules.styles/no-raw-color.scope" },
|
|
102
|
+
{ code: "FUI9004", path: "govern.rules.styles/not-a-real-rule" },
|
|
103
|
+
{ code: "FUI9004", path: "govern.scales.space.source" },
|
|
104
|
+
{ code: "FUI9004", path: "screenshots.renderer" },
|
|
105
|
+
{ code: "FUI9004", path: "styles" },
|
|
106
|
+
{ code: "FUI9004", path: "tokens.mystery" },
|
|
107
|
+
]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("does not diagnose a rule-keyed exclude, which the scan consumes", () => {
|
|
111
|
+
const authored = {
|
|
112
|
+
govern: {
|
|
113
|
+
rules: { "styles/no-raw-color": { exclude: ["src/legacy/vendor/**"] } },
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const parsed = fragmentsConfigSchema.parse(authored);
|
|
117
|
+
|
|
118
|
+
expect(detectUnconsumedConfigKeys(authored, parsed)).toEqual([]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("reports an authored spacing scale that no effective property policy references", () => {
|
|
122
|
+
const declared: GovernanceConfig = {
|
|
123
|
+
scales: {
|
|
124
|
+
spacing: { kind: "scale", unit: "px", values: [0, 4, 8] },
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
const effective: GovernanceConfig = {
|
|
128
|
+
...declared,
|
|
129
|
+
scales: {
|
|
130
|
+
...declared.scales,
|
|
131
|
+
space: { kind: "scale", unit: "px", values: [0, 4, 8] },
|
|
132
|
+
},
|
|
133
|
+
styles: [
|
|
134
|
+
{
|
|
135
|
+
kind: "style.rawSpacing.mustMatchScale",
|
|
136
|
+
scale: "space",
|
|
137
|
+
appliesTo: ["margin", "padding"],
|
|
138
|
+
severity: "warn",
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
expect(detectOrphanGovernanceScales(declared, effective)).toEqual([
|
|
144
|
+
expect.objectContaining({
|
|
145
|
+
code: "FUI9005",
|
|
146
|
+
path: "govern.scales.spacing",
|
|
147
|
+
message: expect.stringContaining('bound to the scale named "space"'),
|
|
148
|
+
}),
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("does not report a scale referenced by an effective property policy", () => {
|
|
153
|
+
const policy: GovernanceConfig = {
|
|
154
|
+
scales: {
|
|
155
|
+
space: { kind: "scale", unit: "px", values: [0, 4, 8] },
|
|
156
|
+
},
|
|
157
|
+
styles: [
|
|
158
|
+
{
|
|
159
|
+
kind: "style.rawSpacing.mustMatchScale",
|
|
160
|
+
scale: "space",
|
|
161
|
+
appliesTo: ["margin"],
|
|
162
|
+
severity: "warn",
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
expect(detectOrphanGovernanceScales(policy, policy)).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("accepts govern.ci.failOnInert as a consumed key", () => {
|
|
171
|
+
const authored = { govern: { ci: { failOnInert: true } } };
|
|
172
|
+
|
|
173
|
+
expect(detectUnconsumedConfigKeys(authored, fragmentsConfigSchema.parse(authored))).toEqual([]);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("collidingRecordDiagnostic", () => {
|
|
178
|
+
const scaleFact = (property: string, scale: string, severity: GovernanceSeverity) =>
|
|
179
|
+
makeStylePropertyScaleFact({ property, scale, severity });
|
|
180
|
+
|
|
181
|
+
it("names the enforced and dropped settings of a colliding config record", () => {
|
|
182
|
+
const kept = scaleFact("padding", "space", "warn");
|
|
183
|
+
const skipped = scaleFact("padding", "myScale", "error");
|
|
184
|
+
|
|
185
|
+
expect(collidingRecordDiagnostic({ kept, skipped })).toEqual({
|
|
186
|
+
code: "FUI9007",
|
|
187
|
+
kind: "colliding-record",
|
|
188
|
+
severity: "warn",
|
|
189
|
+
path: "govern.styles[style.rawSpacing.mustMatchScale property=padding]",
|
|
190
|
+
message: expect.stringContaining("scale=space") as unknown as string,
|
|
191
|
+
});
|
|
192
|
+
expect(collidingRecordDiagnostic({ kept, skipped })?.message).toContain("scale=myScale");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("names the colliding record kind for singleton records", () => {
|
|
196
|
+
expect(
|
|
197
|
+
collidingRecordDiagnostic({
|
|
198
|
+
kept: makeStyleRawColorForbiddenFact({ except: [], prefer: "token", severity: "warn" }),
|
|
199
|
+
skipped: makeStyleRawColorForbiddenFact({
|
|
200
|
+
except: [],
|
|
201
|
+
prefer: "css-variable",
|
|
202
|
+
severity: "error",
|
|
203
|
+
}),
|
|
204
|
+
})?.path
|
|
205
|
+
).toBe("govern.styles[style.rawColors.forbid]");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Two singleton-keyed kinds live in one section. If their diagnostics share a path,
|
|
209
|
+
// the (code, path) dedupe every consumer runs discards one collision entirely — the
|
|
210
|
+
// silent drop this code exists to end, reintroduced one layer up.
|
|
211
|
+
it("gives two different singleton kinds in one section distinct identities", () => {
|
|
212
|
+
const rawColor = collidingRecordDiagnostic({
|
|
213
|
+
kept: makeStyleRawColorForbiddenFact({ except: [], prefer: "token", severity: "warn" }),
|
|
214
|
+
skipped: makeStyleRawColorForbiddenFact({
|
|
215
|
+
except: [],
|
|
216
|
+
prefer: "css-variable",
|
|
217
|
+
severity: "error",
|
|
218
|
+
}),
|
|
219
|
+
});
|
|
220
|
+
const fontSize = collidingRecordDiagnostic({
|
|
221
|
+
kept: makeStyleFontSizeScaleFact({ scale: "type", severity: "warn" }),
|
|
222
|
+
skipped: makeStyleFontSizeScaleFact({ scale: "legacyType", severity: "error" }),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
expect(rawColor?.path).not.toBe(fontSize?.path);
|
|
226
|
+
expect(fontSize?.path).toBe("govern.styles[style.fontSize.mustMatchScale]");
|
|
227
|
+
expect(fontSize?.message).toContain("style.fontSize.mustMatchScale");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("leaves token-channel collisions to the token diagnostics, not config truth", () => {
|
|
231
|
+
const token = (value: string) =>
|
|
232
|
+
makeTokenDefinitionFact({
|
|
233
|
+
name: "--fui-color-accent",
|
|
234
|
+
value,
|
|
235
|
+
location: { file: "tokens/base.css", line: 1, column: 1 },
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
expect(collidingRecordDiagnostic({ kept: token("#000"), skipped: token("#fff") })).toBeNull();
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("ignores collisions between different fact kinds", () => {
|
|
242
|
+
expect(
|
|
243
|
+
collidingRecordDiagnostic({
|
|
244
|
+
kept: scaleFact("padding", "space", "warn"),
|
|
245
|
+
skipped: makeStyleFontSizeScaleFact({ scale: "type", severity: "warn" }),
|
|
246
|
+
})
|
|
247
|
+
).toBeNull();
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Three records on one fact id produce TWO conflicts that render the same path. Emitted
|
|
251
|
+
// separately, the (code, path) dedupe every consumer runs kept only the last pair and
|
|
252
|
+
// the intermediate record vanished — the silence this code exists to end, one layer up.
|
|
253
|
+
it("aggregates a three-way collision into one diagnostic that names every drop", () => {
|
|
254
|
+
const diagnostics = collidingRecordDiagnostics([
|
|
255
|
+
{
|
|
256
|
+
kept: scaleFact("padding", "space", "warn"),
|
|
257
|
+
skipped: scaleFact("padding", "myScale", "error"),
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
kept: scaleFact("padding", "space", "warn"),
|
|
261
|
+
skipped: scaleFact("padding", "legacy", "info"),
|
|
262
|
+
},
|
|
263
|
+
]);
|
|
264
|
+
|
|
265
|
+
expect(diagnostics).toHaveLength(1);
|
|
266
|
+
expect(diagnostics[0]?.path).toBe(
|
|
267
|
+
"govern.styles[style.rawSpacing.mustMatchScale property=padding]"
|
|
268
|
+
);
|
|
269
|
+
expect(diagnostics[0]?.message).toContain("has 3 `style.rawSpacing.mustMatchScale` records");
|
|
270
|
+
expect(diagnostics[0]?.message).toContain("scale=myScale");
|
|
271
|
+
expect(diagnostics[0]?.message).toContain("scale=legacy");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("keeps distinct fact ids in distinct diagnostics", () => {
|
|
275
|
+
expect(
|
|
276
|
+
collidingRecordDiagnostics([
|
|
277
|
+
{
|
|
278
|
+
kept: scaleFact("padding", "space", "warn"),
|
|
279
|
+
skipped: scaleFact("padding", "myScale", "error"),
|
|
280
|
+
},
|
|
281
|
+
{ kept: scaleFact("gap", "space", "warn"), skipped: scaleFact("gap", "myScale", "error") },
|
|
282
|
+
]).map((diagnostic) => diagnostic.path)
|
|
283
|
+
).toEqual([
|
|
284
|
+
"govern.styles[style.rawSpacing.mustMatchScale property=padding]",
|
|
285
|
+
"govern.styles[style.rawSpacing.mustMatchScale property=gap]",
|
|
286
|
+
]);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("overriddenRecordSeverityDiagnostic", () => {
|
|
291
|
+
it("names the record, the override that outranked it, and both severities", () => {
|
|
292
|
+
expect(
|
|
293
|
+
overriddenRecordSeverityDiagnostic({
|
|
294
|
+
ruleId: "styles/no-raw-color",
|
|
295
|
+
source: "govern.rules[tokens/hardcoded-values]",
|
|
296
|
+
section: "govern.styles",
|
|
297
|
+
record: "style.rawColors.forbid",
|
|
298
|
+
authored: "serious",
|
|
299
|
+
enforced: "moderate",
|
|
300
|
+
})
|
|
301
|
+
).toEqual({
|
|
302
|
+
code: "FUI9008",
|
|
303
|
+
kind: "overridden-record-severity",
|
|
304
|
+
severity: "warn",
|
|
305
|
+
path: "govern.styles[style.rawColors.forbid]",
|
|
306
|
+
message: expect.stringContaining(
|
|
307
|
+
"govern.rules[tokens/hardcoded-values]"
|
|
308
|
+
) as unknown as string,
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("reads the authoring record off the one config-record table", () => {
|
|
313
|
+
expect(configRecordShape("style_raw_color_forbidden")).toEqual({
|
|
314
|
+
section: "govern.styles",
|
|
315
|
+
record: "style.rawColors.forbid",
|
|
316
|
+
});
|
|
317
|
+
expect(configRecordShape("token_definition")).toBeNull();
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
69
321
|
describe("evaluateGovernanceIntegrity", () => {
|
|
70
322
|
it("1. no policy at all → inert, not fatal, not blocking-capable", () => {
|
|
71
323
|
const verdict = evaluate({ policy: undefined, policySource: "none", declared: false });
|
|
@@ -207,4 +459,28 @@ describe("evaluateGovernanceIntegrity", () => {
|
|
|
207
459
|
// degraded is enforceable, so not CI-fatal
|
|
208
460
|
expect(verdict.fatalForCi).toBe(false);
|
|
209
461
|
});
|
|
462
|
+
|
|
463
|
+
it("includes inert config diagnostics in the doctor roster without changing integrity status", () => {
|
|
464
|
+
const policy: GovernanceConfig = {
|
|
465
|
+
rules: { "styles/no-raw-color": { enabled: true, severity: "warn" } },
|
|
466
|
+
};
|
|
467
|
+
const baseline = evaluate({ policy, policySource: "config", declared: true });
|
|
468
|
+
const withDiagnostic = evaluate({
|
|
469
|
+
policy,
|
|
470
|
+
policySource: "config",
|
|
471
|
+
declared: true,
|
|
472
|
+
configDiagnostics: Array.from({ length: 2 }, () => ({
|
|
473
|
+
code: "FUI9005",
|
|
474
|
+
kind: "orphan-scale",
|
|
475
|
+
severity: "warn",
|
|
476
|
+
path: "govern.scales.spacing",
|
|
477
|
+
message: "orphan",
|
|
478
|
+
})),
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
expect(withDiagnostic.status).toBe(baseline.status);
|
|
482
|
+
expect(withDiagnostic.fatalForCi).toBe(baseline.fatalForCi);
|
|
483
|
+
expect(withDiagnostic.roster).toMatchObject({ active: 1, inert: 1 });
|
|
484
|
+
expect(withDiagnostic.configDiagnostics).toHaveLength(1);
|
|
485
|
+
});
|
|
210
486
|
});
|