@brandlab-digital/conformance 1.0.0-rc.9 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cli.d.ts +0 -3
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +35 -93
  4. package/dist/cli.js.map +1 -1
  5. package/dist/current-release.d.ts +3 -0
  6. package/dist/current-release.d.ts.map +1 -0
  7. package/dist/current-release.js +54 -0
  8. package/dist/current-release.js.map +1 -0
  9. package/dist/project.d.ts +19 -24
  10. package/dist/project.d.ts.map +1 -1
  11. package/dist/project.js +444 -1070
  12. package/dist/project.js.map +1 -1
  13. package/dist/release-manifest.d.ts +29 -82
  14. package/dist/release-manifest.d.ts.map +1 -1
  15. package/dist/release-manifest.js +141 -348
  16. package/dist/release-manifest.js.map +1 -1
  17. package/dist/rendered-content.d.ts +36 -0
  18. package/dist/rendered-content.d.ts.map +1 -0
  19. package/dist/rendered-content.js +146 -0
  20. package/dist/rendered-content.js.map +1 -0
  21. package/dist/verification-policy.d.ts +162 -0
  22. package/dist/verification-policy.d.ts.map +1 -0
  23. package/dist/verification-policy.js +309 -0
  24. package/dist/verification-policy.js.map +1 -0
  25. package/package.json +6 -22
  26. package/dist/legacy-architecture-contracts.d.ts +0 -31
  27. package/dist/legacy-architecture-contracts.d.ts.map +0 -1
  28. package/dist/legacy-architecture-contracts.js +0 -49
  29. package/dist/legacy-architecture-contracts.js.map +0 -1
  30. package/dist/locations.d.ts +0 -63
  31. package/dist/locations.d.ts.map +0 -1
  32. package/dist/locations.js +0 -105
  33. package/dist/locations.js.map +0 -1
  34. package/dist/project-config-v2-contract.d.ts +0 -32
  35. package/dist/project-config-v2-contract.d.ts.map +0 -1
  36. package/dist/project-config-v2-contract.js +0 -926
  37. package/dist/project-config-v2-contract.js.map +0 -1
  38. package/dist/project-config-v3.d.ts +0 -43
  39. package/dist/project-config-v3.d.ts.map +0 -1
  40. package/dist/project-config-v3.js +0 -2876
  41. package/dist/project-config-v3.js.map +0 -1
  42. package/dist/project-config.d.ts +0 -3
  43. package/dist/project-config.d.ts.map +0 -1
  44. package/dist/project-config.js +0 -3
  45. package/dist/project-config.js.map +0 -1
  46. package/dist/release-catalog.d.ts +0 -4
  47. package/dist/release-catalog.d.ts.map +0 -1
  48. package/dist/release-catalog.js +0 -787
  49. package/dist/release-catalog.js.map +0 -1
  50. package/dist/release-contract-compatibility.d.ts +0 -104
  51. package/dist/release-contract-compatibility.d.ts.map +0 -1
  52. package/dist/release-contract-compatibility.js +0 -119
  53. package/dist/release-contract-compatibility.js.map +0 -1
  54. package/dist/schema-hygiene.d.ts +0 -26
  55. package/dist/schema-hygiene.d.ts.map +0 -1
  56. package/dist/schema-hygiene.js +0 -28
  57. package/dist/schema-hygiene.js.map +0 -1
  58. package/dist/source-safety-contracts.d.ts +0 -20
  59. package/dist/source-safety-contracts.d.ts.map +0 -1
  60. package/dist/source-safety-contracts.js +0 -60
  61. package/dist/source-safety-contracts.js.map +0 -1
  62. package/dist/vitest.d.ts +0 -110
  63. package/dist/vitest.d.ts.map +0 -1
  64. package/dist/vitest.js +0 -2678
  65. package/dist/vitest.js.map +0 -1
package/dist/project.js CHANGED
@@ -1,195 +1,147 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import { join, relative, resolve } from "node:path";
3
- import { evaluateSharedPackageVersionSpecs, findForbiddenBrandlabPatterns, } from "./vitest.js";
4
- import { DEFAULT_BRANDLAB_RELEASE_CATALOG, } from "./release-catalog.js";
5
- import { BRANDLAB_CONFIG_V3_RULESET_ID, inspectBrandlabConfigV3, } from "./project-config-v3.js";
6
- import { BRANDLAB_CONFIG_V2_RULESET_ID, inspectBrandlabConfigV2 as inspectImmutableBrandlabConfigV2, } from "./project-config-v2-contract.js";
7
- import { BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS, BRANDLAB_SCHEMA_HYGIENE_V3_RULESET_ID, } from "./schema-hygiene.js";
8
- import { BRANDLAB_SOURCE_SAFETY_V2_PATTERNS, BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID, sourceSafetyRulesetForArchitecture, targetOnlySourceSafetyPatterns, } from "./source-safety-contracts.js";
9
- import { inspectLegacyArchitectureContract, legacyArchitectureRulesetId, targetOnlyLegacyArchitectureIssues, } from "./legacy-architecture-contracts.js";
10
- import { BRANDLAB_SHARED_PACKAGE_NAMES, compareBrandlabSemver, digestBrandlabAuthority, isBrandlabSemver, validateBrandlabReleaseCatalog, validateBrandlabRuntimeReleaseCatalog, validateBrandlabRuntimeReleaseManifestSubsetAgainstCatalog, validateBrandlabSupportPolicy, } from "./release-manifest.js";
11
- export const BRANDLAB_CONFORMANCE_SCHEMA_VERSION = "brandlab.conformance.v1";
12
- export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "control-plane-v1";
13
- export const BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID = "version-aware-architecture-dispatch-v1";
14
- const LEGACY_ARCHITECTURE_EVALUATOR_CONTRACT = "architecture-v2";
15
- const LEGACY_CONFIGURATION_CONTRACT = "legacy-config-v1";
16
- const CONFIGURATION_V2_CONTRACT = "project-config-v2";
17
- const CONFIGURATION_V3_CONTRACT = "project-config-v3";
18
- const SEO_V3_CONTRACT = "seo-v3";
3
+ import { BRANDLAB_CURRENT_RUNTIME_RELEASE } from "./current-release.js";
4
+ import { BRANDLAB_SHARED_PACKAGE_NAMES, compareBrandlabSemver, digestBrandlabAuthority, isBrandlabSemver, projectBrandlabRuntimeReleaseContract, validateBrandlabReleaseManifest, validateBrandlabRuntimeReleaseContract, } from "./release-manifest.js";
5
+ import { applyBrandlabVerificationPolicy, BRANDLAB_VERIFICATION_POLICY_VERSION, } from "./verification-policy.js";
6
+ export { BRANDLAB_VERIFICATION_CHECKS, BRANDLAB_VERIFICATION_FAMILIES, BRANDLAB_VERIFICATION_POLICY_VERSION, } from "./verification-policy.js";
7
+ export const BRANDLAB_CONFORMANCE_SCHEMA_VERSION = "brandlab.conformance.v2";
8
+ export const BRANDLAB_CONFORMANCE_EVALUATOR_VERSION = "current-only-v2";
19
9
  const RUNTIME_PACKAGE_FIELDS = [
20
10
  ["next", "next"],
21
11
  ["react", "react"],
22
12
  ["react-dom", "reactDom"],
23
13
  ["typescript", "typescript"],
24
14
  ];
