@veraxhq/verax 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (217) hide show
  1. package/README.md +14 -18
  2. package/bin/verax.js +7 -0
  3. package/package.json +15 -5
  4. package/src/cli/commands/baseline.js +104 -0
  5. package/src/cli/commands/default.js +323 -111
  6. package/src/cli/commands/doctor.js +36 -4
  7. package/src/cli/commands/ga.js +243 -0
  8. package/src/cli/commands/gates.js +95 -0
  9. package/src/cli/commands/inspect.js +131 -2
  10. package/src/cli/commands/release-check.js +213 -0
  11. package/src/cli/commands/run.js +498 -103
  12. package/src/cli/commands/security-check.js +211 -0
  13. package/src/cli/commands/truth.js +114 -0
  14. package/src/cli/entry.js +305 -68
  15. package/src/cli/util/angular-component-extractor.js +179 -0
  16. package/src/cli/util/angular-navigation-detector.js +141 -0
  17. package/src/cli/util/angular-network-detector.js +161 -0
  18. package/src/cli/util/angular-state-detector.js +162 -0
  19. package/src/cli/util/ast-interactive-detector.js +546 -0
  20. package/src/cli/util/ast-network-detector.js +603 -0
  21. package/src/cli/util/ast-usestate-detector.js +602 -0
  22. package/src/cli/util/bootstrap-guard.js +86 -0
  23. package/src/cli/util/detection-engine.js +4 -3
  24. package/src/cli/util/determinism-runner.js +123 -0
  25. package/src/cli/util/determinism-writer.js +129 -0
  26. package/src/cli/util/env-url.js +4 -0
  27. package/src/cli/util/events.js +76 -0
  28. package/src/cli/util/expectation-extractor.js +380 -74
  29. package/src/cli/util/findings-writer.js +126 -15
  30. package/src/cli/util/learn-writer.js +3 -1
  31. package/src/cli/util/observation-engine.js +69 -23
  32. package/src/cli/util/observe-writer.js +3 -1
  33. package/src/cli/util/paths.js +6 -14
  34. package/src/cli/util/project-discovery.js +23 -0
  35. package/src/cli/util/project-writer.js +3 -1
  36. package/src/cli/util/redact.js +2 -2
  37. package/src/cli/util/run-resolver.js +64 -0
  38. package/src/cli/util/runtime-budget.js +147 -0
  39. package/src/cli/util/source-requirement.js +55 -0
  40. package/src/cli/util/summary-writer.js +13 -1
  41. package/src/cli/util/svelte-navigation-detector.js +163 -0
  42. package/src/cli/util/svelte-network-detector.js +80 -0
  43. package/src/cli/util/svelte-sfc-extractor.js +147 -0
  44. package/src/cli/util/svelte-state-detector.js +243 -0
  45. package/src/cli/util/vue-navigation-detector.js +177 -0
  46. package/src/cli/util/vue-sfc-extractor.js +162 -0
  47. package/src/cli/util/vue-state-detector.js +215 -0
  48. package/src/types/global.d.ts +28 -0
  49. package/src/types/ts-ast.d.ts +24 -0
  50. package/src/verax/cli/doctor.js +2 -2
  51. package/src/verax/cli/finding-explainer.js +56 -3
  52. package/src/verax/cli/init.js +1 -1
  53. package/src/verax/cli/url-safety.js +12 -2
  54. package/src/verax/cli/wizard.js +13 -2
  55. package/src/verax/core/artifacts/registry.js +154 -0
  56. package/src/verax/core/artifacts/verifier.js +980 -0
  57. package/src/verax/core/baseline/baseline.enforcer.js +137 -0
  58. package/src/verax/core/baseline/baseline.snapshot.js +231 -0
  59. package/src/verax/core/budget-engine.js +1 -1
  60. package/src/verax/core/capabilities/gates.js +499 -0
  61. package/src/verax/core/capabilities/registry.js +475 -0
  62. package/src/verax/core/confidence/confidence-compute.js +137 -0
  63. package/src/verax/core/confidence/confidence-invariants.js +234 -0
  64. package/src/verax/core/confidence/confidence-report-writer.js +112 -0
  65. package/src/verax/core/confidence/confidence-weights.js +44 -0
  66. package/src/verax/core/confidence/confidence.defaults.js +65 -0
  67. package/src/verax/core/confidence/confidence.loader.js +79 -0
  68. package/src/verax/core/confidence/confidence.schema.js +94 -0
  69. package/src/verax/core/confidence-engine-refactor.js +484 -0
  70. package/src/verax/core/confidence-engine.js +486 -0
  71. package/src/verax/core/confidence-engine.js.backup +471 -0
  72. package/src/verax/core/contracts/index.js +29 -0
  73. package/src/verax/core/contracts/types.js +185 -0
  74. package/src/verax/core/contracts/validators.js +381 -0
  75. package/src/verax/core/decision-snapshot.js +31 -4
  76. package/src/verax/core/decisions/decision.trace.js +276 -0
  77. package/src/verax/core/determinism/contract-writer.js +89 -0
  78. package/src/verax/core/determinism/contract.js +139 -0
  79. package/src/verax/core/determinism/diff.js +364 -0
  80. package/src/verax/core/determinism/engine.js +221 -0
  81. package/src/verax/core/determinism/finding-identity.js +148 -0
  82. package/src/verax/core/determinism/normalize.js +438 -0
  83. package/src/verax/core/determinism/report-writer.js +92 -0
  84. package/src/verax/core/determinism/run-fingerprint.js +118 -0
  85. package/src/verax/core/determinism-model.js +35 -6
  86. package/src/verax/core/dynamic-route-intelligence.js +528 -0
  87. package/src/verax/core/evidence/evidence-capture-service.js +307 -0
  88. package/src/verax/core/evidence/evidence-intent-ledger.js +165 -0
  89. package/src/verax/core/evidence-builder.js +487 -0
  90. package/src/verax/core/execution-mode-context.js +77 -0
  91. package/src/verax/core/execution-mode-detector.js +190 -0
  92. package/src/verax/core/failures/exit-codes.js +86 -0
  93. package/src/verax/core/failures/failure-summary.js +76 -0
  94. package/src/verax/core/failures/failure.factory.js +225 -0
  95. package/src/verax/core/failures/failure.ledger.js +132 -0
  96. package/src/verax/core/failures/failure.types.js +196 -0
  97. package/src/verax/core/failures/index.js +10 -0
  98. package/src/verax/core/ga/ga-report-writer.js +43 -0
  99. package/src/verax/core/ga/ga.artifact.js +49 -0
  100. package/src/verax/core/ga/ga.contract.js +434 -0
  101. package/src/verax/core/ga/ga.enforcer.js +86 -0
  102. package/src/verax/core/guardrails/guardrails-report-writer.js +109 -0
  103. package/src/verax/core/guardrails/policy.defaults.js +210 -0
  104. package/src/verax/core/guardrails/policy.loader.js +83 -0
  105. package/src/verax/core/guardrails/policy.schema.js +110 -0
  106. package/src/verax/core/guardrails/truth-reconciliation.js +136 -0
  107. package/src/verax/core/guardrails-engine.js +505 -0
  108. package/src/verax/core/incremental-store.js +15 -7
  109. package/src/verax/core/observe/run-timeline.js +316 -0
  110. package/src/verax/core/perf/perf.contract.js +186 -0
  111. package/src/verax/core/perf/perf.display.js +65 -0
  112. package/src/verax/core/perf/perf.enforcer.js +91 -0
  113. package/src/verax/core/perf/perf.monitor.js +209 -0
  114. package/src/verax/core/perf/perf.report.js +198 -0
  115. package/src/verax/core/pipeline-tracker.js +238 -0
  116. package/src/verax/core/product-definition.js +127 -0
  117. package/src/verax/core/release/provenance.builder.js +271 -0
  118. package/src/verax/core/release/release-report-writer.js +40 -0
  119. package/src/verax/core/release/release.enforcer.js +159 -0
  120. package/src/verax/core/release/reproducibility.check.js +221 -0
  121. package/src/verax/core/release/sbom.builder.js +283 -0
  122. package/src/verax/core/replay-validator.js +4 -4
  123. package/src/verax/core/replay.js +1 -1
  124. package/src/verax/core/report/cross-index.js +192 -0
  125. package/src/verax/core/report/human-summary.js +222 -0
  126. package/src/verax/core/route-intelligence.js +419 -0
  127. package/src/verax/core/security/secrets.scan.js +326 -0
  128. package/src/verax/core/security/security-report.js +50 -0
  129. package/src/verax/core/security/security.enforcer.js +124 -0
  130. package/src/verax/core/security/supplychain.defaults.json +38 -0
  131. package/src/verax/core/security/supplychain.policy.js +326 -0
  132. package/src/verax/core/security/vuln.scan.js +265 -0
  133. package/src/verax/core/silence-impact.js +1 -1
  134. package/src/verax/core/silence-model.js +9 -7
  135. package/src/verax/core/truth/truth.certificate.js +250 -0
  136. package/src/verax/core/ui-feedback-intelligence.js +515 -0
  137. package/src/verax/detect/comparison.js +8 -3
  138. package/src/verax/detect/confidence-engine.js +645 -57
  139. package/src/verax/detect/confidence-helper.js +33 -0
  140. package/src/verax/detect/detection-engine.js +19 -2
  141. package/src/verax/detect/dynamic-route-findings.js +335 -0
  142. package/src/verax/detect/evidence-index.js +15 -65
  143. package/src/verax/detect/expectation-chain-detector.js +417 -0
  144. package/src/verax/detect/expectation-model.js +56 -3
  145. package/src/verax/detect/explanation-helpers.js +1 -1
  146. package/src/verax/detect/finding-detector.js +2 -2
  147. package/src/verax/detect/findings-writer.js +149 -20
  148. package/src/verax/detect/flow-detector.js +4 -4
  149. package/src/verax/detect/index.js +265 -15
  150. package/src/verax/detect/interactive-findings.js +3 -4
  151. package/src/verax/detect/journey-stall-detector.js +558 -0
  152. package/src/verax/detect/route-findings.js +218 -0
  153. package/src/verax/detect/signal-mapper.js +2 -2
  154. package/src/verax/detect/skip-classifier.js +4 -4
  155. package/src/verax/detect/ui-feedback-findings.js +207 -0
  156. package/src/verax/detect/verdict-engine.js +61 -9
  157. package/src/verax/detect/view-switch-correlator.js +242 -0
  158. package/src/verax/flow/flow-engine.js +3 -2
  159. package/src/verax/flow/flow-spec.js +1 -2
  160. package/src/verax/index.js +413 -33
  161. package/src/verax/intel/effect-detector.js +1 -1
  162. package/src/verax/intel/index.js +2 -2
  163. package/src/verax/intel/route-extractor.js +3 -3
  164. package/src/verax/intel/vue-navigation-extractor.js +81 -18
  165. package/src/verax/intel/vue-router-extractor.js +4 -2
  166. package/src/verax/learn/action-contract-extractor.js +684 -66
  167. package/src/verax/learn/ast-contract-extractor.js +53 -1
  168. package/src/verax/learn/index.js +36 -2
  169. package/src/verax/learn/manifest-writer.js +28 -14
  170. package/src/verax/learn/route-extractor.js +1 -1
  171. package/src/verax/learn/route-validator.js +12 -8
  172. package/src/verax/learn/state-extractor.js +1 -1
  173. package/src/verax/learn/static-extractor-navigation.js +1 -1
  174. package/src/verax/learn/static-extractor-validation.js +2 -2
  175. package/src/verax/learn/static-extractor.js +8 -7
  176. package/src/verax/learn/ts-contract-resolver.js +14 -12
  177. package/src/verax/observe/browser.js +22 -3
  178. package/src/verax/observe/console-sensor.js +2 -2
  179. package/src/verax/observe/expectation-executor.js +2 -1
  180. package/src/verax/observe/focus-sensor.js +1 -1
  181. package/src/verax/observe/human-driver.js +29 -10
  182. package/src/verax/observe/index.js +92 -844
  183. package/src/verax/observe/interaction-discovery.js +27 -15
  184. package/src/verax/observe/interaction-runner.js +31 -14
  185. package/src/verax/observe/loading-sensor.js +6 -0
  186. package/src/verax/observe/navigation-sensor.js +1 -1
  187. package/src/verax/observe/observe-context.js +205 -0
  188. package/src/verax/observe/observe-helpers.js +191 -0
  189. package/src/verax/observe/observe-runner.js +226 -0
  190. package/src/verax/observe/observers/budget-observer.js +185 -0
  191. package/src/verax/observe/observers/console-observer.js +102 -0
  192. package/src/verax/observe/observers/coverage-observer.js +107 -0
  193. package/src/verax/observe/observers/interaction-observer.js +471 -0
  194. package/src/verax/observe/observers/navigation-observer.js +132 -0
  195. package/src/verax/observe/observers/network-observer.js +87 -0
  196. package/src/verax/observe/observers/safety-observer.js +82 -0
  197. package/src/verax/observe/observers/ui-feedback-observer.js +99 -0
  198. package/src/verax/observe/settle.js +1 -0
  199. package/src/verax/observe/state-sensor.js +8 -4
  200. package/src/verax/observe/state-ui-sensor.js +7 -1
  201. package/src/verax/observe/traces-writer.js +27 -16
  202. package/src/verax/observe/ui-feedback-detector.js +742 -0
  203. package/src/verax/observe/ui-signal-sensor.js +155 -2
  204. package/src/verax/scan-summary-writer.js +46 -9
  205. package/src/verax/shared/artifact-manager.js +9 -6
  206. package/src/verax/shared/budget-profiles.js +2 -2
  207. package/src/verax/shared/caching.js +1 -1
  208. package/src/verax/shared/config-loader.js +1 -2
  209. package/src/verax/shared/css-spinner-rules.js +204 -0
  210. package/src/verax/shared/dynamic-route-utils.js +12 -6
  211. package/src/verax/shared/retry-policy.js +1 -6
  212. package/src/verax/shared/root-artifacts.js +1 -1
  213. package/src/verax/shared/view-switch-rules.js +208 -0
  214. package/src/verax/shared/zip-artifacts.js +1 -0
  215. package/src/verax/validate/context-validator.js +1 -1
  216. package/src/verax/observe/index.js.backup +0 -1
  217. package/src/verax/validate/context-validator.js.bak +0 -0
