@brandlab-digital/conformance 1.0.0-rc.2 → 1.0.0-rc.21
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/analytics-contract.d.ts +20 -0
- package/dist/analytics-contract.d.ts.map +1 -0
- package/dist/analytics-contract.js +211 -0
- package/dist/analytics-contract.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +91 -20
- package/dist/cli.js.map +1 -1
- package/dist/project-config-v3.d.ts +9 -1
- package/dist/project-config-v3.d.ts.map +1 -1
- package/dist/project-config-v3.js +246 -37
- package/dist/project-config-v3.js.map +1 -1
- package/dist/project-config.d.ts +1 -1
- package/dist/project-config.d.ts.map +1 -1
- package/dist/project-config.js +1 -1
- package/dist/project-config.js.map +1 -1
- package/dist/project-documentation.d.ts +28 -0
- package/dist/project-documentation.d.ts.map +1 -0
- package/dist/project-documentation.js +398 -0
- package/dist/project-documentation.js.map +1 -0
- package/dist/project.d.ts +9 -2
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +290 -44
- package/dist/project.js.map +1 -1
- package/dist/release-catalog.d.ts.map +1 -1
- package/dist/release-catalog.js +1405 -0
- package/dist/release-catalog.js.map +1 -1
- package/dist/release-contract-compatibility.d.ts +133 -0
- package/dist/release-contract-compatibility.d.ts.map +1 -1
- package/dist/release-contract-compatibility.js +133 -0
- package/dist/release-contract-compatibility.js.map +1 -1
- package/dist/release-manifest.d.ts +1 -0
- package/dist/release-manifest.d.ts.map +1 -1
- package/dist/release-manifest.js +7 -0
- package/dist/release-manifest.js.map +1 -1
- package/dist/rendered-content.d.ts +38 -0
- package/dist/rendered-content.d.ts.map +1 -0
- package/dist/rendered-content.js +149 -0
- package/dist/rendered-content.js.map +1 -0
- package/dist/source-safety-contracts.d.ts +1 -1
- package/dist/source-safety-contracts.d.ts.map +1 -1
- package/dist/source-safety-contracts.js +5 -1
- package/dist/source-safety-contracts.js.map +1 -1
- package/dist/verification-policy.d.ts +99 -0
- package/dist/verification-policy.d.ts.map +1 -0
- package/dist/verification-policy.js +219 -0
- package/dist/verification-policy.js.map +1 -0
- package/package.json +20 -2
package/dist/project.js
CHANGED
|
@@ -2,20 +2,35 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join, relative, resolve } from "node:path";
|
|
3
3
|
import { evaluateSharedPackageVersionSpecs, findForbiddenBrandlabPatterns, } from "./vitest.js";
|
|
4
4
|
import { DEFAULT_BRANDLAB_RELEASE_CATALOG, } from "./release-catalog.js";
|
|
5
|
-
import { BRANDLAB_CONFIG_V3_RULESET_ID, inspectBrandlabConfigV3, } from "./project-config-v3.js";
|
|
5
|
+
import { BRANDLAB_CONFIG_V3_RULESET_ID, BRANDLAB_CONFIG_V4_RULESET_ID, BRANDLAB_CONFIG_V5_RULESET_ID, inspectBrandlabConfigV3, } from "./project-config-v3.js";
|
|
6
6
|
import { BRANDLAB_CONFIG_V2_RULESET_ID, inspectBrandlabConfigV2 as inspectImmutableBrandlabConfigV2, } from "./project-config-v2-contract.js";
|
|
7
7
|
import { BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS, BRANDLAB_SCHEMA_HYGIENE_V3_RULESET_ID, } from "./schema-hygiene.js";
|
|
8
8
|
import { BRANDLAB_SOURCE_SAFETY_V2_PATTERNS, BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID, sourceSafetyRulesetForArchitecture, targetOnlySourceSafetyPatterns, } from "./source-safety-contracts.js";
|
|
9
9
|
import { inspectLegacyArchitectureContract, legacyArchitectureRulesetId, targetOnlyLegacyArchitectureIssues, } from "./legacy-architecture-contracts.js";
|
|
10
10
|
import { BRANDLAB_SHARED_PACKAGE_NAMES, compareBrandlabSemver, digestBrandlabAuthority, isBrandlabSemver, validateBrandlabReleaseCatalog, validateBrandlabRuntimeReleaseCatalog, validateBrandlabRuntimeReleaseManifestSubsetAgainstCatalog, validateBrandlabSupportPolicy, } from "./release-manifest.js";
|
|
11
|
+
import { BRANDLAB_PROJECT_DOCUMENTATION_RULESET_ID, inspectBrandlabProjectDocumentation, } from "./project-documentation.js";
|
|
12
|
+
import { BRANDLAB_ANALYTICS_V3_RULESET_ID, inspectBrandlabAnalyticsV3, } from "./analytics-contract.js";
|
|
13
|
+
import { applyBrandlabVerificationPolicy, BRANDLAB_VERIFICATION_POLICY_VERSION, summarizeVerificationFindings, } from "./verification-policy.js";
|
|
11
14
|
export const BRANDLAB_CONFORMANCE_SCHEMA_VERSION = "brandlab.conformance.v1";
|
|
12
|
-
export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "
|
|
15
|
+
export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "simplified-v1";
|
|
13
16
|
export const BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID = "version-aware-architecture-dispatch-v1";
|
|
14
17
|
const LEGACY_ARCHITECTURE_EVALUATOR_CONTRACT = "architecture-v2";
|
|
15
18
|
const LEGACY_CONFIGURATION_CONTRACT = "legacy-config-v1";
|
|
16
19
|
const CONFIGURATION_V2_CONTRACT = "project-config-v2";
|
|
17
20
|
const CONFIGURATION_V3_CONTRACT = "project-config-v3";
|
|
21
|
+
const CONFIGURATION_V4_CONTRACT = "project-config-v4";
|
|
22
|
+
const CONFIGURATION_V5_CONTRACT = "project-config-v5";
|
|
18
23
|
const SEO_V3_CONTRACT = "seo-v3";
|
|
24
|
+
const SEO_V4_CONTRACT = "seo-v4";
|
|
25
|
+
const ARCHITECTURE_V5_CONTRACT = "architecture-v5";
|
|
26
|
+
const ANALYTICS_V3_CONTRACT = "analytics-v3";
|
|
27
|
+
const ANALYTICS_V4_CONTRACT = "analytics-v4";
|
|
28
|
+
function isSeoV3Compatible(contract) {
|
|
29
|
+
return contract === SEO_V3_CONTRACT || contract === SEO_V4_CONTRACT;
|
|
30
|
+
}
|
|
31
|
+
function isAnalyticsV3Compatible(contract) {
|
|
32
|
+
return contract === ANALYTICS_V3_CONTRACT || contract === ANALYTICS_V4_CONTRACT;
|
|
33
|
+
}
|
|
19
34
|
const RUNTIME_PACKAGE_FIELDS = [
|
|
20
35
|
["next", "next"],
|
|
21
36
|
["react", "react"],
|
|
@@ -59,7 +74,6 @@ export function evaluateBrandlabProject(options) {
|
|
|
59
74
|
installedAttestation: installedRelease
|
|
60
75
|
? releaseManifestByVersion.get(installedRelease) ?? null
|
|
61
76
|
: null,
|
|
62
|
-
releaseAttestationsRequired: options.releaseManifests !== undefined,
|
|
63
77
|
supportPolicy,
|
|
64
78
|
});
|
|
65
79
|
addMigrationFindings({
|
|
@@ -79,6 +93,35 @@ export function evaluateBrandlabProject(options) {
|
|
|
79
93
|
targetRelease,
|
|
80
94
|
architecture: options.architecture,
|
|
81
95
|
});
|
|
96
|
+
const documentationFingerprint = evaluateApplicableProjectDocumentationContracts({
|
|
97
|
+
root,
|
|
98
|
+
findings,
|
|
99
|
+
installedManifest,
|
|
100
|
+
targetManifest,
|
|
101
|
+
});
|
|
102
|
+
const analyticsFingerprint = evaluateApplicableAnalyticsContracts({
|
|
103
|
+
root,
|
|
104
|
+
findings,
|
|
105
|
+
installedManifest,
|
|
106
|
+
targetManifest,
|
|
107
|
+
});
|
|
108
|
+
if (installedManifest) {
|
|
109
|
+
addContactUploadFilenameFindings({
|
|
110
|
+
root,
|
|
111
|
+
findings,
|
|
112
|
+
manifest: installedManifest,
|
|
113
|
+
classification: "maintenance",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if (targetManifest &&
|
|
117
|
+
targetManifest.releaseVersion !== installedManifest?.releaseVersion) {
|
|
118
|
+
addContactUploadFilenameFindings({
|
|
119
|
+
root,
|
|
120
|
+
findings,
|
|
121
|
+
manifest: targetManifest,
|
|
122
|
+
classification: "migration",
|
|
123
|
+
});
|
|
124
|
+
}
|
|
82
125
|
const sourceRoot = join(root, "src");
|
|
83
126
|
const installedSourceSafety = sourceSafetyRulesetForArchitecture(installedManifest?.contracts.architecture);
|
|
84
127
|
const targetSourceSafety = sourceSafetyRulesetForArchitecture(targetManifest?.contracts.architecture);
|
|
@@ -140,17 +183,17 @@ export function evaluateBrandlabProject(options) {
|
|
|
140
183
|
},
|
|
141
184
|
});
|
|
142
185
|
}
|
|
143
|
-
if (installedManifest?.contracts.seo
|
|
186
|
+
if (isSeoV3Compatible(installedManifest?.contracts.seo)) {
|
|
144
187
|
addSourcePatternFindings({
|
|
145
188
|
root,
|
|
146
189
|
sourceRoot,
|
|
147
190
|
findings,
|
|
148
191
|
patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
|
|
149
192
|
classification: "maintenance",
|
|
150
|
-
contract: { name: "seo", version:
|
|
193
|
+
contract: { name: "seo", version: installedManifest.contracts.seo },
|
|
151
194
|
});
|
|
152
195
|
}
|
|
153
|
-
if (targetManifest?.contracts.seo
|
|
196
|
+
if (isSeoV3Compatible(targetManifest?.contracts.seo) &&
|
|
154
197
|
targetManifest.releaseVersion !== installedManifest?.releaseVersion) {
|
|
155
198
|
addSourcePatternFindings({
|
|
156
199
|
root,
|
|
@@ -158,11 +201,14 @@ export function evaluateBrandlabProject(options) {
|
|
|
158
201
|
findings,
|
|
159
202
|
patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
|
|
160
203
|
classification: "migration",
|
|
161
|
-
contract: { name: "seo", version:
|
|
204
|
+
contract: { name: "seo", version: targetManifest.contracts.seo },
|
|
162
205
|
});
|
|
163
206
|
}
|
|
164
207
|
}
|
|
165
208
|
const sortedFindings = findings.sort(compareFindings);
|
|
209
|
+
const policyResult = applyBrandlabVerificationPolicy(sortedFindings);
|
|
210
|
+
const operationalFindings = policyResult.operationalFindings.sort(compareFindings);
|
|
211
|
+
const diagnosticFindings = policyResult.diagnostics.sort(compareFindings);
|
|
166
212
|
const authorityDigest = digestBrandlabAuthority({
|
|
167
213
|
releaseCatalog,
|
|
168
214
|
supportPolicy,
|
|
@@ -172,14 +218,25 @@ export function evaluateBrandlabProject(options) {
|
|
|
172
218
|
installed: installedManifest?.contracts ?? null,
|
|
173
219
|
target: targetManifest?.contracts ?? null,
|
|
174
220
|
evaluator: BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID,
|
|
221
|
+
verificationPolicy: BRANDLAB_VERIFICATION_POLICY_VERSION,
|
|
175
222
|
rulesets: {
|
|
176
223
|
configurationV2: BRANDLAB_CONFIG_V2_RULESET_ID,
|
|
177
224
|
configurationV3: BRANDLAB_CONFIG_V3_RULESET_ID,
|
|
225
|
+
configurationV4: BRANDLAB_CONFIG_V4_RULESET_ID,
|
|
226
|
+
configurationV5: BRANDLAB_CONFIG_V5_RULESET_ID,
|
|
178
227
|
seoV3: BRANDLAB_SCHEMA_HYGIENE_V3_RULESET_ID,
|
|
179
228
|
installedSourceSafety: installedSourceSafety?.id ?? null,
|
|
180
229
|
targetSourceSafety: targetSourceSafety?.id ?? null,
|
|
181
230
|
installedLegacyArchitecture: legacyArchitectureRulesetId(installedManifest?.contracts.architecture),
|
|
182
231
|
targetLegacyArchitecture: legacyArchitectureRulesetId(targetManifest?.contracts.architecture),
|
|
232
|
+
projectDocumentation: installedManifest?.contracts.architecture === ARCHITECTURE_V5_CONTRACT ||
|
|
233
|
+
targetManifest?.contracts.architecture === ARCHITECTURE_V5_CONTRACT
|
|
234
|
+
? BRANDLAB_PROJECT_DOCUMENTATION_RULESET_ID
|
|
235
|
+
: null,
|
|
236
|
+
analyticsV3: isAnalyticsV3Compatible(installedManifest?.contracts.analytics) ||
|
|
237
|
+
isAnalyticsV3Compatible(targetManifest?.contracts.analytics)
|
|
238
|
+
? BRANDLAB_ANALYTICS_V3_RULESET_ID
|
|
239
|
+
: null,
|
|
183
240
|
},
|
|
184
241
|
});
|
|
185
242
|
const implementationFingerprint = digestBrandlabAuthority({
|
|
@@ -187,7 +244,16 @@ export function evaluateBrandlabProject(options) {
|
|
|
187
244
|
packageLock: packageLock.fingerprint,
|
|
188
245
|
brandlabConfig: readOptionalText(join(root, "brandlab.config.ts")),
|
|
189
246
|
architecture: architectureFingerprint,
|
|
190
|
-
|
|
247
|
+
documentation: documentationFingerprint,
|
|
248
|
+
analytics: analyticsFingerprint,
|
|
249
|
+
evidence: operationalFindings.map(({ ruleId, classification, severity, path, line }) => ({
|
|
250
|
+
ruleId,
|
|
251
|
+
classification,
|
|
252
|
+
severity,
|
|
253
|
+
path: path ?? null,
|
|
254
|
+
line: line ?? null,
|
|
255
|
+
})),
|
|
256
|
+
diagnostics: diagnosticFindings.map(({ ruleId, classification, severity, path, line }) => ({
|
|
191
257
|
ruleId,
|
|
192
258
|
classification,
|
|
193
259
|
severity,
|
|
@@ -205,11 +271,98 @@ export function evaluateBrandlabProject(options) {
|
|
|
205
271
|
installedRelease,
|
|
206
272
|
targetRelease,
|
|
207
273
|
findings: sortedFindings,
|
|
274
|
+
operationalFindings,
|
|
275
|
+
diagnostics: {
|
|
276
|
+
findings: diagnosticFindings,
|
|
277
|
+
maintenance: summarizeVerificationFindings(diagnosticFindings, "maintenance"),
|
|
278
|
+
migration: summarizeVerificationFindings(diagnosticFindings, "migration"),
|
|
279
|
+
advisory: summarizeVerificationFindings(diagnosticFindings, "advisory"),
|
|
280
|
+
},
|
|
281
|
+
verification: policyResult.verification,
|
|
208
282
|
maintenance: summarize(sortedFindings, "maintenance"),
|
|
209
283
|
migration: summarize(sortedFindings, "migration"),
|
|
210
284
|
advisory: summarize(sortedFindings, "advisory"),
|
|
211
285
|
};
|
|
212
286
|
}
|
|
287
|
+
function evaluateApplicableProjectDocumentationContracts(options) {
|
|
288
|
+
const installedArchitecture = options.installedManifest?.contracts.architecture ?? null;
|
|
289
|
+
const targetArchitecture = options.targetManifest?.contracts.architecture ?? null;
|
|
290
|
+
if (installedArchitecture !== ARCHITECTURE_V5_CONTRACT &&
|
|
291
|
+
targetArchitecture !== ARCHITECTURE_V5_CONTRACT) {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
const report = inspectBrandlabProjectDocumentation(options.root);
|
|
295
|
+
if (installedArchitecture === ARCHITECTURE_V5_CONTRACT) {
|
|
296
|
+
for (const issue of report.issues) {
|
|
297
|
+
options.findings.push(mapProjectDocumentationIssue(issue, "maintenance"));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else if (targetArchitecture === ARCHITECTURE_V5_CONTRACT) {
|
|
301
|
+
for (const issue of report.issues) {
|
|
302
|
+
options.findings.push(mapProjectDocumentationIssue(issue, "migration"));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
rulesetId: report.rulesetId,
|
|
307
|
+
files: report.files,
|
|
308
|
+
applicableRuleIds: report.issues.map((issue) => issue.ruleId).sort(),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function mapProjectDocumentationIssue(issue, classification) {
|
|
312
|
+
return {
|
|
313
|
+
ruleId: issue.ruleId,
|
|
314
|
+
surface: issue.surface,
|
|
315
|
+
classification,
|
|
316
|
+
severity: issue.severity,
|
|
317
|
+
contract: { name: "architecture", version: ARCHITECTURE_V5_CONTRACT },
|
|
318
|
+
evidenceKind: issue.evidenceKind,
|
|
319
|
+
...(issue.path ? { path: issue.path } : {}),
|
|
320
|
+
...(issue.line ? { line: issue.line } : {}),
|
|
321
|
+
expected: issue.expected,
|
|
322
|
+
actual: issue.actual,
|
|
323
|
+
remediation: issue.remediation,
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function evaluateApplicableAnalyticsContracts(options) {
|
|
327
|
+
const installedAnalytics = options.installedManifest?.contracts.analytics ?? null;
|
|
328
|
+
const targetAnalytics = options.targetManifest?.contracts.analytics ?? null;
|
|
329
|
+
if (!isAnalyticsV3Compatible(installedAnalytics) &&
|
|
330
|
+
!isAnalyticsV3Compatible(targetAnalytics)) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
const inspection = inspectBrandlabAnalyticsV3(options.root);
|
|
334
|
+
if (isAnalyticsV3Compatible(installedAnalytics)) {
|
|
335
|
+
for (const issue of inspection.issues) {
|
|
336
|
+
options.findings.push(mapAnalyticsV3Issue(issue, "maintenance", installedAnalytics));
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (isAnalyticsV3Compatible(targetAnalytics)) {
|
|
340
|
+
for (const issue of inspection.issues) {
|
|
341
|
+
options.findings.push(mapAnalyticsV3Issue(issue, "migration", targetAnalytics));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return {
|
|
345
|
+
rulesetId: inspection.rulesetId,
|
|
346
|
+
websiteCallsDeclared: inspection.websiteCallsDeclared,
|
|
347
|
+
leadDiagnosticsDeclared: inspection.leadDiagnosticsDeclared,
|
|
348
|
+
applicableRuleIds: inspection.issues.map((issue) => issue.ruleId).sort(),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
function mapAnalyticsV3Issue(issue, classification, contractVersion) {
|
|
352
|
+
return {
|
|
353
|
+
ruleId: issue.ruleId,
|
|
354
|
+
surface: issue.surface,
|
|
355
|
+
classification,
|
|
356
|
+
severity: issue.severity,
|
|
357
|
+
contract: { name: "analytics", version: contractVersion },
|
|
358
|
+
evidenceKind: issue.evidenceKind,
|
|
359
|
+
...(issue.path ? { path: issue.path } : {}),
|
|
360
|
+
...(issue.line ? { line: issue.line } : {}),
|
|
361
|
+
expected: issue.expected,
|
|
362
|
+
actual: issue.actual,
|
|
363
|
+
remediation: issue.remediation,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
213
366
|
function addSourcePatternFindings(options) {
|
|
214
367
|
for (const match of findForbiddenBrandlabPatterns({
|
|
215
368
|
roots: [options.sourceRoot],
|
|
@@ -231,9 +384,9 @@ function addSourcePatternFindings(options) {
|
|
|
231
384
|
}
|
|
232
385
|
}
|
|
233
386
|
export function shouldFailBrandlabConformance(result) {
|
|
234
|
-
if (result.maintenance.blockers > 0)
|
|
387
|
+
if (result.verification.maintenance.blockers > 0)
|
|
235
388
|
return true;
|
|
236
|
-
return result.mode === "preflight" && result.migration.blockers > 0;
|
|
389
|
+
return result.mode === "preflight" && result.verification.migration.blockers > 0;
|
|
237
390
|
}
|
|
238
391
|
export function detectInstalledBrandlabRelease(packageJson) {
|
|
239
392
|
if (!packageJson)
|
|
@@ -325,17 +478,6 @@ function addReleaseMaintenanceFindings(options) {
|
|
|
325
478
|
remediation: "Use a supported exact release or add and verify its external manifest and runtime projection.",
|
|
326
479
|
}));
|
|
327
480
|
}
|
|
328
|
-
if (options.releaseAttestationsRequired &&
|
|
329
|
-
options.installedManifest &&
|
|
330
|
-
!options.installedAttestation) {
|
|
331
|
-
options.findings.push(releaseFinding({
|
|
332
|
-
ruleId: "brandlab-release-attestation-missing",
|
|
333
|
-
severity: "blocker",
|
|
334
|
-
expected: `Immutable registry attestation for ${installedRelease}`,
|
|
335
|
-
actual: "The operational release-manifest set does not attest the installed release.",
|
|
336
|
-
remediation: "Load the installed release manifest referenced by support policy before accepting package provenance.",
|
|
337
|
-
}));
|
|
338
|
-
}
|
|
339
481
|
addInstalledPackageLockFindings({
|
|
340
482
|
findings: options.findings,
|
|
341
483
|
packageJson,
|
|
@@ -550,16 +692,6 @@ function addInstalledPackageLockFindings(options) {
|
|
|
550
692
|
}
|
|
551
693
|
const registryAttestation = options.installedAttestation?.packages[packageName].registry ?? null;
|
|
552
694
|
const expectedIntegrity = registryAttestation?.integrity ?? null;
|
|
553
|
-
if (registryAttestation &&
|
|
554
|
-
(registryAttestation.state !== "verified" || !expectedIntegrity)) {
|
|
555
|
-
options.findings.push(releaseFinding({
|
|
556
|
-
ruleId: "brandlab-release-registry-attestation-unverified",
|
|
557
|
-
severity: "blocker",
|
|
558
|
-
expected: `${packageName} with verified immutable npm registry integrity`,
|
|
559
|
-
actual: `The ${options.installedRelease} release manifest does not contain verified registry integrity for ${packageName}.`,
|
|
560
|
-
remediation: "Do not adopt this release until publication evidence is finalized and independently verified.",
|
|
561
|
-
}));
|
|
562
|
-
}
|
|
563
695
|
if (expectedIntegrity && resolvedPackage?.integrity !== expectedIntegrity) {
|
|
564
696
|
options.findings.push(releaseFinding({
|
|
565
697
|
ruleId: "brandlab-release-lock-integrity-mismatch",
|
|
@@ -572,9 +704,8 @@ function addInstalledPackageLockFindings(options) {
|
|
|
572
704
|
path: "package-lock.json",
|
|
573
705
|
}));
|
|
574
706
|
}
|
|
575
|
-
if (
|
|
576
|
-
(
|
|
577
|
-
!isApprovedBrandlabRegistryResolution(resolvedPackage.resolved, packageName, options.installedRelease))) {
|
|
707
|
+
if (!resolvedPackage?.resolved ||
|
|
708
|
+
!isApprovedBrandlabRegistryResolution(resolvedPackage.resolved, packageName, options.installedRelease)) {
|
|
578
709
|
options.findings.push(releaseFinding({
|
|
579
710
|
ruleId: "brandlab-release-lock-registry-source-mismatch",
|
|
580
711
|
severity: "blocker",
|
|
@@ -674,6 +805,13 @@ function addInstalledRuntimeFindings(options) {
|
|
|
674
805
|
path: "package.json",
|
|
675
806
|
}));
|
|
676
807
|
}
|
|
808
|
+
addSharpRuntimeFindings({
|
|
809
|
+
findings: options.findings,
|
|
810
|
+
packageJson: options.packageJson,
|
|
811
|
+
packageLock: options.packageLock,
|
|
812
|
+
manifest: options.manifest,
|
|
813
|
+
classification: "maintenance",
|
|
814
|
+
});
|
|
677
815
|
}
|
|
678
816
|
function addTargetRuntimeFindings(options) {
|
|
679
817
|
if (!options.packageJson)
|
|
@@ -713,6 +851,16 @@ function addTargetRuntimeFindings(options) {
|
|
|
713
851
|
if (options.packageJson.packageManager !== options.targetManifest.runtime.packageManager) {
|
|
714
852
|
runtimeMismatches.push(`packageManager(expected=${options.targetManifest.runtime.packageManager},actual=${options.packageJson.packageManager ?? "missing"})`);
|
|
715
853
|
}
|
|
854
|
+
const targetSharp = options.targetManifest.runtime.sharp;
|
|
855
|
+
if (targetSharp) {
|
|
856
|
+
const actualOverride = options.packageJson.overrides?.sharp ?? null;
|
|
857
|
+
const sharpResolutions = options.packageLock.sharpResolutions;
|
|
858
|
+
if (actualOverride !== targetSharp ||
|
|
859
|
+
sharpResolutions.length !== 1 ||
|
|
860
|
+
sharpResolutions[0]?.version !== targetSharp) {
|
|
861
|
+
runtimeMismatches.push(`sharp(expected=${targetSharp},override=${actualOverride ?? "missing"},resolutions=${formatSharpResolutions(sharpResolutions)})`);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
716
864
|
if (runtimeMismatches.length > 0) {
|
|
717
865
|
options.findings.push(migrationRuntimeFinding({
|
|
718
866
|
ruleId: "brandlab-migration-runtime-baseline",
|
|
@@ -722,6 +870,65 @@ function addTargetRuntimeFindings(options) {
|
|
|
722
870
|
}));
|
|
723
871
|
}
|
|
724
872
|
}
|
|
873
|
+
function addSharpRuntimeFindings(options) {
|
|
874
|
+
const expected = options.manifest.runtime.sharp;
|
|
875
|
+
if (!expected)
|
|
876
|
+
return;
|
|
877
|
+
const actualOverride = options.packageJson.overrides?.sharp ?? null;
|
|
878
|
+
if (actualOverride !== expected) {
|
|
879
|
+
options.findings.push(runtimeFinding({
|
|
880
|
+
ruleId: "brandlab-runtime-sharp-override",
|
|
881
|
+
expected: `overrides.sharp=${expected}`,
|
|
882
|
+
actual: `overrides.sharp=${actualOverride ?? "missing"}.`,
|
|
883
|
+
remediation: "Add the release-owned Sharp override so Next.js and site-next use one security-patched native runtime.",
|
|
884
|
+
path: "package.json",
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
887
|
+
if (options.packageLock.state !== "valid")
|
|
888
|
+
return;
|
|
889
|
+
if (options.packageLock.sharpResolutions.length !== 1 ||
|
|
890
|
+
options.packageLock.sharpResolutions[0]?.version !== expected) {
|
|
891
|
+
options.findings.push(runtimeFinding({
|
|
892
|
+
ruleId: "brandlab-runtime-sharp-resolution",
|
|
893
|
+
expected: `one sharp@${expected} lockfile resolution`,
|
|
894
|
+
actual: formatSharpResolutions(options.packageLock.sharpResolutions),
|
|
895
|
+
remediation: "Regenerate package-lock.json after applying the exact release-owned Sharp override.",
|
|
896
|
+
path: "package-lock.json",
|
|
897
|
+
}));
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
function addContactUploadFilenameFindings(options) {
|
|
901
|
+
if (options.manifest.contracts.contact !== "contact-v4" &&
|
|
902
|
+
!options.manifest.capabilities.includes("client-safe-contact-upload-filenames")) {
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const path = "src/lib/contact/imageUploads.ts";
|
|
906
|
+
const source = readOptionalText(join(options.root, path));
|
|
907
|
+
// Raw File objects already retain their reviewed names. This rule is only
|
|
908
|
+
// relevant when a project owns a client-side image processing adapter.
|
|
909
|
+
if (!source || /\bnormalizeContactUploadFile\s*\(/.test(source))
|
|
910
|
+
return;
|
|
911
|
+
options.findings.push({
|
|
912
|
+
ruleId: "brandlab-contact-upload-filename-normalization",
|
|
913
|
+
surface: "contact",
|
|
914
|
+
classification: options.classification,
|
|
915
|
+
severity: "blocker",
|
|
916
|
+
contract: {
|
|
917
|
+
name: "contact",
|
|
918
|
+
version: options.manifest.contracts.contact,
|
|
919
|
+
},
|
|
920
|
+
evidenceKind: "static",
|
|
921
|
+
path,
|
|
922
|
+
expected: "Compressed contact images are wrapped with normalizeContactUploadFile before FormData submission.",
|
|
923
|
+
actual: "The contact upload module does not use the shared filename normalizer.",
|
|
924
|
+
remediation: "Use normalizeContactUploadFile(original, processed) from @brandlab-digital/site-next/contact so anonymous browser Blobs retain a valid reviewed filename.",
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
function formatSharpResolutions(resolutions) {
|
|
928
|
+
return resolutions.length === 0
|
|
929
|
+
? "none"
|
|
930
|
+
: resolutions.map((entry) => `${entry.path}=${entry.version ?? "missing"}`).join(", ");
|
|
931
|
+
}
|
|
725
932
|
function runtimeFinding(options) {
|
|
726
933
|
return {
|
|
727
934
|
ruleId: options.ruleId,
|
|
@@ -760,10 +967,14 @@ function evaluateApplicableArchitectureContracts(options) {
|
|
|
760
967
|
configV2Inspection ??= inspectImmutableBrandlabConfigV2(options.root);
|
|
761
968
|
return configV2Inspection;
|
|
762
969
|
};
|
|
763
|
-
|
|
764
|
-
const inspectConfigV3 = () => {
|
|
765
|
-
|
|
766
|
-
|
|
970
|
+
const configV3Inspections = new Map();
|
|
971
|
+
const inspectConfigV3 = (contractVersion) => {
|
|
972
|
+
const cached = configV3Inspections.get(contractVersion);
|
|
973
|
+
if (cached)
|
|
974
|
+
return cached;
|
|
975
|
+
const inspection = inspectBrandlabConfigV3(options.root, { contractVersion });
|
|
976
|
+
configV3Inspections.set(contractVersion, inspection);
|
|
977
|
+
return inspection;
|
|
767
978
|
};
|
|
768
979
|
const legacyInspections = new Map();
|
|
769
980
|
let legacyArchitectureInput;
|
|
@@ -793,7 +1004,19 @@ function evaluateApplicableArchitectureContracts(options) {
|
|
|
793
1004
|
}
|
|
794
1005
|
}
|
|
795
1006
|
else if (installedConfiguration === CONFIGURATION_V3_CONTRACT) {
|
|
796
|
-
const inspection = inspectConfigV3();
|
|
1007
|
+
const inspection = inspectConfigV3(CONFIGURATION_V3_CONTRACT);
|
|
1008
|
+
for (const issue of inspection.issues) {
|
|
1009
|
+
options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
else if (installedConfiguration === CONFIGURATION_V4_CONTRACT) {
|
|
1013
|
+
const inspection = inspectConfigV3(CONFIGURATION_V4_CONTRACT);
|
|
1014
|
+
for (const issue of inspection.issues) {
|
|
1015
|
+
options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
else if (installedConfiguration === CONFIGURATION_V5_CONTRACT) {
|
|
1019
|
+
const inspection = inspectConfigV3(CONFIGURATION_V5_CONTRACT);
|
|
797
1020
|
for (const issue of inspection.issues) {
|
|
798
1021
|
options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
|
|
799
1022
|
}
|
|
@@ -826,9 +1049,9 @@ function evaluateApplicableArchitectureContracts(options) {
|
|
|
826
1049
|
// supported target release is explicitly selected.
|
|
827
1050
|
const configSource = readOptionalText(join(options.root, "brandlab.config.ts"));
|
|
828
1051
|
if (/\bdefineBrandlabProject\s*\(/.test(configSource ?? "")) {
|
|
829
|
-
for (const issue of inspectConfigV3().issues) {
|
|
1052
|
+
for (const issue of inspectConfigV3(CONFIGURATION_V5_CONTRACT).issues) {
|
|
830
1053
|
options.findings.push(mapConfigIssue(issue, "advisory", {
|
|
831
|
-
configuration:
|
|
1054
|
+
configuration: CONFIGURATION_V5_CONTRACT,
|
|
832
1055
|
}));
|
|
833
1056
|
}
|
|
834
1057
|
}
|
|
@@ -846,7 +1069,17 @@ function evaluateApplicableArchitectureContracts(options) {
|
|
|
846
1069
|
}
|
|
847
1070
|
}
|
|
848
1071
|
else if (hasDistinctTarget && targetConfiguration === CONFIGURATION_V3_CONTRACT) {
|
|
849
|
-
for (const issue of inspectConfigV3().issues) {
|
|
1072
|
+
for (const issue of inspectConfigV3(CONFIGURATION_V3_CONTRACT).issues) {
|
|
1073
|
+
options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
else if (hasDistinctTarget && targetConfiguration === CONFIGURATION_V4_CONTRACT) {
|
|
1077
|
+
for (const issue of inspectConfigV3(CONFIGURATION_V4_CONTRACT).issues) {
|
|
1078
|
+
options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
else if (hasDistinctTarget && targetConfiguration === CONFIGURATION_V5_CONTRACT) {
|
|
1082
|
+
for (const issue of inspectConfigV3(CONFIGURATION_V5_CONTRACT).issues) {
|
|
850
1083
|
options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
|
|
851
1084
|
}
|
|
852
1085
|
}
|
|
@@ -867,7 +1100,9 @@ function evaluateApplicableArchitectureContracts(options) {
|
|
|
867
1100
|
}
|
|
868
1101
|
}
|
|
869
1102
|
const inspectedConfigV2 = configV2Inspection;
|
|
870
|
-
const inspectedConfigV3 =
|
|
1103
|
+
const inspectedConfigV3 = (configV3Inspections.get(CONFIGURATION_V4_CONTRACT) ??
|
|
1104
|
+
configV3Inspections.get(CONFIGURATION_V3_CONTRACT) ??
|
|
1105
|
+
null);
|
|
871
1106
|
return {
|
|
872
1107
|
evaluator: BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID,
|
|
873
1108
|
installedContract: options.installedManifest?.contracts.architecture ?? null,
|
|
@@ -1050,7 +1285,7 @@ function readPackageLock(root) {
|
|
|
1050
1285
|
const rootDevDependencies = stringRecord(rootPackage.devDependencies);
|
|
1051
1286
|
const rootEngines = stringRecord(rootPackage.engines);
|
|
1052
1287
|
const resolvedPackages = new Map();
|
|
1053
|
-
for (const packageName of [...BRANDLAB_SHARED_PACKAGE_NAMES, ...RUNTIME_PACKAGE_FIELDS.map(([name]) => name)]) {
|
|
1288
|
+
for (const packageName of [...BRANDLAB_SHARED_PACKAGE_NAMES, ...RUNTIME_PACKAGE_FIELDS.map(([name]) => name), "sharp"]) {
|
|
1054
1289
|
const entry = parsed.packages[`node_modules/${packageName}`];
|
|
1055
1290
|
if (!isObjectRecord(entry))
|
|
1056
1291
|
continue;
|
|
@@ -1061,6 +1296,13 @@ function readPackageLock(root) {
|
|
|
1061
1296
|
linked: entry.link === true,
|
|
1062
1297
|
});
|
|
1063
1298
|
}
|
|
1299
|
+
const sharpResolutions = Object.entries(parsed.packages)
|
|
1300
|
+
.filter(([packagePath]) => packagePath === "node_modules/sharp" || packagePath.endsWith("/node_modules/sharp"))
|
|
1301
|
+
.map(([packagePath, entry]) => ({
|
|
1302
|
+
path: packagePath,
|
|
1303
|
+
version: isObjectRecord(entry) && typeof entry.version === "string" ? entry.version : null,
|
|
1304
|
+
}))
|
|
1305
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
1064
1306
|
return {
|
|
1065
1307
|
state: "valid",
|
|
1066
1308
|
lockfileVersion,
|
|
@@ -1068,6 +1310,7 @@ function readPackageLock(root) {
|
|
|
1068
1310
|
rootDevDependencies,
|
|
1069
1311
|
rootEngines,
|
|
1070
1312
|
resolvedPackages,
|
|
1313
|
+
sharpResolutions,
|
|
1071
1314
|
fingerprint: {
|
|
1072
1315
|
state: "valid",
|
|
1073
1316
|
lockfileVersion,
|
|
@@ -1080,6 +1323,7 @@ function readPackageLock(root) {
|
|
|
1080
1323
|
rootDevDependencies,
|
|
1081
1324
|
rootEngines,
|
|
1082
1325
|
resolvedPackages,
|
|
1326
|
+
sharpResolutions,
|
|
1083
1327
|
fingerprint: {},
|
|
1084
1328
|
}, packageName)])),
|
|
1085
1329
|
node: rootEngines.node ?? null,
|
|
@@ -1088,6 +1332,7 @@ function readPackageLock(root) {
|
|
|
1088
1332
|
packageName,
|
|
1089
1333
|
{ version: entry.version, linked: entry.linked },
|
|
1090
1334
|
])),
|
|
1335
|
+
sharp: sharpResolutions,
|
|
1091
1336
|
},
|
|
1092
1337
|
};
|
|
1093
1338
|
}
|
|
@@ -1099,6 +1344,7 @@ function emptyPackageLockInspection(state, extra = {}) {
|
|
|
1099
1344
|
rootDevDependencies: {},
|
|
1100
1345
|
rootEngines: {},
|
|
1101
1346
|
resolvedPackages: new Map(),
|
|
1347
|
+
sharpResolutions: [],
|
|
1102
1348
|
fingerprint: { state, ...extra },
|
|
1103
1349
|
};
|
|
1104
1350
|
}
|