15
+ const SOURCE_EXTENSIONS = new Set([".js", ".jsx", ".mjs", ".ts", ".tsx"]);
16
+ const SOURCE_RULES = [
17
+ {
18
+ ruleId: "brandlab-source-retired-analytics-event",
19
+ surface: "analytics",
20
+ pattern: /\b(?:contact_form_submission|phone_calls|form_start|cta_click)\b/,
21
+ expected: "Only the current Brandlab analytics event contract is used.",
22
+ remediation: "Use the canonical phone_call and contact_form events.",
23
+ },
24
+ {
25
+ ruleId: "brandlab-source-local-google-tag-bootstrap",
26
+ surface: "analytics",
27
+ pattern: /googletagmanager\.com\/gtag\/js|window\.dataLayer\s*=\s*window\.dataLayer|function\s+gtag\s*\(/,
28
+ expected: "Google tag bootstrap is owned by the shared analytics runtime.",
29
+ remediation: "Remove the project-local bootstrap and supply only approved analytics identifiers.",
30
+ },
31
+ {
32
+ ruleId: "brandlab-source-local-contact-delivery",
33
+ surface: "contact",
34
+ pattern: /\.emails\.send\s*\(|resend\.emails\.send\s*\(/,
35
+ expected: "Email delivery is owned by the shared contact runtime.",
36
+ remediation: "Pass the Resend client to the shared contact adapter instead of sending locally.",
37
+ },
38
+ {
39
+ ruleId: "brandlab-source-local-contact-email-renderer",
40
+ surface: "contact",
41
+ pattern: /\bContactFormEmail\b|\brenderContactEmail\b/,
42
+ expected: "Contact email rendering is owned by the shared contact runtime.",
43
+ remediation: "Remove the local email renderer and configure shared brand variation only.",
44
+ },
45
+ {
46
+ ruleId: "brandlab-source-dangerously-set-inner-html",
47
+ surface: "security",
48
+ pattern: /\bdangerouslySetInnerHTML\b/,
49
+ expected: "No project-owned raw HTML injection.",
50
+ remediation: "Use structured React output or the shared sanitised renderer.",
51
+ },
52
+ {
53
+ ruleId: "brandlab-source-raw-json-ld",
54
+ surface: "schema",
55
+ pattern: /application\/ld\+json/,
56
+ expected: "Structured data is rendered through the shared schema component.",
57
+ remediation: "Replace raw JSON-LD output with the shared schema graph renderer.",
58
+ },
59
+ {
60
+ ruleId: "brandlab-source-self-serving-rating-schema",
61
+ surface: "schema",
62
+ pattern: /['"]AggregateRating['"]/,
63
+ expected: "No self-serving aggregate rating schema.",
64
+ remediation: "Show verified review proof visibly without LocalBusiness or Organization rating schema.",
65
+ },
66
+ {
67
+ ruleId: "brandlab-source-synthetic-offer-schema",
68
+ surface: "schema",
69
+ pattern: /\bpriceValidUntil\b|['"]@type['"]\s*:\s*['"]Offer['"]/,
70
+ expected: "No synthetic Offer markup on service pages.",
71
+ remediation: "Remove invented Offer data and use the shared service schema.",
72
+ },
73
+ {
74
+ ruleId: "brandlab-source-local-google-reviews-fetch",
75
+ surface: "reviews",
76
+ pattern: /places\.googleapis\.com|maps\.googleapis\.com\/maps\/api\/place/,
77
+ expected: "Google Places fetching is owned by the shared review runtime.",
78
+ remediation: "Provide only the verified Place ID, Maps URL and server-side API key.",
79
+ },
80
+ {
81
+ ruleId: "brandlab-source-public-secret",
82
+ surface: "security",
83
+ pattern: /NEXT_PUBLIC_[A-Z0-9_]*(?:API_KEY|SECRET|TOKEN)/,
84
+ expected: "Secrets remain server-only.",
85
+ remediation: "Move the credential to an approved server-only environment variable.",
86
+ },
87
+ {
88
+ ruleId: "brandlab-source-retired-contact-state",
89
+ surface: "contact",
90
+ pattern: /\bCONTACT_CONTEXT_SECRET\b|\bKV_REST_API_(?:URL|TOKEN)\b/,
91
+ expected: "The simplified contact runtime has no project-level state-service requirement.",
92
+ remediation: "Remove the retired contact state variables and use the shared stateless contact flow.",
93
+ },
94
+ ];
25
95
  export function evaluateBrandlabProject(options) {
26
96
  const root = resolve(options.root);
27
97
  const mode = options.mode ?? "local";
28
- const releaseCatalog = options.releaseCatalog ?? DEFAULT_BRANDLAB_RELEASE_CATALOG;
29
- const supportPolicy = options.supportPolicy;
30
- validateBrandlabRuntimeReleaseCatalog(releaseCatalog);
31
- if (options.releaseManifests) {
32
- validateBrandlabReleaseCatalog(options.releaseManifests);
33
- validateBrandlabRuntimeReleaseManifestSubsetAgainstCatalog(releaseCatalog, options.releaseManifests);
98
+ const release = options.release ?? BRANDLAB_CURRENT_RUNTIME_RELEASE;
99
+ validateBrandlabRuntimeReleaseContract(release);
100
+ if (options.releaseManifest) {
101
+ validateBrandlabReleaseManifest(options.releaseManifest);
102
+ if (digestBrandlabAuthority(projectBrandlabRuntimeReleaseContract(options.releaseManifest)) !==
103
+ digestBrandlabAuthority(release)) {
104
+ throw new Error("The release manifest differs from the embedded current release.");
105
+ }
34
106
  }
35
- validateBrandlabSupportPolicy(supportPolicy, releaseCatalog);
36
107
  const packageJson = readPackageJson(root);
37
108
  const packageLock = readPackageLock(root);
38
109
  const detectedRelease = detectInstalledBrandlabRelease(packageJson);
39
- const installedRelease = options.installedRelease === undefined
40
- ? detectedRelease
41
- : options.installedRelease;
42
- const targetRelease = options.targetRelease ?? null;
43
- const releaseByVersion = new Map(releaseCatalog.map((manifest) => [manifest.releaseVersion, manifest]));
44
- const installedManifest = installedRelease
45
- ? releaseByVersion.get(installedRelease) ?? null
46
- : null;
47
- const targetManifest = targetRelease
48
- ? releaseByVersion.get(targetRelease) ?? null
110
+ const installedRelease = options.installedRelease === undefined ? detectedRelease : options.installedRelease;
111
+ const manifest = release.releaseVersion === installedRelease ? release : null;
112
+ const attestation = options.releaseManifest?.releaseVersion === installedRelease
113
+ ? options.releaseManifest
49
114
  : null;
50
- const releaseManifestByVersion = new Map((options.releaseManifests ?? []).map((manifest) => [manifest.releaseVersion, manifest]));
51
115
  const findings = [];
52
- addReleaseMaintenanceFindings({
116
+ inspectRelease({
53
117
  findings,
54
118
  packageJson,
55
119
  packageLock,
56
120
  installedRelease,
57
121
  detectedRelease,
58
- installedManifest,
59
- installedAttestation: installedRelease
60
- ? releaseManifestByVersion.get(installedRelease) ?? null
61
- : null,
62
- releaseAttestationsRequired: options.releaseManifests !== undefined,
63
- supportPolicy,
64
- });
65
- addMigrationFindings({
66
- findings,
67
- installedRelease,
68
- targetRelease,
69
- targetManifest,
70
- supportPolicy,
71
- packageJson,
72
- packageLock,
122
+ manifest,
123
+ attestation,
73
124
  });
74
- const architectureFingerprint = evaluateApplicableArchitectureContracts({
75
- root,
76
- findings,
77
- installedManifest,
78
- targetManifest,
79
- targetRelease,
80
- architecture: options.architecture,
81
- });
82
- const sourceRoot = join(root, "src");
83
- const installedSourceSafety = sourceSafetyRulesetForArchitecture(installedManifest?.contracts.architecture);
84
- const targetSourceSafety = sourceSafetyRulesetForArchitecture(targetManifest?.contracts.architecture);
85
- if (existsSync(sourceRoot)) {
86
- if (installedSourceSafety) {
87
- addSourcePatternFindings({
88
- root,
89
- sourceRoot,
90
- findings,
91
- patterns: installedSourceSafety.patterns,
92
- classification: "maintenance",
93
- contract: {
94
- name: "source-safety",
95
- version: installedSourceSafety.id,
96
- },
97
- });
98
- if (!targetSourceSafety &&
99
- installedSourceSafety.id !== BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID) {
100
- addSourcePatternFindings({
101
- root,
102
- sourceRoot,
103
- findings,
104
- patterns: targetOnlySourceSafetyPatterns(installedSourceSafety, {
105
- id: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
106
- patterns: BRANDLAB_SOURCE_SAFETY_V2_PATTERNS,
107
- }),
108
- classification: "advisory",
109
- contract: {
110
- name: "source-safety",
111
- version: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
112
- },
113
- });
114
- }
115
- }
116
- else if (!installedManifest && !targetSourceSafety) {
117
- addSourcePatternFindings({
118
- root,
119
- sourceRoot,
120
- findings,
121
- patterns: BRANDLAB_SOURCE_SAFETY_V2_PATTERNS,
122
- classification: "advisory",
123
- contract: {
124
- name: "source-safety",
125
- version: BRANDLAB_SOURCE_SAFETY_V2_RULESET_ID,
126
- },
127
- });
128
- }
129
- if (targetSourceSafety &&
130
- targetManifest?.releaseVersion !== installedManifest?.releaseVersion) {
131
- addSourcePatternFindings({
132
- root,
133
- sourceRoot,
134
- findings,
135
- patterns: targetOnlySourceSafetyPatterns(installedSourceSafety, targetSourceSafety),
136
- classification: "migration",
137
- contract: {
138
- name: "source-safety",
139
- version: targetSourceSafety.id,
140
- },
141
- });
142
- }
143
- if (installedManifest?.contracts.seo === SEO_V3_CONTRACT) {
144
- addSourcePatternFindings({
145
- root,
146
- sourceRoot,
147
- findings,
148
- patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
149
- classification: "maintenance",
150
- contract: { name: "seo", version: SEO_V3_CONTRACT },
151
- });
152
- }
153
- if (targetManifest?.contracts.seo === SEO_V3_CONTRACT &&
154
- targetManifest.releaseVersion !== installedManifest?.releaseVersion) {
155
- addSourcePatternFindings({
156
- root,
157
- sourceRoot,
158
- findings,
159
- patterns: BRANDLAB_DEPRECATED_SCHEMA_SOURCE_PATTERNS,
160
- classification: "migration",
161
- contract: { name: "seo", version: SEO_V3_CONTRACT },
162
- });
163
- }
164
- }
125
+ inspectProjectConfig(root, findings);
126
+ inspectProjectSource(root, findings);
165
127
  const sortedFindings = findings.sort(compareFindings);
128
+ const policyResult = applyBrandlabVerificationPolicy(sortedFindings, options.verificationEvidence);
129
+ const operationalFindings = policyResult.operationalFindings.sort(compareFindings);
166
130
  const authorityDigest = digestBrandlabAuthority({
167
- releaseCatalog,
168
- supportPolicy,
169
- releaseManifests: options.releaseManifests ?? null,
131
+ release,
132
+ releaseManifest: options.releaseManifest ?? null,
170
133
  });
171
134
  const contractDigest = digestBrandlabAuthority({
172
- installed: installedManifest?.contracts ?? null,
173
- target: targetManifest?.contracts ?? null,
174
- evaluator: BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID,
175
- rulesets: {
176
- configurationV2: BRANDLAB_CONFIG_V2_RULESET_ID,
177
- configurationV3: BRANDLAB_CONFIG_V3_RULESET_ID,
178
- seoV3: BRANDLAB_SCHEMA_HYGIENE_V3_RULESET_ID,
179
- installedSourceSafety: installedSourceSafety?.id ?? null,
180
- targetSourceSafety: targetSourceSafety?.id ?? null,
181
- installedLegacyArchitecture: legacyArchitectureRulesetId(installedManifest?.contracts.architecture),
182
- targetLegacyArchitecture: legacyArchitectureRulesetId(targetManifest?.contracts.architecture),
183
- },
135
+ installed: manifest?.contracts ?? null,
136
+ evaluator: BRANDLAB_CONFORMANCE_EVALUATOR_VERSION,
137
+ verificationPolicy: BRANDLAB_VERIFICATION_POLICY_VERSION,
184
138
  });
185
139
  const implementationFingerprint = digestBrandlabAuthority({
186
140
  packageJson: packageJson ?? null,
187
141
  packageLock: packageLock.fingerprint,
188
142
  brandlabConfig: readOptionalText(join(root, "brandlab.config.ts")),
189
- architecture: architectureFingerprint,
190
- evidence: sortedFindings.map(({ ruleId, classification, severity, path, line }) => ({
143
+ findings: sortedFindings.map(({ ruleId, severity, path, line }) => ({
191
144
  ruleId,
192
- classification,
193
145
  severity,
194
146
  path: path ?? null,
195
147
  line: line ?? null,
@@ -203,37 +155,14 @@ export function evaluateBrandlabProject(options) {
203
155
  implementationFingerprint,
204
156
  mode,
205
157
  installedRelease,
206
- targetRelease,
207
- findings: sortedFindings,
208
- maintenance: summarize(sortedFindings, "maintenance"),
209
- migration: summarize(sortedFindings, "migration"),
210
- advisory: summarize(sortedFindings, "advisory"),
158
+ findings: operationalFindings,
159
+ operationalFindings,
160
+ verification: policyResult.verification,
161
+ summary: policyResult.verification.summary,
211
162
  };
212
163
  }
213
- function addSourcePatternFindings(options) {
214
- for (const match of findForbiddenBrandlabPatterns({
215
- roots: [options.sourceRoot],
216
- patterns: options.patterns,
217
- })) {
218
- options.findings.push({
219
- ruleId: `brandlab-source-${slugify(match.patternName)}`,
220
- surface: surfaceForRule(match.patternName),
221
- classification: options.classification,
222
- severity: options.classification === "advisory" ? "warning" : "blocker",
223
- contract: options.contract,
224
- evidenceKind: "static",
225
- path: normalizePath(relative(options.root, match.file)),
226
- line: match.line,
227
- expected: `No ${match.patternName} source pattern`,
228
- actual: `A prohibited source pattern was detected at line ${match.line}.`,
229
- remediation: `Remove the ${match.patternName} implementation and use the supported shared-platform path.`,
230
- });
231
- }
232
- }
233
164
  export function shouldFailBrandlabConformance(result) {
234
- if (result.maintenance.blockers > 0)
235
- return true;
236
- return result.mode === "preflight" && result.migration.blockers > 0;
165
+ return result.verification.summary.blockers > 0;
237
166
  }
238
167
  export function detectInstalledBrandlabRelease(packageJson) {
239
168
  if (!packageJson)
@@ -245,759 +174,261 @@ export function detectInstalledBrandlabRelease(packageJson) {
245
174
  return null;
246
175
  return new Set(versions).size === 1 ? versions[0] : null;
247
176
  }
248
- export function readBrandlabArchitectureConfig(root) {
249
- const source = readOptionalText(join(root, "brandlab.config.ts"));
250
- if (!source)
251
- return null;
252
- const architectureSource = extractDelimitedProperty(source, "architecture", "{", "}");
253
- if (!architectureSource)
254
- return null;
255
- const architecture = {};
256
- assignString(architecture, "profile", extractStringProperty(architectureSource, "profile"));
257
- assignString(architecture, "status", extractStringProperty(architectureSource, "status"));
258
- assignString(architecture, "targetProfile", extractStringProperty(architectureSource, "targetProfile"));
259
- assignString(architecture, "migrationRationale", extractStringProperty(architectureSource, "migrationRationale"));
260
- assignNumber(architecture, "staticRouteFileLimit", extractNumberProperty(architectureSource, "staticRouteFileLimit"));
261
- assignNumber(architecture, "largeRouteFileLineLimit", extractNumberProperty(architectureSource, "largeRouteFileLineLimit"));
262
- assignNumber(architecture, "routeFileContentCharLimit", extractNumberProperty(architectureSource, "routeFileContentCharLimit"));
263
- const routeFamiliesSource = extractDelimitedProperty(architectureSource, "routeFamilies", "[", "]");
264
- if (routeFamiliesSource) {
265
- architecture.routeFamilies = extractObjectBodies(routeFamiliesSource).map((routeFamily) => ({
266
- name: extractStringProperty(routeFamily, "name") ?? undefined,
267
- model: extractStringProperty(routeFamily, "model") ?? undefined,
268
- route: extractStringProperty(routeFamily, "route") ?? undefined,
269
- dataSource: extractStringProperty(routeFamily, "dataSource") ?? undefined,
270
- }));
271
- }
272
- const contentOwnershipSource = extractDelimitedProperty(architectureSource, "contentOwnership", "{", "}");
273
- if (contentOwnershipSource) {
274
- architecture.contentOwnership = {
275
- routeFiles: extractStringProperty(contentOwnershipSource, "routeFiles") ?? undefined,
276
- pageData: extractStringProperty(contentOwnershipSource, "pageData") ?? undefined,
277
- componentCopy: extractStringProperty(contentOwnershipSource, "componentCopy") ?? undefined,
278
- };
279
- }
280
- const topologySource = extractDelimitedProperty(architectureSource, "topology", "{", "}");
281
- if (topologySource) {
282
- architecture.topology = {
283
- allowedRootDirs: extractStringArray(topologySource, "allowedRootDirs"),
284
- allowedComponentDirs: extractStringArray(topologySource, "allowedComponentDirs"),
285
- allowedLibDirs: extractStringArray(topologySource, "allowedLibDirs"),
286
- allowedTypeFiles: extractStringArray(topologySource, "allowedTypeFiles"),
287
- legacyAllowedRootDirs: extractStringArray(topologySource, "legacyAllowedRootDirs"),
288
- legacyAllowedComponentDirs: extractStringArray(topologySource, "legacyAllowedComponentDirs"),
289
- legacyAllowedLibDirs: extractStringArray(topologySource, "legacyAllowedLibDirs"),
290
- legacyAllowedTypeFiles: extractStringArray(topologySource, "legacyAllowedTypeFiles"),
291
- targetTopology: extractStringProperty(topologySource, "targetTopology") ?? undefined,
292
- };
177
+ function inspectRelease(options) {
178
+ if (!options.packageJson) {
179
+ options.findings.push(releaseFinding("brandlab-release-package-json-missing", "A readable package.json", "package.json is missing or invalid.", "Restore a valid package.json."));
180
+ return;
293
181
  }
294
- return architecture;
295
- }
296
- function addReleaseMaintenanceFindings(options) {
297
- const packageJson = options.packageJson;
298
- if (!packageJson) {
299
- options.findings.push(releaseFinding({
300
- ruleId: "brandlab-release-package-json-missing",
301
- severity: "blocker",
302
- expected: "A readable project package.json",
303
- actual: "package.json is missing or invalid.",
304
- remediation: "Restore a valid package.json before evaluating Brandlab conformance.",
305
- }));
182
+ if (!options.installedRelease) {
183
+ options.findings.push(releaseFinding("brandlab-release-installed-version-unresolved", "All four Brandlab packages pinned to one exact release", "The installed Brandlab release could not be resolved.", "Pin all four packages to one exact supported version."));
306
184
  return;
307
185
  }
308
- const installedRelease = options.installedRelease;
309
- if (!installedRelease) {
310
- options.findings.push(releaseFinding({
311
- ruleId: "brandlab-release-installed-version-unresolved",
312
- severity: "blocker",
313
- expected: "Four exact, internally aligned Brandlab package versions",
314
- actual: "The installed Brandlab release could not be resolved.",
315
- remediation: "Pin all four Brandlab packages to one exact supported release.",
316
- }));
186
+ if (!options.manifest) {
187
+ options.findings.push(releaseFinding("brandlab-release-unsupported", "The release selected by the active Brandlab channel", `${options.installedRelease} is not supported by the active platform.`, "Upgrade the project to the current candidate before further work."));
317
188
  return;
318
189
  }
319
- if (!options.installedManifest) {
320
- options.findings.push(releaseFinding({
321
- ruleId: "brandlab-release-unsupported",
322
- severity: "blocker",
323
- expected: "An installed release with an embedded runtime contract",
324
- actual: `${options.installedRelease} has no runtime release contract.`,
325
- remediation: "Use a supported exact release or add and verify its external manifest and runtime projection.",
326
- }));
190
+ if (options.detectedRelease !== options.installedRelease) {
191
+ options.findings.push(releaseFinding("brandlab-release-declared-installed-mismatch", options.installedRelease, options.detectedRelease ?? "unresolved", "Restore one exact package version across all four Brandlab packages.", "package.json"));
327
192
  }
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
- addInstalledPackageLockFindings({
193
+ inspectBrandlabPackages({
340
194
  findings: options.findings,
341
- packageJson,
195
+ packageJson: options.packageJson,
342
196
  packageLock: options.packageLock,
343
- installedRelease,
344
- installedAttestation: options.installedAttestation,
345
- });
346
- const versionIssues = evaluateSharedPackageVersionSpecs({
347
- packageJson,
348
- expectedVersion: installedRelease,
349
- packageNames: BRANDLAB_SHARED_PACKAGE_NAMES,
350
- });
351
- for (const issue of versionIssues) {
352
- options.findings.push(releaseFinding({
353
- ruleId: `brandlab-release-package-${issue.issue}`,
354
- severity: "blocker",
355
- expected: `${issue.packageName}@${installedRelease}`,
356
- actual: `${issue.packageName} is ${issue.installedVersion ?? "missing"}.`,
357
- remediation: "Restore exact lockstep package pins; do not use ranges, tags or mixed releases.",
358
- path: "package.json",
359
- }));
360
- }
361
- if (options.detectedRelease && options.detectedRelease !== installedRelease) {
362
- options.findings.push(releaseFinding({
363
- ruleId: "brandlab-release-declared-installed-mismatch",
364
- severity: "blocker",
365
- expected: `Detected package release ${installedRelease}`,
366
- actual: `package.json resolves to ${options.detectedRelease}.`,
367
- remediation: "Use the detected exact release or correct all four package pins.",
368
- path: "package.json",
369
- }));
370
- }
371
- const lifecycle = options.supportPolicy.releases[installedRelease]?.lifecycle;
372
- if (options.installedManifest && !lifecycle) {
373
- options.findings.push(releaseFinding({
374
- ruleId: "brandlab-release-not-operationally-supported",
375
- severity: "blocker",
376
- expected: "An installed release listed in mutable support policy",
377
- actual: `${installedRelease} has a runtime contract but is not approved for operational use.`,
378
- remediation: "Use the current supported release or supply the synthetic candidate policy only inside packed release fixtures.",
379
- }));
380
- }
381
- if (lifecycle === "security-blocked" || lifecycle === "retired") {
382
- options.findings.push(releaseFinding({
383
- ruleId: `brandlab-release-${lifecycle}`,
384
- severity: "blocker",
385
- expected: "A current or tolerated installed release",
386
- actual: `${installedRelease} is ${lifecycle}.`,
387
- remediation: "Prepare an owner-approved migration to a supported release.",
388
- }));
389
- }
390
- if (options.installedManifest) {
391
- addInstalledRuntimeFindings({
392
- findings: options.findings,
393
- packageJson,
394
- packageLock: options.packageLock,
395
- manifest: options.installedManifest,
396
- });
397
- }
398
- }
399
- function addMigrationFindings(options) {
400
- if (!options.targetRelease) {
401
- if (options.installedRelease &&
402
- options.installedRelease !== options.supportPolicy.currentReleaseVersion) {
403
- options.findings.push({
404
- ...releaseFinding({
405
- ruleId: "brandlab-release-upgrade-available",
406
- severity: "info",
407
- expected: "No migration is active until a target release is approved.",
408
- actual: `${options.supportPolicy.currentReleaseVersion} is current; ${options.installedRelease} remains installed.`,
409
- remediation: "Keep monitoring the tolerated exact pin or select a separately approved target release.",
410
- }),
411
- classification: "advisory",
412
- });
413
- }
414
- return;
415
- }
416
- if (!options.targetManifest) {
417
- options.findings.push({
418
- ...releaseFinding({
419
- ruleId: "brandlab-migration-target-unsupported",
420
- severity: "blocker",
421
- expected: "A target release with an embedded runtime contract",
422
- actual: `${options.targetRelease} has no runtime release contract.`,
423
- remediation: "Select a manifest-backed runtime contract before preparing migration work.",
424
- }),
425
- classification: "migration",
426
- });
427
- return;
428
- }
429
- const targetLifecycle = options.supportPolicy.releases[options.targetRelease]?.lifecycle;
430
- if (!targetLifecycle) {
431
- options.findings.push({
432
- ...releaseFinding({
433
- ruleId: "brandlab-migration-target-not-approved",
434
- severity: "blocker",
435
- expected: "A target release explicitly approved in mutable support policy",
436
- actual: `${options.targetRelease} has a runtime contract but no approved support-policy lifecycle.`,
437
- remediation: "Keep the installed release unchanged until the candidate release and project migration are separately approved.",
438
- }),
439
- classification: "migration",
440
- });
441
- }
442
- if (targetLifecycle === "security-blocked" || targetLifecycle === "retired") {
443
- options.findings.push({
444
- ...releaseFinding({
445
- ruleId: `brandlab-migration-target-${targetLifecycle}`,
446
- severity: "blocker",
447
- expected: "A current or tolerated target release",
448
- actual: `${options.targetRelease} is ${targetLifecycle}.`,
449
- remediation: "Select a current or tolerated target release.",
450
- }),
451
- classification: "migration",
452
- });
453
- return;
454
- }
455
- if (!options.installedRelease || options.installedRelease === options.targetRelease)
456
- return;
457
- const comparison = compareBrandlabSemver(options.targetRelease, options.installedRelease);
458
- options.findings.push({
459
- ...releaseFinding({
460
- ruleId: comparison < 0
461
- ? "brandlab-migration-downgrade-review"
462
- : "brandlab-migration-target-selected",
463
- severity: comparison < 0 ? "blocker" : "info",
464
- expected: comparison < 0
465
- ? "A separately documented rollback decision"
466
- : "Target-release migration evidence",
467
- actual: `${options.installedRelease} -> ${options.targetRelease}`,
468
- remediation: comparison < 0
469
- ? "Use the immutable rollback manifest and record explicit owner approval."
470
- : "Address migration findings and preserve the installed release until target proof passes.",
471
- }),
472
- classification: "migration",
197
+ installedRelease: options.installedRelease,
198
+ attestation: options.attestation,
473
199
  });
474
- addTargetRuntimeFindings({
200
+ inspectRuntime({
475
201
  findings: options.findings,
476
202
  packageJson: options.packageJson,
477
203
  packageLock: options.packageLock,
478
- targetManifest: options.targetManifest,
204
+ manifest: options.manifest,
479
205
  });
480
206
  }
481
- function addInstalledPackageLockFindings(options) {
482
- const { packageLock } = options;
483
- if (packageLock.state !== "valid") {
484
- const detail = packageLock.state === "missing"
485
- ? "package-lock.json is missing."
486
- : packageLock.state === "unsupported"
487
- ? `package-lock.json uses unsupported lockfileVersion ${String(packageLock.lockfileVersion)}.`
488
- : "package-lock.json is invalid or has no npm root package entry.";
489
- options.findings.push(releaseFinding({
490
- ruleId: `brandlab-release-package-lock-${packageLock.state}`,
491
- severity: "blocker",
492
- expected: "A valid npm package-lock.json (lockfile v2 or v3) with root and resolved package evidence",
493
- actual: detail,
494
- remediation: "Regenerate the npm lockfile from the exact supported package declarations and review the resulting diff.",
495
- path: "package-lock.json",
496
- }));
207
+ function inspectBrandlabPackages(options) {
208
+ if (options.packageLock.state !== "valid") {
209
+ options.findings.push(releaseFinding(`brandlab-release-package-lock-${options.packageLock.state}`, "A valid npm lockfile v2 or v3", `package-lock.json state is ${options.packageLock.state}.`, "Regenerate the lockfile from the exact package declarations.", "package-lock.json"));
497
210
  return;
498
211
  }
499
212
  for (const packageName of BRANDLAB_SHARED_PACKAGE_NAMES) {
500
213
  const declared = declaredPackageSpec(options.packageJson, packageName);
501
- const lockedSpec = lockedRootSpec(packageLock, packageName);
502
- const resolvedPackage = packageLock.resolvedPackages.get(packageName);
503
- if (options.packageJson.dependencies?.[packageName] !== undefined &&
504
- options.packageJson.devDependencies?.[packageName] !== undefined) {
505
- options.findings.push(releaseFinding({
506
- ruleId: "brandlab-release-package-duplicate-declaration",
507
- severity: "blocker",
508
- expected: `${packageName} declared exactly once`,
509
- actual: `${packageName} appears in both dependencies and devDependencies.`,
510
- remediation: "Keep each Brandlab package in one appropriate dependency section.",
511
- path: "package.json",
512
- }));
513
- }
514
- if (packageLock.rootDependencies[packageName] !== undefined &&
515
- packageLock.rootDevDependencies[packageName] !== undefined) {
516
- options.findings.push(releaseFinding({
517
- ruleId: "brandlab-release-lock-duplicate-declaration",
518
- severity: "blocker",
519
- expected: `${packageName} recorded once in the lockfile root`,
520
- actual: `${packageName} appears in both root dependency sections.`,
521
- remediation: "Regenerate the lockfile from the single package.json declaration.",
522
- path: "package-lock.json",
523
- }));
524
- }
525
- if (lockedSpec !== options.installedRelease || lockedSpec !== declared) {
526
- options.findings.push(releaseFinding({
527
- ruleId: "brandlab-release-lock-root-spec-mismatch",
528
- severity: "blocker",
529
- expected: `${packageName}@${options.installedRelease} in package.json and the lockfile root`,
530
- actual: `package.json=${declared ?? "missing"}; lockfile=${lockedSpec ?? "missing"}.`,
531
- remediation: "Reinstall from the exact lockstep package declarations so root lock specifications match package.json.",
532
- path: "package-lock.json",
533
- }));
534
- }
535
- if (!resolvedPackage ||
536
- resolvedPackage.version !== options.installedRelease ||
537
- resolvedPackage.linked ||
538
- !resolvedPackage.resolved ||
539
- !resolvedPackage.integrity) {
540
- options.findings.push(releaseFinding({
541
- ruleId: "brandlab-release-lock-resolution-mismatch",
542
- severity: "blocker",
543
- expected: `${packageName} resolved to immutable ${options.installedRelease}`,
544
- actual: !resolvedPackage
545
- ? "No resolved node_modules package entry exists."
546
- : `version=${resolvedPackage.version ?? "missing"}; linked=${String(resolvedPackage.linked)}; resolved=${resolvedPackage.resolved ? "present" : "missing"}; integrity=${resolvedPackage.integrity ? "present" : "missing"}.`,
547
- remediation: "Produce a clean lockfile with a non-workspace resolved package, source and integrity at the declared exact release.",
548
- path: "package-lock.json",
549
- }));
214
+ const locked = lockedRootSpec(options.packageLock, packageName);
215
+ const resolved = options.packageLock.resolvedPackages.get(packageName);
216
+ const duplicate = options.packageJson.dependencies?.[packageName] !== undefined &&
217
+ options.packageJson.devDependencies?.[packageName] !== undefined;
218
+ if (duplicate || declared !== options.installedRelease || locked !== declared) {
219
+ options.findings.push(releaseFinding("brandlab-release-package-pin-mismatch", `${packageName}@${options.installedRelease} declared once and locked exactly`, `package.json=${declared ?? "missing"}; lockfile=${locked ?? "missing"}; duplicate=${String(duplicate)}.`, "Restore exact lockstep package declarations and regenerate the lockfile.", "package.json"));
550
220
  }
551
- const registryAttestation = options.installedAttestation?.packages[packageName].registry ?? null;
552
- 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
- }));
221
+ if (!resolved ||
222
+ resolved.version !== options.installedRelease ||
223
+ resolved.linked ||
224
+ !resolved.integrity ||
225
+ !resolved.resolved ||
226
+ !isApprovedRegistryResolution(resolved.resolved, packageName, options.installedRelease)) {
227
+ options.findings.push(releaseFinding("brandlab-release-package-resolution-mismatch", `${packageName}@${options.installedRelease} from the canonical npm registry`, !resolved
228
+ ? "The resolved lockfile package is missing."
229
+ : `version=${resolved.version ?? "missing"}; linked=${String(resolved.linked)}; integrity=${resolved.integrity ? "present" : "missing"}.`, "Run a clean npm install from the exact published release.", "package-lock.json"));
562
230
  }
563
- if (expectedIntegrity && resolvedPackage?.integrity !== expectedIntegrity) {
564
- options.findings.push(releaseFinding({
565
- ruleId: "brandlab-release-lock-integrity-mismatch",
566
- severity: "blocker",
567
- expected: `${packageName} lock integrity matching the immutable release manifest`,
568
- actual: resolvedPackage?.integrity
569
- ? "The lock integrity differs from verified registry evidence."
570
- : "The lock integrity is missing.",
571
- remediation: "Recreate the lock from the verified registry release and investigate unexpected package content.",
572
- path: "package-lock.json",
573
- }));
231
+ const expectedIntegrity = options.attestation?.packages[packageName].registry.integrity ?? null;
232
+ if (expectedIntegrity && resolved?.integrity !== expectedIntegrity) {
233
+ options.findings.push(releaseFinding("brandlab-release-package-integrity-mismatch", `${packageName} integrity matching the immutable manifest`, "The lockfile integrity differs from the release attestation.", "Regenerate the lockfile from the verified registry package.", "package-lock.json"));
574
234
  }
575
- if (options.installedAttestation &&
576
- (!resolvedPackage?.resolved ||
577
- !isApprovedBrandlabRegistryResolution(resolvedPackage.resolved, packageName, options.installedRelease))) {
578
- options.findings.push(releaseFinding({
579
- ruleId: "brandlab-release-lock-registry-source-mismatch",
580
- severity: "blocker",
581
- expected: `${packageName}@${options.installedRelease} from the canonical HTTPS npm registry artifact`,
582
- actual: resolvedPackage?.resolved
583
- ? "The lockfile resolution does not use the approved registry.npmjs.org package artifact."
584
- : "The lockfile resolution source is missing.",
585
- remediation: "Regenerate the lockfile with the canonical npm registry and the immutable manifest integrity; do not accept file, Git or alternate-registry resolutions.",
586
- path: "package-lock.json",
587
- }));
588
- }
589
- }
590
- }
591
- function isApprovedBrandlabRegistryResolution(resolved, packageName, version) {
592
- try {
593
- const url = new URL(resolved);
594
- const artifactName = packageName.split("/").at(-1);
595
- if (!artifactName)
596
- return false;
597
- return url.protocol === "https:" &&
598
- url.hostname === "registry.npmjs.org" &&
599
- url.port === "" &&
600
- url.username === "" &&
601
- url.password === "" &&
602
- url.search === "" &&
603
- url.hash === "" &&
604
- url.pathname === `/${packageName}/-/${artifactName}-${version}.tgz`;
605
- }
606
- catch {
607
- return false;
608
235
  }
609
236
  }
610
- function addInstalledRuntimeFindings(options) {
237
+ function inspectRuntime(options) {
611
238
  for (const [packageName, runtimeField] of RUNTIME_PACKAGE_FIELDS) {
612
239
  const expected = options.manifest.runtime[runtimeField];
613
240
  const declared = declaredPackageSpec(options.packageJson, packageName);
614
- if (declared !== expected) {
615
- options.findings.push(runtimeFinding({
616
- ruleId: `brandlab-runtime-${slugify(packageName)}-declaration`,
617
- expected: `${packageName}@${expected} declared exactly`,
618
- actual: `${packageName} is ${declared ?? "missing"}.`,
619
- remediation: `Use the ${options.manifest.releaseVersion} embedded runtime contract.`,
620
- path: "package.json",
621
- }));
622
- }
623
- if (options.packageLock.state !== "valid")
624
- continue;
625
- const lockedSpec = lockedRootSpec(options.packageLock, packageName);
241
+ const locked = lockedRootSpec(options.packageLock, packageName);
626
242
  const resolved = options.packageLock.resolvedPackages.get(packageName);
627
- if (lockedSpec !== expected || lockedSpec !== declared) {
628
- options.findings.push(runtimeFinding({
629
- ruleId: `brandlab-runtime-${slugify(packageName)}-lock-spec`,
630
- expected: `${packageName}@${expected} in the lockfile root`,
631
- actual: `package.json=${declared ?? "missing"}; lockfile=${lockedSpec ?? "missing"}.`,
632
- remediation: "Regenerate package-lock.json from the exact runtime declaration.",
633
- path: "package-lock.json",
634
- }));
635
- }
636
- if (!resolved || resolved.version !== expected || resolved.linked) {
637
- options.findings.push(runtimeFinding({
638
- ruleId: `brandlab-runtime-${slugify(packageName)}-resolution`,
639
- expected: `${packageName} resolved to ${expected}`,
640
- actual: !resolved
641
- ? "No resolved node_modules package entry exists."
642
- : `version=${resolved.version ?? "missing"}; linked=${String(resolved.linked)}.`,
643
- remediation: "Reinstall the immutable runtime baseline and preserve the reviewed lockfile.",
644
- path: "package-lock.json",
645
- }));
243
+ if (!runtimeVersionSatisfiesRelease(packageName, declared, expected) ||
244
+ locked !== declared ||
245
+ resolved?.version !== declared ||
246
+ resolved.linked) {
247
+ options.findings.push(runtimeFinding(`brandlab-runtime-${slugify(packageName)}`, runtimeExpectation(packageName, expected), `package.json=${declared ?? "missing"}; lockfile=${locked ?? "missing"}; resolved=${resolved?.version ?? "missing"}.`, `Use the supported ${options.manifest.releaseVersion} runtime or a newer exact security patch in the same framework line.`, "package.json"));
646
248
  }
647
249
  }
648
- const nodeEngine = options.packageJson.engines?.node ?? null;
649
- if (nodeEngine !== options.manifest.runtime.node) {
650
- options.findings.push(runtimeFinding({
651
- ruleId: "brandlab-runtime-node-declaration",
652
- expected: `engines.node=${options.manifest.runtime.node}`,
653
- actual: `engines.node=${nodeEngine ?? "missing"}.`,
654
- remediation: "Restore the installed release's Node engine declaration.",
655
- path: "package.json",
656
- }));
657
- }
658
- if (options.packageLock.state === "valid" &&
659
- options.packageLock.rootEngines.node !== options.manifest.runtime.node) {
660
- options.findings.push(runtimeFinding({
661
- ruleId: "brandlab-runtime-node-lock-spec",
662
- expected: `lockfile root engines.node=${options.manifest.runtime.node}`,
663
- actual: `lockfile root engines.node=${options.packageLock.rootEngines.node ?? "missing"}.`,
664
- remediation: "Regenerate package-lock.json after restoring the supported Node engine.",
665
- path: "package-lock.json",
666
- }));
250
+ const node = options.packageJson.engines?.node ?? null;
251
+ const lockNode = options.packageLock.rootEngines.node ?? null;
252
+ if (node !== options.manifest.runtime.node || lockNode !== node) {
253
+ options.findings.push(runtimeFinding("brandlab-runtime-node", `engines.node=${options.manifest.runtime.node}`, `package.json=${node ?? "missing"}; lockfile=${lockNode ?? "missing"}.`, "Restore the supported Node engine and regenerate the lockfile.", "package.json"));
667
254
  }
668
255
  if (options.packageJson.packageManager !== options.manifest.runtime.packageManager) {
669
- options.findings.push(runtimeFinding({
670
- ruleId: "brandlab-runtime-package-manager",
671
- expected: `packageManager=${options.manifest.runtime.packageManager}`,
672
- actual: `packageManager=${options.packageJson.packageManager ?? "missing"}.`,
673
- remediation: "Restore the installed release's exact npm package-manager declaration.",
674
- path: "package.json",
675
- }));
256
+ options.findings.push(runtimeFinding("brandlab-runtime-package-manager", options.manifest.runtime.packageManager, options.packageJson.packageManager ?? "missing", "Use the package manager declared by the current release.", "package.json"));
676
257
  }
677
- }
678
- function addTargetRuntimeFindings(options) {
679
- if (!options.packageJson)
680
- return;
681
- const packageMismatches = [];
682
- for (const packageName of BRANDLAB_SHARED_PACKAGE_NAMES) {
683
- const declared = declaredPackageSpec(options.packageJson, packageName);
684
- const locked = lockedRootSpec(options.packageLock, packageName);
685
- const resolved = options.packageLock.resolvedPackages.get(packageName)?.version ?? null;
686
- if (declared !== options.targetManifest.releaseVersion ||
687
- locked !== options.targetManifest.releaseVersion ||
688
- resolved !== options.targetManifest.releaseVersion) {
689
- packageMismatches.push(`${packageName}(declared=${declared ?? "missing"},locked=${locked ?? "missing"},resolved=${resolved ?? "missing"})`);
258
+ const expectedSharp = options.manifest.runtime.sharp;
259
+ if (expectedSharp) {
260
+ const override = options.packageJson.overrides?.sharp ?? null;
261
+ if (override !== expectedSharp ||
262
+ options.packageLock.sharpResolutions.length !== 1 ||
263
+ options.packageLock.sharpResolutions[0]?.version !== expectedSharp) {
264
+ options.findings.push(runtimeFinding("brandlab-runtime-sharp", `one sharp@${expectedSharp} resolution`, `override=${override ?? "missing"}; resolutions=${options.packageLock.sharpResolutions.map((entry) => `${entry.path}=${entry.version}`).join(", ") || "none"}.`, "Restore the release-owned Sharp override and regenerate the lockfile.", "package-lock.json"));
690
265
  }
691
266
  }
692
- if (packageMismatches.length > 0) {
693
- options.findings.push(migrationRuntimeFinding({
694
- ruleId: "brandlab-migration-package-provenance",
695
- expected: `All four packages declared and locked at ${options.targetManifest.releaseVersion}`,
696
- actual: packageMismatches.join("; "),
697
- remediation: "Apply the target exact pins in an approved migration branch and regenerate the reviewed lockfile.",
267
+ }
268
+ function inspectProjectConfig(root, findings) {
269
+ const configPath = join(root, "brandlab.config.ts");
270
+ const source = readOptionalText(configPath);
271
+ if (!source) {
272
+ findings.push(staticFinding({
273
+ ruleId: "brandlab-config-missing",
274
+ surface: "routes",
275
+ path: "brandlab.config.ts",
276
+ expected: "One small typed Brandlab project configuration",
277
+ actual: "brandlab.config.ts is missing.",
278
+ remediation: "Add project identity, route families and enabled capabilities.",
698
279
  }));
280
+ return;
699
281
  }
700
- const runtimeMismatches = [];
701
- for (const [packageName, runtimeField] of RUNTIME_PACKAGE_FIELDS) {
702
- const expected = options.targetManifest.runtime[runtimeField];
703
- const declared = declaredPackageSpec(options.packageJson, packageName);
704
- const locked = lockedRootSpec(options.packageLock, packageName);
705
- const resolved = options.packageLock.resolvedPackages.get(packageName)?.version ?? null;
706
- if (declared !== expected || locked !== expected || resolved !== expected) {
707
- runtimeMismatches.push(`${packageName}(expected=${expected},declared=${declared ?? "missing"},locked=${locked ?? "missing"},resolved=${resolved ?? "missing"})`);
708
- }
709
- }
710
- if (options.packageJson.engines?.node !== options.targetManifest.runtime.node) {
711
- runtimeMismatches.push(`node(expected=${options.targetManifest.runtime.node},actual=${options.packageJson.engines?.node ?? "missing"})`);
712
- }
713
- if (options.packageJson.packageManager !== options.targetManifest.runtime.packageManager) {
714
- runtimeMismatches.push(`packageManager(expected=${options.targetManifest.runtime.packageManager},actual=${options.packageJson.packageManager ?? "missing"})`);
715
- }
716
- if (runtimeMismatches.length > 0) {
717
- options.findings.push(migrationRuntimeFinding({
718
- ruleId: "brandlab-migration-runtime-baseline",
719
- expected: `The ${options.targetManifest.releaseVersion} immutable runtime baseline`,
720
- actual: runtimeMismatches.join("; "),
721
- remediation: "Apply and lock the target runtime only inside the separately approved migration.",
282
+ if (!/\bdefineBrandlabProject\s*\(\s*\{/.test(source) ||
283
+ !/\bprojectId\s*:\s*['"][a-z0-9][a-z0-9-]*['"]/.test(source) ||
284
+ !/\bprofile\s*:\s*['"]next-(?:local-service|root-level)['"]/.test(source)) {
285
+ findings.push(staticFinding({
286
+ ruleId: "brandlab-config-invalid",
287
+ surface: "routes",
288
+ path: "brandlab.config.ts",
289
+ expected: "A literal defineBrandlabProject configuration with projectId and profile",
290
+ actual: "The minimum project identity contract could not be verified.",
291
+ remediation: "Keep the configuration literal and limited to project variation.",
722
292
  }));
723
293
  }
724
- }
725
- function runtimeFinding(options) {
726
- return {
727
- ruleId: options.ruleId,
728
- surface: "runtime",
729
- classification: "maintenance",
730
- severity: "blocker",
731
- contract: { name: "runtime", version: "installed-runtime-contract" },
732
- evidenceKind: "static",
733
- path: options.path,
734
- expected: options.expected,
735
- actual: options.actual,
736
- remediation: options.remediation,
737
- };
738
- }
739
- function migrationRuntimeFinding(options) {
740
- return {
741
- ruleId: options.ruleId,
742
- surface: "runtime",
743
- classification: "migration",
744
- severity: "blocker",
745
- contract: { name: "runtime", version: "target-runtime-contract" },
746
- evidenceKind: "static",
747
- path: "package-lock.json",
748
- expected: options.expected,
749
- actual: options.actual,
750
- remediation: options.remediation,
751
- };
752
- }
753
- function evaluateApplicableArchitectureContracts(options) {
754
- const installedConfiguration = options.installedManifest?.contracts.configuration ?? null;
755
- const targetConfiguration = options.targetManifest?.contracts.configuration ?? null;
756
- const hasDistinctTarget = Boolean(options.targetRelease &&
757
- options.targetRelease !== options.installedManifest?.releaseVersion);
758
- let configV2Inspection = null;
759
- const inspectConfigV2 = () => {
760
- configV2Inspection ??= inspectImmutableBrandlabConfigV2(options.root);
761
- return configV2Inspection;
762
- };
763
- let configV3Inspection = null;
764
- const inspectConfigV3 = () => {
765
- configV3Inspection ??= inspectBrandlabConfigV3(options.root);
766
- return configV3Inspection;
767
- };
768
- const legacyInspections = new Map();
769
- let legacyArchitectureInput;
770
- let legacyArchitectureInputResolved = false;
771
- const inspectLegacy = (contract) => {
772
- const cached = legacyInspections.get(contract);
773
- if (cached)
774
- return cached;
775
- if (!legacyArchitectureInputResolved) {
776
- legacyArchitectureInput = options.architecture === undefined
777
- ? readBrandlabArchitectureConfig(options.root)
778
- : options.architecture;
779
- legacyArchitectureInputResolved = true;
780
- }
781
- const inspection = inspectLegacyArchitectureContract({
782
- root: options.root,
783
- architecture: legacyArchitectureInput,
784
- contract,
785
- });
786
- legacyInspections.set(contract, inspection);
787
- return inspection;
788
- };
789
- if (installedConfiguration === CONFIGURATION_V2_CONTRACT) {
790
- const inspection = inspectConfigV2();
791
- for (const issue of inspection.issues) {
792
- options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
294
+ const envSource = readOptionalText(join(root, ".env.example")) ?? "";
295
+ if (/\bcontact\s*:\s*\{/.test(source)) {
296
+ requireEnvKey(findings, envSource, "RESEND_API_KEY", "contact");
297
+ requireEnvKey(findings, envSource, "CONTACT_EMAIL", "contact");
298
+ }
299
+ if (/\breviews\s*:\s*\{[\s\S]*?\bprovider\s*:\s*['"]google-places['"]/.test(source)) {
300
+ requireEnvKey(findings, envSource, "GOOGLE_MAPS_PLATFORM_API_KEY", "reviews");
301
+ }
302
+ for (const retired of ["CONTACT_CONTEXT_SECRET", "KV_REST_API_URL", "KV_REST_API_TOKEN"]) {
303
+ if (new RegExp(`^${retired}=`, "m").test(envSource)) {
304
+ findings.push(staticFinding({
305
+ ruleId: "brandlab-environment-retired-contact-state",
306
+ surface: "environment",
307
+ path: ".env.example",
308
+ expected: "Only current contact environment variables",
309
+ actual: `${retired} is still documented.`,
310
+ remediation: `Remove ${retired}; it is not part of the simplified contact standard.`,
311
+ }));
793
312
  }
794
313
  }
795
- else if (installedConfiguration === CONFIGURATION_V3_CONTRACT) {
796
- const inspection = inspectConfigV3();
797
- for (const issue of inspection.issues) {
798
- options.findings.push(mapConfigIssue(issue, "maintenance", options.installedManifest?.contracts));
314
+ }
315
+ function requireEnvKey(findings, envSource, key, capability) {
316
+ if (new RegExp(`^${key}=`, "m").test(envSource))
317
+ return;
318
+ findings.push(staticFinding({
319
+ ruleId: `brandlab-environment-${capability}-${slugify(key)}`,
320
+ surface: "environment",
321
+ path: ".env.example",
322
+ expected: `${key} documented for the enabled ${capability} capability`,
323
+ actual: `${key} is missing from .env.example.`,
324
+ remediation: `Document the non-secret ${key} variable name without adding a credential value.`,
325
+ }));
326
+ }
327
+ function inspectProjectSource(root, findings) {
328
+ const sourceRoot = join(root, "src");
329
+ if (!existsSync(sourceRoot))
330
+ return;
331
+ for (const file of sourceFiles(sourceRoot)) {
332
+ const source = readFileSync(file, "utf8");
333
+ const executableSource = stripCommentsPreservingLayout(source);
334
+ for (const rule of SOURCE_RULES) {
335
+ const match = executableSource.match(rule.pattern);
336
+ if (!match || match.index === undefined)
337
+ continue;
338
+ findings.push(staticFinding({
339
+ ruleId: rule.ruleId,
340
+ surface: rule.surface,
341
+ path: normalizePath(relative(root, file)),
342
+ line: lineAt(source, match.index),
343
+ expected: rule.expected,
344
+ actual: "A project-owned implementation was detected.",
345
+ remediation: rule.remediation,
346
+ }));
799
347
  }
800
348
  }
801
- else if (installedConfiguration === LEGACY_CONFIGURATION_CONTRACT) {
802
- const installedArchitecture = options.installedManifest?.contracts.architecture ?? null;
803
- if (installedArchitecture === "architecture-v1" || installedArchitecture === "architecture-v2") {
804
- const installedInspection = inspectLegacy(installedArchitecture);
805
- for (const issue of installedInspection.issues) {
806
- options.findings.push(mapArchitectureIssue(issue, "maintenance", {
807
- contractVersion: installedArchitecture,
808
- }));
349
+ }
350
+ function stripCommentsPreservingLayout(source) {
351
+ const output = [...source];
352
+ let state = "code";
353
+ let escaped = false;
354
+ for (let index = 0; index < output.length; index += 1) {
355
+ const current = output[index];
356
+ const next = output[index + 1];
357
+ if (state === "line") {
358
+ if (current === "\n" || current === "\r") {
359
+ state = "code";
809
360
  }
810
- if (installedArchitecture === "architecture-v1" &&
811
- !(hasDistinctTarget &&
812
- options.targetManifest?.contracts.architecture === "architecture-v2")) {
813
- const v2Inspection = inspectLegacy("architecture-v2");
814
- for (const issue of targetOnlyLegacyArchitectureIssues(installedInspection, v2Inspection)) {
815
- options.findings.push(mapArchitectureIssue(issue, "advisory", {
816
- contractVersion: "architecture-v2",
817
- }));
818
- }
361
+ else {
362
+ output[index] = " ";
819
363
  }
364
+ continue;
820
365
  }
821
- }
822
- else if (!options.installedManifest) {
823
- // Unsupported or unadopted projects still benefit from architecture
824
- // diagnosis, but no current evaluator may retroactively become their
825
- // installed maintenance contract. Keep this lane advisory until a
826
- // supported target release is explicitly selected.
827
- const configSource = readOptionalText(join(options.root, "brandlab.config.ts"));
828
- if (/\bdefineBrandlabProject\s*\(/.test(configSource ?? "")) {
829
- for (const issue of inspectConfigV3().issues) {
830
- options.findings.push(mapConfigIssue(issue, "advisory", {
831
- configuration: CONFIGURATION_V3_CONTRACT,
832
- }));
366
+ if (state === "block") {
367
+ if (current === "*" && next === "/") {
368
+ output[index] = " ";
369
+ output[index + 1] = " ";
370
+ index += 1;
371
+ state = "code";
372
+ }
373
+ else if (current !== "\n" && current !== "\r") {
374
+ output[index] = " ";
833
375
  }
376
+ continue;
834
377
  }
835
- else {
836
- for (const issue of inspectLegacy("architecture-v2").issues) {
837
- options.findings.push(mapArchitectureIssue(issue, "advisory", {
838
- contractVersion: "architecture-v2",
839
- }));
378
+ if (state !== "code") {
379
+ if (escaped) {
380
+ escaped = false;
381
+ }
382
+ else if (current === "\\") {
383
+ escaped = true;
840
384
  }
385
+ else if ((state === "single" && current === "'") ||
386
+ (state === "double" && current === '"') ||
387
+ (state === "template" && current === "`")) {
388
+ state = "code";
389
+ }
390
+ continue;
841
391
  }
842
- }
843
- if (hasDistinctTarget && targetConfiguration === CONFIGURATION_V2_CONTRACT) {
844
- for (const issue of inspectConfigV2().issues) {
845
- options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
392
+ if (current === "'") {
393
+ state = "single";
846
394
  }
847
- }
848
- else if (hasDistinctTarget && targetConfiguration === CONFIGURATION_V3_CONTRACT) {
849
- for (const issue of inspectConfigV3().issues) {
850
- options.findings.push(mapConfigIssue(issue, "migration", options.targetManifest?.contracts));
395
+ else if (current === '"') {
396
+ state = "double";
851
397
  }
852
- }
853
- else if (hasDistinctTarget && targetConfiguration === LEGACY_CONFIGURATION_CONTRACT) {
854
- const targetArchitecture = options.targetManifest?.contracts.architecture ?? null;
855
- if (targetArchitecture === "architecture-v1" || targetArchitecture === "architecture-v2") {
856
- const installedArchitecture = options.installedManifest?.contracts.architecture ?? null;
857
- const installedInspection = installedConfiguration === LEGACY_CONFIGURATION_CONTRACT &&
858
- (installedArchitecture === "architecture-v1" || installedArchitecture === "architecture-v2")
859
- ? inspectLegacy(installedArchitecture)
860
- : null;
861
- const targetInspection = inspectLegacy(targetArchitecture);
862
- for (const issue of targetOnlyLegacyArchitectureIssues(installedInspection, targetInspection)) {
863
- options.findings.push(mapArchitectureIssue(issue, "migration", {
864
- contractVersion: targetArchitecture,
865
- }));
866
- }
398
+ else if (current === "`") {
399
+ state = "template";
400
+ }
401
+ else if (current === "/" && next === "/") {
402
+ output[index] = " ";
403
+ output[index + 1] = " ";
404
+ index += 1;
405
+ state = "line";
406
+ }
407
+ else if (current === "/" && next === "*") {
408
+ output[index] = " ";
409
+ output[index + 1] = " ";
410
+ index += 1;
411
+ state = "block";
867
412
  }
868
413
  }
869
- const inspectedConfigV2 = configV2Inspection;
870
- const inspectedConfigV3 = configV3Inspection;
871
- return {
872
- evaluator: BRANDLAB_ARCHITECTURE_DISPATCH_RULESET_ID,
873
- installedContract: options.installedManifest?.contracts.architecture ?? null,
874
- targetContract: options.targetManifest?.contracts.architecture ?? null,
875
- legacy: Object.fromEntries([...legacyInspections.entries()]
876
- .sort(([left], [right]) => left.localeCompare(right))
877
- .map(([contract, inspection]) => [
878
- contract,
879
- {
880
- rulesetId: inspection.rulesetId,
881
- profile: inspection.report.profile,
882
- status: inspection.report.status,
883
- pages: inspection.report.pageFileCount,
884
- applicableRuleIds: inspection.issues.map((issue) => issue.code).sort(),
885
- },
886
- ])),
887
- configV2: inspectedConfigV2
888
- ? {
889
- projectId: inspectedConfigV2.config?.projectId ?? null,
890
- profile: inspectedConfigV2.config?.profile ?? null,
891
- imports: [...inspectedConfigV2.imports].sort(),
892
- executableImports: [...inspectedConfigV2.executableImports].sort(),
893
- }
894
- : null,
895
- configV3: inspectedConfigV3
896
- ? {
897
- projectId: inspectedConfigV3.config?.projectId ?? null,
898
- profile: inspectedConfigV3.config?.profile ?? null,
899
- imports: [...inspectedConfigV3.imports].sort(),
900
- executableImports: [...inspectedConfigV3.executableImports].sort(),
901
- outputOwnedImports: [...inspectedConfigV3.outputOwnedImports].sort(),
902
- executableExports: Object.fromEntries([...inspectedConfigV3.executableExports.entries()]
903
- .sort(([left], [right]) => left.localeCompare(right))
904
- .map(([moduleName, exports]) => [moduleName, [...exports].sort()])),
905
- outputOwnedExports: Object.fromEntries([...inspectedConfigV3.outputOwnedExports.entries()]
906
- .sort(([left], [right]) => left.localeCompare(right))
907
- .map(([moduleName, exports]) => [moduleName, [...exports].sort()])),
908
- }
909
- : null,
910
- };
911
- }
912
- function mapArchitectureIssue(issue, classification, options) {
913
- const rawSeverity = issue.severity === "error"
914
- ? "blocker"
915
- : issue.severity === "warning"
916
- ? "warning"
917
- : "info";
918
- const severity = classification === "advisory" && rawSeverity === "blocker"
919
- ? "warning"
920
- : rawSeverity;
921
- return {
922
- ruleId: issue.code,
923
- surface: surfaceForRule(issue.code),
924
- classification,
925
- severity,
926
- contract: {
927
- name: "architecture",
928
- version: options.contractVersion ?? LEGACY_ARCHITECTURE_EVALUATOR_CONTRACT,
929
- },
930
- evidenceKind: "static",
931
- path: issue.path,
932
- expected: issue.fix ?? "The project matches its applicable Brandlab architecture contract.",
933
- actual: issue.detail,
934
- remediation: issue.fix ?? "Apply the supported shared-platform implementation for this rule.",
935
- };
414
+ return output.join("");
936
415
  }
937
- function mapConfigIssue(issue, classification, contracts) {
938
- const severity = classification === "advisory" && issue.severity === "blocker"
939
- ? "warning"
940
- : issue.severity;
941
- const contractName = contractNameForConfigSurface(issue.surface);
942
- const contractVersion = contracts?.[contractName] ??
943
- contracts?.configuration ??
944
- CONFIGURATION_V3_CONTRACT;
945
- return {
946
- ruleId: issue.ruleId,
947
- surface: issue.surface,
948
- classification,
949
- severity,
950
- contract: {
951
- name: contractName,
952
- version: contractVersion,
953
- },
954
- evidenceKind: issue.evidenceKind,
955
- path: issue.path,
956
- line: issue.line,
957
- expected: issue.expected,
958
- actual: issue.actual,
959
- remediation: issue.remediation,
960
- };
961
- }
962
- function contractNameForConfigSurface(surface) {
963
- if (/contact|email|delivery|attachment/.test(surface))
964
- return "contact";
965
- if (/analytics/.test(surface))
966
- return "analytics";
967
- if (/metadata|schema|breadcrumb|sitemap|robots|location|review|google-places/.test(surface)) {
968
- return "seo";
969
- }
970
- if (/platform-contract|business|routes|headers|accessibility|blog|architecture/.test(surface)) {
971
- return "architecture";
416
+ function sourceFiles(root) {
417
+ const files = [];
418
+ for (const entry of readdirSync(root, { withFileTypes: true })) {
419
+ if (entry.name.startsWith(".") || entry.name === "node_modules")
420
+ continue;
421
+ const path = join(root, entry.name);
422
+ if (entry.isDirectory()) {
423
+ files.push(...sourceFiles(path));
424
+ continue;
425
+ }
426
+ if (SOURCE_EXTENSIONS.has(extension(entry.name)) &&
427
+ !/\.(?:test|spec)\.[cm]?[jt]sx?$/.test(entry.name)) {
428
+ files.push(path);
429
+ }
972
430
  }
973
- return "configuration";
974
- }
975
- function releaseFinding(options) {
976
- return {
977
- ruleId: options.ruleId,
978
- surface: "release",
979
- classification: "maintenance",
980
- severity: options.severity,
981
- contract: { name: "release-authority", version: "v3" },
982
- evidenceKind: "static",
983
- path: options.path,
984
- expected: options.expected,
985
- actual: options.actual,
986
- remediation: options.remediation,
987
- };
988
- }
989
- function summarize(findings, classification) {
990
- const relevant = findings.filter((finding) => finding.classification === classification);
991
- const blockers = relevant.filter((finding) => finding.severity === "blocker").length;
992
- const warnings = relevant.filter((finding) => finding.severity === "warning").length;
993
- const info = relevant.filter((finding) => finding.severity === "info").length;
994
- return {
995
- status: blockers > 0 ? "blocked" : warnings > 0 ? "warning" : "pass",
996
- total: relevant.length,
997
- blockers,
998
- warnings,
999
- info,
1000
- };
431
+ return files.sort();
1001
432
  }
1002
433
  function readPackageJson(root) {
1003
434
  const source = readOptionalText(join(root, "package.json"));
@@ -1011,48 +442,38 @@ function readPackageJson(root) {
1011
442
  }
1012
443
  }
1013
444
  function readPackageLock(root) {
1014
- const path = join(root, "package-lock.json");
1015
- const source = readOptionalText(path);
1016
- if (!source) {
1017
- return emptyPackageLockInspection("missing");
1018
- }
445
+ const source = readOptionalText(join(root, "package-lock.json"));
446
+ if (!source)
447
+ return emptyPackageLock("missing");
1019
448
  let parsed;
1020
449
  try {
1021
450
  parsed = JSON.parse(source);
1022
451
  }
1023
452
  catch {
1024
- return emptyPackageLockInspection("invalid", {
1025
- digest: digestBrandlabAuthority(source),
1026
- });
1027
- }
1028
- if (!isObjectRecord(parsed)) {
1029
- return emptyPackageLockInspection("invalid", {
1030
- digest: digestBrandlabAuthority(parsed),
1031
- });
453
+ return emptyPackageLock("invalid", { digest: digestBrandlabAuthority(source) });
1032
454
  }
1033
- const lockfileVersion = typeof parsed.lockfileVersion === "number"
1034
- ? parsed.lockfileVersion
1035
- : null;
455
+ if (!isRecord(parsed))
456
+ return emptyPackageLock("invalid");
457
+ const lockfileVersion = typeof parsed.lockfileVersion === "number" ? parsed.lockfileVersion : null;
1036
458
  if (lockfileVersion !== 2 && lockfileVersion !== 3) {
1037
- return emptyPackageLockInspection("unsupported", {
1038
- lockfileVersion,
1039
- digest: digestBrandlabAuthority(parsed),
1040
- });
459
+ return emptyPackageLock("unsupported", { lockfileVersion });
1041
460
  }
1042
- if (!isObjectRecord(parsed.packages) || !isObjectRecord(parsed.packages[""])) {
1043
- return emptyPackageLockInspection("invalid", {
1044
- lockfileVersion,
1045
- digest: digestBrandlabAuthority(parsed),
1046
- });
461
+ if (!isRecord(parsed.packages) || !isRecord(parsed.packages[""])) {
462
+ return emptyPackageLock("invalid", { lockfileVersion });
1047
463
  }
1048
464
  const rootPackage = parsed.packages[""];
1049
465
  const rootDependencies = stringRecord(rootPackage.dependencies);
1050
466
  const rootDevDependencies = stringRecord(rootPackage.devDependencies);
1051
467
  const rootEngines = stringRecord(rootPackage.engines);
1052
468
  const resolvedPackages = new Map();
1053
- for (const packageName of [...BRANDLAB_SHARED_PACKAGE_NAMES, ...RUNTIME_PACKAGE_FIELDS.map(([name]) => name)]) {
469
+ const packageNames = [
470
+ ...BRANDLAB_SHARED_PACKAGE_NAMES,
471
+ ...RUNTIME_PACKAGE_FIELDS.map(([name]) => name),
472
+ "sharp",
473
+ ];
474
+ for (const packageName of packageNames) {
1054
475
  const entry = parsed.packages[`node_modules/${packageName}`];
1055
- if (!isObjectRecord(entry))
476
+ if (!isRecord(entry))
1056
477
  continue;
1057
478
  resolvedPackages.set(packageName, {
1058
479
  version: typeof entry.version === "string" ? entry.version : null,
@@ -1061,6 +482,13 @@ function readPackageLock(root) {
1061
482
  linked: entry.link === true,
1062
483
  });
1063
484
  }
485
+ const sharpResolutions = Object.entries(parsed.packages)
486
+ .filter(([path]) => path === "node_modules/sharp" || path.endsWith("/node_modules/sharp"))
487
+ .map(([path, entry]) => ({
488
+ path,
489
+ version: isRecord(entry) && typeof entry.version === "string" ? entry.version : null,
490
+ }))
491
+ .sort((left, right) => left.path.localeCompare(right.path));
1064
492
  return {
1065
493
  state: "valid",
1066
494
  lockfileVersion,
@@ -1068,170 +496,135 @@ function readPackageLock(root) {
1068
496
  rootDevDependencies,
1069
497
  rootEngines,
1070
498
  resolvedPackages,
499
+ sharpResolutions,
1071
500
  fingerprint: {
1072
- state: "valid",
1073
501
  lockfileVersion,
1074
- root: {
1075
- packages: Object.fromEntries([...BRANDLAB_SHARED_PACKAGE_NAMES, ...RUNTIME_PACKAGE_FIELDS.map(([name]) => name)]
1076
- .map((packageName) => [packageName, lockedRootSpec({
1077
- state: "valid",
1078
- lockfileVersion,
1079
- rootDependencies,
1080
- rootDevDependencies,
1081
- rootEngines,
1082
- resolvedPackages,
1083
- fingerprint: {},
1084
- }, packageName)])),
1085
- node: rootEngines.node ?? null,
1086
- },
1087
- resolved: Object.fromEntries([...resolvedPackages.entries()].map(([packageName, entry]) => [
1088
- packageName,
1089
- { version: entry.version, linked: entry.linked },
502
+ rootDependencies,
503
+ rootDevDependencies,
504
+ rootEngines,
505
+ resolved: Object.fromEntries([...resolvedPackages].map(([name, value]) => [
506
+ name,
507
+ { version: value.version, linked: value.linked },
1090
508
  ])),
509
+ sharpResolutions,
1091
510
  },
1092
511
  };
1093
512
  }
1094
- function emptyPackageLockInspection(state, extra = {}) {
513
+ function emptyPackageLock(state, fingerprint = {}) {
1095
514
  return {
1096
515
  state,
1097
- lockfileVersion: typeof extra.lockfileVersion === "number" ? extra.lockfileVersion : null,
516
+ lockfileVersion: typeof fingerprint.lockfileVersion === "number"
517
+ ? fingerprint.lockfileVersion
518
+ : null,
1098
519
  rootDependencies: {},
1099
520
  rootDevDependencies: {},
1100
521
  rootEngines: {},
1101
522
  resolvedPackages: new Map(),
1102
- fingerprint: { state, ...extra },
523
+ sharpResolutions: [],
524
+ fingerprint: { state, ...fingerprint },
1103
525
  };
1104
526
  }
527
+ function runtimeVersionSatisfiesRelease(packageName, actual, expected) {
528
+ if (!actual || !isBrandlabSemver(actual))
529
+ return false;
530
+ if (!["next", "react", "react-dom"].includes(packageName))
531
+ return actual === expected;
532
+ const actualParts = actual.match(/^(\d+)\.(\d+)\.(\d+)$/);
533
+ const expectedParts = expected.match(/^(\d+)\.(\d+)\.(\d+)$/);
534
+ return Boolean(actualParts &&
535
+ expectedParts &&
536
+ actualParts[1] === expectedParts[1] &&
537
+ actualParts[2] === expectedParts[2] &&
538
+ compareBrandlabSemver(actual, expected) >= 0);
539
+ }
540
+ function runtimeExpectation(packageName, expected) {
541
+ return ["next", "react", "react-dom"].includes(packageName)
542
+ ? `${packageName}@${expected} or a newer exact patch in the same major/minor line`
543
+ : `${packageName}@${expected}`;
544
+ }
545
+ function isApprovedRegistryResolution(resolved, packageName, version) {
546
+ try {
547
+ const url = new URL(resolved);
548
+ const artifact = packageName.split("/").at(-1);
549
+ return Boolean(artifact &&
550
+ url.protocol === "https:" &&
551
+ url.hostname === "registry.npmjs.org" &&
552
+ url.pathname === `/${packageName}/-/${artifact}-${version}.tgz`);
553
+ }
554
+ catch {
555
+ return false;
556
+ }
557
+ }
1105
558
  function declaredPackageSpec(packageJson, packageName) {
1106
- return packageJson.dependencies?.[packageName] ??
559
+ return (packageJson.dependencies?.[packageName] ??
1107
560
  packageJson.devDependencies?.[packageName] ??
1108
- null;
1109
- }
1110
- function lockedRootSpec(packageLock, packageName) {
1111
- return packageLock.rootDependencies[packageName] ??
1112
- packageLock.rootDevDependencies[packageName] ??
1113
- null;
1114
- }
1115
- function stringRecord(value) {
1116
- if (!isObjectRecord(value))
1117
- return {};
1118
- return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
1119
- }
1120
- function isObjectRecord(value) {
1121
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
561
+ null);
1122
562
  }
1123
- function readOptionalText(path) {
1124
- return existsSync(path) ? readFileSync(path, "utf8") : null;
563
+ function lockedRootSpec(lock, packageName) {
564
+ return lock.rootDependencies[packageName] ?? lock.rootDevDependencies[packageName] ?? null;
1125
565
  }
1126
- function extractDelimitedProperty(source, propertyName, openChar, closeChar) {
1127
- const propertyMatch = source.match(new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:`));
1128
- if (!propertyMatch || propertyMatch.index === undefined)
1129
- return null;
1130
- const start = source.indexOf(openChar, propertyMatch.index + propertyMatch[0].length);
1131
- if (start === -1)
1132
- return null;
1133
- return extractDelimitedBody(source, start, openChar, closeChar);
566
+ function releaseFinding(ruleId, expected, actual, remediation, path) {
567
+ return {
568
+ ruleId,
569
+ surface: "release",
570
+ severity: "blocker",
571
+ contract: { name: "release", version: "current-release" },
572
+ evidenceKind: "static",
573
+ ...(path ? { path } : {}),
574
+ expected,
575
+ actual,
576
+ remediation,
577
+ };
1134
578
  }
1135
- function extractDelimitedBody(source, start, openChar, closeChar) {
1136
- let depth = 0;
1137
- let quote = null;
1138
- let escaped = false;
1139
- for (let index = start; index < source.length; index += 1) {
1140
- const char = source[index];
1141
- if (quote) {
1142
- if (escaped)
1143
- escaped = false;
1144
- else if (char === "\\")
1145
- escaped = true;
1146
- else if (char === quote)
1147
- quote = null;
1148
- continue;
1149
- }
1150
- if (char === '"' || char === "'" || char === "`")
1151
- quote = char;
1152
- else if (char === openChar)
1153
- depth += 1;
1154
- else if (char === closeChar) {
1155
- depth -= 1;
1156
- if (depth === 0)
1157
- return source.slice(start + 1, index);
1158
- }
1159
- }
1160
- return null;
579
+ function runtimeFinding(ruleId, expected, actual, remediation, path) {
580
+ return {
581
+ ruleId,
582
+ surface: "runtime",
583
+ severity: "blocker",
584
+ contract: { name: "runtime", version: "installed-release" },
585
+ evidenceKind: "static",
586
+ path,
587
+ expected,
588
+ actual,
589
+ remediation,
590
+ };
1161
591
  }
1162
- function extractObjectBodies(source) {
1163
- const bodies = [];
1164
- let quote = null;
1165
- let escaped = false;
1166
- let arrayDepth = 0;
1167
- for (let index = 0; index < source.length; index += 1) {
1168
- const char = source[index];
1169
- if (quote) {
1170
- if (escaped)
1171
- escaped = false;
1172
- else if (char === "\\")
1173
- escaped = true;
1174
- else if (char === quote)
1175
- quote = null;
1176
- continue;
1177
- }
1178
- if (char === '"' || char === "'" || char === "`") {
1179
- quote = char;
1180
- continue;
1181
- }
1182
- if (char === "[")
1183
- arrayDepth += 1;
1184
- else if (char === "]")
1185
- arrayDepth -= 1;
1186
- else if (char === "{" && arrayDepth === 0) {
1187
- const body = extractDelimitedBody(source, index, "{", "}");
1188
- if (body !== null) {
1189
- bodies.push(body);
1190
- index += body.length + 1;
1191
- }
1192
- }
1193
- }
1194
- return bodies;
592
+ function staticFinding(options) {
593
+ return {
594
+ ruleId: options.ruleId,
595
+ surface: options.surface,
596
+ severity: "blocker",
597
+ contract: { name: "project", version: "current-only-v2" },
598
+ evidenceKind: "static",
599
+ ...(options.path ? { path: options.path } : {}),
600
+ ...(options.line ? { line: options.line } : {}),
601
+ expected: options.expected,
602
+ actual: options.actual,
603
+ remediation: options.remediation,
604
+ };
1195
605
  }
1196
- function extractStringProperty(source, propertyName) {
1197
- return source.match(new RegExp("\\b" + escapeRegExp(propertyName) + "\\s*:\\s*['\"`]([^'\"`]+)['\"`]", "m"))?.[1] ?? null;
606
+ function compareFindings(left, right) {
607
+ return (left.ruleId.localeCompare(right.ruleId) ||
608
+ (left.path ?? "").localeCompare(right.path ?? "") ||
609
+ (left.line ?? 0) - (right.line ?? 0));
1198
610
  }
1199
- function extractNumberProperty(source, propertyName) {
1200
- const raw = source.match(new RegExp(`\\b${escapeRegExp(propertyName)}\\s*:\\s*(\\d+)`, "m"))?.[1];
1201
- return raw ? Number(raw) : null;
611
+ function readOptionalText(path) {
612
+ return existsSync(path) ? readFileSync(path, "utf8") : null;
1202
613
  }
1203
- function extractStringArray(source, propertyName) {
1204
- const arraySource = extractDelimitedProperty(source, propertyName, "[", "]");
1205
- if (arraySource === null)
1206
- return undefined;
1207
- return [...arraySource.matchAll(/['\"]([^'\"]+)['\"]/g)].map((match) => match[1]);
614
+ function stringRecord(value) {
615
+ if (!isRecord(value))
616
+ return {};
617
+ return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string"));
1208
618
  }
1209
- function assignString(target, key, value) {
1210
- if (value !== null)
1211
- target[key] = value;
619
+ function isRecord(value) {
620
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1212
621
  }
1213
- function assignNumber(target, key, value) {
1214
- if (value !== null)
1215
- target[key] = value;
622
+ function extension(name) {
623
+ const index = name.lastIndexOf(".");
624
+ return index === -1 ? "" : name.slice(index);
1216
625
  }
1217
- function surfaceForRule(ruleId) {
1218
- for (const surface of [
1219
- "contact",
1220
- "metadata",
1221
- "schema",
1222
- "sitemap",
1223
- "breadcrumb",
1224
- "analytics",
1225
- "accessibility",
1226
- "runtime",
1227
- "release",
1228
- ]) {
1229
- if (ruleId.toLowerCase().includes(surface))
1230
- return surface;
1231
- }
1232
- if (/api key|secret|credential|recaptcha/i.test(ruleId))
1233
- return "security";
1234
- return "architecture";
626
+ function lineAt(source, index) {
627
+ return source.slice(0, index).split("\n").length;
1235
628
  }
1236
629
  function slugify(value) {
1237
630
  return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
@@ -1239,23 +632,4 @@ function slugify(value) {
1239
632
  function normalizePath(value) {
1240
633
  return value.split(/[/\\]/).join("/");
1241
634
  }
1242
- function escapeRegExp(value) {
1243
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1244
- }
1245
- function compareFindings(first, second) {
1246
- const classificationOrder = {
1247
- maintenance: 0,
1248
- migration: 1,
1249
- advisory: 2,
1250
- };
1251
- const severityOrder = {
1252
- blocker: 0,
1253
- warning: 1,
1254
- info: 2,
1255
- };
1256
- return (classificationOrder[first.classification] - classificationOrder[second.classification] ||
1257
- severityOrder[first.severity] - severityOrder[second.severity] ||
1258
- first.ruleId.localeCompare(second.ruleId) ||
1259
- (first.path ?? "").localeCompare(second.path ?? ""));
1260
- }
1261
635
  //# sourceMappingURL=project.js.map