@@ -0,0 +1,326 @@
1
+ /**
2
+ * PHASE 21.8 — Supply-Chain Policy
3
+ *
4
+ * Enforces supply-chain security policies:
5
+ * - License allowlist/denylist
6
+ * - Integrity hash requirements
7
+ * - Postinstall script restrictions
8
+ */
9
+
10
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
11
+ import { resolve } from 'path';
12
+ import DEFAULT_POLICY from './supplychain.defaults.json' with { type: 'json' };
13
+
14
+ /**
15
+ * Load supply-chain policy
16
+ *
17
+ * @param {string} projectDir - Project directory
18
+ * @returns {Object} Policy object
19
+ */
20
+ export function loadSupplyChainPolicy(projectDir) {
21
+ const customPath = resolve(projectDir, 'supplychain.policy.json');
22
+
23
+ if (existsSync(customPath)) {
24
+ try {
25
+ const custom = JSON.parse(readFileSync(customPath, 'utf-8'));
26
+ // Merge with defaults (custom overrides)
27
+ return {
28
+ ...DEFAULT_POLICY,
29
+ ...custom,
30
+ licensePolicy: {
31
+ ...DEFAULT_POLICY.licensePolicy,
32
+ ...(custom.licensePolicy || {})
33
+ },
34
+ integrityPolicy: {
35
+ ...DEFAULT_POLICY.integrityPolicy,
36
+ ...(custom.integrityPolicy || {})
37
+ },
38
+ scriptPolicy: {
39
+ ...DEFAULT_POLICY.scriptPolicy,
40
+ ...(custom.scriptPolicy || {})
41
+ },
42
+ sourcePolicy: {
43
+ ...DEFAULT_POLICY.sourcePolicy,
44
+ ...(custom.sourcePolicy || {})
45
+ }
46
+ };
47
+ } catch {
48
+ // Invalid custom policy, use defaults
49
+ }
50
+ }
51
+
52
+ return DEFAULT_POLICY;
53
+ }
54
+
55
+ /**
56
+ * Get package.json dependencies
57
+ *
58
+ * @param {string} projectDir - Project directory
59
+ * @returns {Object} Dependencies
60
+ */
61
+ function getDependencies(projectDir) {
62
+ try {
63
+ const pkgPath = resolve(projectDir, 'package.json');
64
+ if (!existsSync(pkgPath)) {
65
+ return { dependencies: {}, devDependencies: {} };
66
+ }
67
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
68
+ return {
69
+ dependencies: pkg.dependencies || {},
70
+ devDependencies: pkg.devDependencies || {}
71
+ };
72
+ } catch {
73
+ return { dependencies: {}, devDependencies: {} };
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get package license from node_modules
79
+ *
80
+ * @param {string} projectDir - Project directory
81
+ * @param {string} packageName - Package name
82
+ * @returns {string|null} License or null
83
+ */
84
+ function getPackageLicense(projectDir, packageName) {
85
+ try {
86
+ const pkgPath = resolve(projectDir, 'node_modules', packageName, 'package.json');
87
+ if (!existsSync(pkgPath)) {
88
+ return null;
89
+ }
90
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
91
+
92
+ if (typeof pkg.license === 'string') {
93
+ return pkg.license;
94
+ } else if (pkg.license && pkg.license.type) {
95
+ return pkg.license.type;
96
+ } else if (Array.isArray(pkg.licenses) && pkg.licenses.length > 0) {
97
+ return pkg.licenses[0].type || pkg.licenses[0];
98
+ }
99
+
100
+ return null;
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Check package-lock.json for integrity hashes
108
+ *
109
+ * @param {string} projectDir - Project directory
110
+ * @returns {Object} Integrity check results
111
+ */
112
+ function checkIntegrityHashes(projectDir) {
113
+ const lockPath = resolve(projectDir, 'package-lock.json');
114
+ const missing = [];
115
+
116
+ if (!existsSync(lockPath)) {
117
+ return { missing: [], total: 0 };
118
+ }
119
+
120
+ try {
121
+ const lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
122
+ const packages = lock.packages || {};
123
+ let total = 0;
124
+
125
+ for (const [path, pkg] of Object.entries(packages)) {
126
+ if (path && pkg && pkg.version) {
127
+ total++;
128
+ if (!pkg.integrity && !pkg.resolved?.includes('file:')) {
129
+ missing.push({
130
+ package: pkg.name || path,
131
+ path: path,
132
+ version: pkg.version
133
+ });
134
+ }
135
+ }
136
+ }
137
+
138
+ return { missing, total };
139
+ } catch {
140
+ return { missing: [], total: 0 };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Check for postinstall scripts
146
+ *
147
+ * @param {string} projectDir - Project directory
148
+ * @returns {Array} Packages with postinstall scripts
149
+ */
150
+ function checkPostinstallScripts(projectDir) {
151
+ const packages = [];
152
+ const nodeModulesPath = resolve(projectDir, 'node_modules');
153
+
154
+ if (!existsSync(nodeModulesPath)) {
155
+ return packages;
156
+ }
157
+
158
+ try {
159
+ const { readdirSync } = require('fs');
160
+ const entries = readdirSync(nodeModulesPath, { withFileTypes: true });
161
+
162
+ for (const entry of entries) {
163
+ if (entry.isDirectory() && !entry.name.startsWith('.')) {
164
+ const pkgPath = resolve(nodeModulesPath, entry.name, 'package.json');
165
+ if (existsSync(pkgPath)) {
166
+ try {
167
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
168
+ if (pkg.scripts) {
169
+ if (pkg.scripts.postinstall || pkg.scripts.preinstall || pkg.scripts.install) {
170
+ packages.push({
171
+ name: pkg.name || entry.name,
172
+ version: pkg.version || 'unknown',
173
+ scripts: Object.keys(pkg.scripts).filter(s =>
174
+ ['postinstall', 'preinstall', 'install'].includes(s)
175
+ )
176
+ });
177
+ }
178
+ }
179
+ } catch {
180
+ // Skip invalid packages
181
+ }
182
+ }
183
+ }
184
+ }
185
+ } catch {
186
+ // If scanning fails, return empty
187
+ }
188
+
189
+ return packages;
190
+ }
191
+
192
+ /**
193
+ * Evaluate supply-chain policy
194
+ *
195
+ * @param {string} projectDir - Project directory
196
+ * @returns {Promise<Object>} Evaluation results
197
+ */
198
+ export async function evaluateSupplyChainPolicy(projectDir) {
199
+ const policy = loadSupplyChainPolicy(projectDir);
200
+ const deps = getDependencies(projectDir);
201
+ const violations = [];
202
+ const warnings = [];
203
+
204
+ // Check licenses
205
+ const allDeps = { ...deps.dependencies, ...deps.devDependencies };
206
+ for (const [packageName] of Object.entries(allDeps)) {
207
+ const license = getPackageLicense(projectDir, packageName);
208
+
209
+ if (license) {
210
+ // Check denylist
211
+ if (policy.licensePolicy.denylist.includes(license)) {
212
+ violations.push({
213
+ type: 'FORBIDDEN_LICENSE',
214
+ package: packageName,
215
+ license: license,
216
+ severity: 'BLOCKING',
217
+ message: `Package ${packageName} uses forbidden license: ${license}`
218
+ });
219
+ }
220
+
221
+ // Check allowlist (if strict mode)
222
+ if (policy.licensePolicy.strictMode && !policy.licensePolicy.allowlist.includes(license)) {
223
+ violations.push({
224
+ type: 'UNALLOWED_LICENSE',
225
+ package: packageName,
226
+ license: license,
227
+ severity: 'BLOCKING',
228
+ message: `Package ${packageName} uses unallowed license: ${license}`
229
+ });
230
+ }
231
+ } else {
232
+ warnings.push({
233
+ type: 'MISSING_LICENSE',
234
+ package: packageName,
235
+ message: `Package ${packageName} has no license information`
236
+ });
237
+ }
238
+ }
239
+
240
+ // Check integrity hashes
241
+ if (policy.integrityPolicy.requireIntegrityHash) {
242
+ const integrityCheck = checkIntegrityHashes(projectDir);
243
+ for (const missing of integrityCheck.missing) {
244
+ if (!policy.integrityPolicy.allowedMissingIntegrity.includes(missing.package)) {
245
+ violations.push({
246
+ type: 'MISSING_INTEGRITY',
247
+ package: missing.package,
248
+ version: missing.version,
249
+ severity: 'BLOCKING',
250
+ message: `Package ${missing.package}@${missing.version} missing integrity hash`
251
+ });
252
+ }
253
+ }
254
+ }
255
+
256
+ // Check postinstall scripts
257
+ if (policy.scriptPolicy.forbidPostinstall ||
258
+ policy.scriptPolicy.forbidPreinstall ||
259
+ policy.scriptPolicy.forbidInstall) {
260
+ const scripts = checkPostinstallScripts(projectDir);
261
+ for (const pkg of scripts) {
262
+ const forbiddenScripts = pkg.scripts.filter(s => {
263
+ if (s === 'postinstall' && policy.scriptPolicy.forbidPostinstall) return true;
264
+ if (s === 'preinstall' && policy.scriptPolicy.forbidPreinstall) return true;
265
+ if (s === 'install' && policy.scriptPolicy.forbidInstall) return true;
266
+ return false;
267
+ });
268
+
269
+ if (forbiddenScripts.length > 0 && !policy.scriptPolicy.allowlist.includes(pkg.name)) {
270
+ violations.push({
271
+ type: 'FORBIDDEN_SCRIPT',
272
+ package: pkg.name,
273
+ version: pkg.version,
274
+ scripts: forbiddenScripts,
275
+ severity: 'BLOCKING',
276
+ message: `Package ${pkg.name} has forbidden scripts: ${forbiddenScripts.join(', ')}`
277
+ });
278
+ }
279
+ }
280
+ }
281
+
282
+ const ok = violations.length === 0;
283
+
284
+ return {
285
+ ok,
286
+ violations,
287
+ warnings,
288
+ summary: {
289
+ totalViolations: violations.length,
290
+ totalWarnings: warnings.length,
291
+ byType: violations.reduce((acc, v) => {
292
+ acc[v.type] = (acc[v.type] || 0) + 1;
293
+ return acc;
294
+ }, {}),
295
+ evaluatedAt: new Date().toISOString()
296
+ },
297
+ policy: {
298
+ version: policy.version,
299
+ licensePolicy: {
300
+ allowlist: policy.licensePolicy.allowlist,
301
+ denylist: policy.licensePolicy.denylist,
302
+ strictMode: policy.licensePolicy.strictMode
303
+ }
304
+ }
305
+ };
306
+ }
307
+
308
+ /**
309
+ * Write supply-chain report
310
+ *
311
+ * @param {string} projectDir - Project directory
312
+ * @param {Object} report - Evaluation results
313
+ * @returns {string} Path to written file
314
+ */
315
+ export function writeSupplyChainReport(projectDir, report) {
316
+ const outputDir = resolve(projectDir, 'release');
317
+ if (!existsSync(outputDir)) {
318
+ mkdirSync(outputDir, { recursive: true });
319
+ }
320
+
321
+ const outputPath = resolve(outputDir, 'security.supplychain.report.json');
322
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
323
+
324
+ return outputPath;
325
+ }
326
+
@@ -0,0 +1,265 @@
1
+ /**
2
+ * PHASE 21.8 — Vulnerability Scanner
3
+ *
4
+ * Scans dependencies for vulnerabilities using npm audit.
5
+ * HIGH/CRITICAL = BLOCKING, MEDIUM = WARNING (configurable).
6
+ */
7
+
8
+ import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
9
+ import { resolve } from 'path';
10
+ import { execSync } from 'child_process';
11
+
12
+ /**
13
+ * Check if OSV scanner is available
14
+ *
15
+ * @returns {boolean} Whether osv-scanner is available
16
+ */
17
+ function checkOSVAvailable() {
18
+ try {
19
+ execSync('osv-scanner --version', {
20
+ stdio: ['ignore', 'pipe', 'pipe'],
21
+ timeout: 5000
22
+ });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Run npm audit
31
+ *
32
+ * @param {string} projectDir - Project directory
33
+ * @returns {Object|null} Audit results or null
34
+ */
35
+ function runNpmAudit(projectDir) {
36
+ try {
37
+ const result = execSync('npm audit --json', {
38
+ cwd: projectDir,
39
+ encoding: 'utf-8',
40
+ stdio: ['ignore', 'pipe', 'pipe']
41
+ });
42
+
43
+ return JSON.parse(result);
44
+ } catch (error) {
45
+ // npm audit exits with non-zero on vulnerabilities
46
+ try {
47
+ const stderr = error.stderr?.toString() || '';
48
+ const stdout = error.stdout?.toString() || '';
49
+ const output = stdout || stderr;
50
+
51
+ if (output) {
52
+ return JSON.parse(output);
53
+ }
54
+ } catch {
55
+ // Failed to parse
56
+ }
57
+
58
+ return null;
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Parse vulnerabilities from audit results
64
+ *
65
+ * @param {Object} auditResults - npm audit JSON output
66
+ * @returns {Array} Array of vulnerabilities
67
+ */
68
+ function parseVulnerabilities(auditResults) {
69
+ const vulnerabilities = [];
70
+
71
+ if (!auditResults || !auditResults.vulnerabilities) {
72
+ return vulnerabilities;
73
+ }
74
+
75
+ for (const [packageName, vulnData] of Object.entries(auditResults.vulnerabilities)) {
76
+ if (Array.isArray(vulnData)) {
77
+ for (const vuln of vulnData) {
78
+ vulnerabilities.push({
79
+ package: packageName,
80
+ severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
81
+ title: vuln.title || vuln.name || 'Unknown vulnerability',
82
+ url: vuln.url || null,
83
+ dependencyOf: vuln.dependencyOf || null,
84
+ via: vuln.via || null
85
+ });
86
+ }
87
+ } else if (vulnData.vulnerabilities) {
88
+ for (const vuln of vulnData.vulnerabilities) {
89
+ vulnerabilities.push({
90
+ package: packageName,
91
+ severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
92
+ title: vuln.title || vuln.name || 'Unknown vulnerability',
93
+ url: vuln.url || null,
94
+ dependencyOf: vulnData.dependencyOf || null,
95
+ via: vuln.via || null
96
+ });
97
+ }
98
+ }
99
+ }
100
+
101
+ return vulnerabilities;
102
+ }
103
+
104
+ /**
105
+ * Scan for vulnerabilities
106
+ *
107
+ * @param {string} projectDir - Project directory
108
+ * @param {Object} options - Options
109
+ * @param {boolean} options.blockMedium - Block MEDIUM severity (default: false)
110
+ * @param {boolean} options.requireOSV - Require OSV scanner (default: false)
111
+ * @returns {Promise<Object>} Scan results
112
+ */
113
+ export async function scanVulnerabilities(projectDir, options = {}) {
114
+ const { blockMedium = false, requireOSV = false } = options;
115
+
116
+ // Check OSV availability
117
+ const osvAvailable = checkOSVAvailable();
118
+ let osvResults = null;
119
+
120
+ if (osvAvailable) {
121
+ try {
122
+ // Run OSV scanner
123
+ const osvOutput = execSync('osv-scanner --format=json .', {
124
+ cwd: projectDir,
125
+ encoding: 'utf-8',
126
+ stdio: ['ignore', 'pipe', 'pipe'],
127
+ timeout: 60000
128
+ });
129
+ try {
130
+ osvResults = JSON.parse(osvOutput);
131
+ } catch {
132
+ // OSV scanner may output non-JSON on errors
133
+ }
134
+ } catch {
135
+ // OSV scanner failed, fall back to npm audit
136
+ }
137
+ }
138
+
139
+ // Always try npm audit as fallback
140
+ const auditResults = runNpmAudit(projectDir);
141
+
142
+ if (!auditResults && !osvResults) {
143
+ if (requireOSV && !osvAvailable) {
144
+ return {
145
+ ok: false,
146
+ error: 'OSV scanner not available',
147
+ availability: 'NOT_AVAILABLE',
148
+ tool: null,
149
+ vulnerabilities: [],
150
+ summary: {
151
+ total: 0,
152
+ critical: 0,
153
+ high: 0,
154
+ medium: 0,
155
+ low: 0,
156
+ scannedAt: new Date().toISOString()
157
+ }
158
+ };
159
+ }
160
+
161
+ return {
162
+ ok: false,
163
+ error: 'Failed to run npm audit',
164
+ availability: 'FAILED',
165
+ tool: 'NPM_AUDIT',
166
+ vulnerabilities: [],
167
+ summary: {
168
+ total: 0,
169
+ critical: 0,
170
+ high: 0,
171
+ medium: 0,
172
+ low: 0,
173
+ scannedAt: new Date().toISOString()
174
+ }
175
+ };
176
+ }
177
+
178
+ // Parse vulnerabilities from npm audit (primary source)
179
+ let vulnerabilities = [];
180
+ if (auditResults) {
181
+ vulnerabilities = parseVulnerabilities(auditResults);
182
+ }
183
+
184
+ // Merge OSV results if available
185
+ if (osvResults && osvResults.results) {
186
+ for (const result of osvResults.results) {
187
+ if (result.packages && Array.isArray(result.packages)) {
188
+ for (const pkg of result.packages) {
189
+ if (result.vulnerabilities && Array.isArray(result.vulnerabilities)) {
190
+ for (const vuln of result.vulnerabilities) {
191
+ // Avoid duplicates (merge by package + ID)
192
+ const existing = vulnerabilities.find(v =>
193
+ v.package === pkg.package?.name &&
194
+ v.osvId === vuln.id
195
+ );
196
+ if (!existing) {
197
+ vulnerabilities.push({
198
+ package: pkg.package?.name || 'unknown',
199
+ severity: vuln.severity?.toUpperCase() || 'UNKNOWN',
200
+ title: vuln.summary || vuln.id || 'Unknown vulnerability',
201
+ url: vuln.database_specific?.url || vuln.id ? `https://osv.dev/vulnerability/${vuln.id}` : null,
202
+ osvId: vuln.id,
203
+ source: 'OSV'
204
+ });
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ const critical = vulnerabilities.filter(v => v.severity === 'CRITICAL');
214
+ const high = vulnerabilities.filter(v => v.severity === 'HIGH');
215
+ const medium = vulnerabilities.filter(v => v.severity === 'MEDIUM');
216
+ const low = vulnerabilities.filter(v => v.severity === 'LOW');
217
+
218
+ // BLOCKING: CRITICAL or HIGH
219
+ // WARNING: MEDIUM (blocking if blockMedium=true)
220
+ const blocking = critical.length > 0 || high.length > 0 || (blockMedium && medium.length > 0);
221
+
222
+ return {
223
+ ok: !blocking,
224
+ blocking,
225
+ availability: osvAvailable ? 'AVAILABLE' : 'NOT_AVAILABLE',
226
+ tool: osvAvailable ? 'OSV_SCANNER' : (auditResults ? 'NPM_AUDIT' : null),
227
+ osvAvailable,
228
+ vulnerabilities,
229
+ summary: {
230
+ total: vulnerabilities.length,
231
+ critical: critical.length,
232
+ high: high.length,
233
+ medium: medium.length,
234
+ low: low.length,
235
+ blocking,
236
+ warnings: blockMedium ? 0 : medium.length,
237
+ scannedAt: new Date().toISOString()
238
+ },
239
+ metadata: {
240
+ auditVersion: auditResults?.auditReportVersion || null,
241
+ npmVersion: auditResults?.npmVersion || null,
242
+ osvVersion: osvAvailable ? 'detected' : null
243
+ }
244
+ };
245
+ }
246
+
247
+ /**
248
+ * Write vulnerability report
249
+ *
250
+ * @param {string} projectDir - Project directory
251
+ * @param {Object} report - Scan results
252
+ * @returns {string} Path to written file
253
+ */
254
+ export function writeVulnReport(projectDir, report) {
255
+ const outputDir = resolve(projectDir, 'release');
256
+ if (!existsSync(outputDir)) {
257
+ mkdirSync(outputDir, { recursive: true });
258
+ }
259
+
260
+ const outputPath = resolve(outputDir, 'security.vuln.report.json');
261
+ writeFileSync(outputPath, JSON.stringify(report, null, 2), 'utf-8');
262
+
263
+ return outputPath;
264
+ }
265
+
@@ -335,7 +335,7 @@ export function createImpactSummary(silences) {
335
335
  * @returns {string} Human-readable interpretation
336
336
  */
337
337
  function generateConfidenceInterpretation(aggregated) {
338
- const { coverage, promise_verification, overall } = aggregated;
338
+ const { coverage: _coverage, promise_verification: _promise_verification, overall } = aggregated;
339
339
 
340
340
  if (overall === 0) {
341
341
  return 'No silence events - observation confidence is complete within evaluated scope';
@@ -18,8 +18,10 @@ import { mapSilenceReasonToOutcome } from './canonical-outcomes.js';
18
18
  *
19
19
  * PHASE 4: Enhanced with lifecycle model
20
20
  *
21
+ * Note: Fields marked as optional are auto-inferred by record() if not provided
22
+ *
21
23
  * @typedef {Object} SilenceEntry
22
- * @property {string} outcome - Canonical outcome: SILENT_FAILURE | COVERAGE_GAP | UNPROVEN_INTERACTION | SAFETY_BLOCK | INFORMATIONAL
24
+ * @property {string} [outcome] - Canonical outcome: SILENT_FAILURE | COVERAGE_GAP | UNPROVEN_INTERACTION | SAFETY_BLOCK | INFORMATIONAL (auto-inferred if missing)
23
25
  * @property {string} scope - What was not evaluated: page | interaction | expectation | sensor | navigation | settle
24
26
  * @property {string} reason - Why it wasn't evaluated: timeout | cap | budget_exceeded | incremental_reuse | safety_skip | no_expectation | discovery_failed | sensor_failed
25
27
  * @property {string} description - Human-readable description of what wasn't observed
@@ -28,12 +30,12 @@ import { mapSilenceReasonToOutcome } from './canonical-outcomes.js';
28
30
  * @property {number} [count] - Number of items affected by this silence (optional)
29
31
  * @property {string} [evidenceUrl] - URL where this silence occurred (optional)
30
32
  *
31
- * PHASE 4 Lifecycle Fields:
32
- * @property {string} silence_type - Technical classification: interaction_not_executed | promise_not_evaluated | sensor_failure | timeout | budget_limit | safety_block | discovery_failure
33
+ * PHASE 4 Lifecycle Fields (all auto-inferred if missing):
34
+ * @property {string} [silence_type] - Technical classification: interaction_not_executed | promise_not_evaluated | sensor_failure | timeout | budget_limit | safety_block | discovery_failure (auto-inferred)
33
35
  * @property {Object} [related_promise] - Promise that could not be evaluated (if applicable) or null with reason
34
- * @property {string} trigger - What triggered this silence: user_action_blocked | navigation_timeout | budget_exhausted | safety_policy | selector_not_found | etc
35
- * @property {string} evaluation_status - blocked | ambiguous | skipped | timed_out | incomplete
36
- * @property {Object} confidence_impact - Which confidence metrics are affected: { coverage: -X%, promise_verification: -Y%, overall: -Z% }
36
+ * @property {string} [trigger] - What triggered this silence: user_action_blocked | navigation_timeout | budget_exhausted | safety_policy | selector_not_found | etc (auto-inferred)
37
+ * @property {string} [evaluation_status] - blocked | ambiguous | skipped | timed_out | incomplete (auto-inferred)
38
+ * @property {Object} [confidence_impact] - Which confidence metrics are affected: { coverage: -X%, promise_verification: -Y%, overall: -Z% } (auto-inferred)
37
39
  */
38
40
 
39
41
  /**
@@ -272,7 +274,7 @@ export class SilenceTracker {
272
274
  /**
273
275
  * PHASE 4: Infer confidence impact from reason and scope
274
276
  */
275
- _inferConfidenceImpact(reason, scope) {
277
+ _inferConfidenceImpact(reason, _scope) {
276
278
  // Map reason to which confidence metric(s) are affected
277
279
  const impact = {
278
280
  coverage: 0,