@clear-capabilities/agentic-security-scanner 0.184.0 → 0.186.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 (228) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/agentic-security.js +111 -1
  3. package/dist/1128.index.js +912 -0
  4. package/dist/1665.index.js +828 -0
  5. package/dist/2584.index.js +1 -1
  6. package/dist/4171.index.js +2 -2
  7. package/dist/5752.index.js +466 -0
  8. package/dist/7405.index.js +1 -1
  9. package/dist/8158.index.js +1713 -0
  10. package/dist/8798.index.js +1 -1
  11. package/dist/9382.index.js +2 -2
  12. package/dist/9579.index.js +1 -1
  13. package/dist/agentic-security.mjs +29 -29
  14. package/dist/agentic-security.mjs.sha256 +1 -1
  15. package/dist/{dad58d1113a12c23a142.json → ca62ee93079be4c24eee.json} +29 -12
  16. package/dist/frontend/src/shell.js +12 -2
  17. package/package.json +29 -12
  18. package/src/dataflow/CLAUDE.md +46 -1
  19. package/src/dataflow/access-paths.js +12 -2
  20. package/src/dataflow/backward.js +42 -7
  21. package/src/dataflow/cross-service-taint.js +83 -1
  22. package/src/dataflow/demand-query.js +1168 -0
  23. package/src/dataflow/dep-fingerprint.js +718 -0
  24. package/src/dataflow/engine.js +48 -6
  25. package/src/dataflow/exploit-prover.js +6 -2
  26. package/src/dataflow/ifds-precise.js +22 -0
  27. package/src/dataflow/incremental-replay.js +118 -0
  28. package/src/dataflow/incremental-session.js +257 -0
  29. package/src/dataflow/incremental.js +237 -35
  30. package/src/dataflow/index.js +25 -94
  31. package/src/dataflow/ir-cache.js +268 -0
  32. package/src/dataflow/privacy-taint.js +7 -0
  33. package/src/dataflow/summaries.js +72 -6
  34. package/src/dataflow/tabulation.js +80 -24
  35. package/src/engine.js +113 -8
  36. package/src/graph/CLAUDE.md +31 -0
  37. package/src/graph/adapters/artifact.js +81 -0
  38. package/src/graph/adapters/crossrepo.js +75 -0
  39. package/src/graph/adapters/dataflow.js +76 -0
  40. package/src/graph/adapters/provenance.js +92 -0
  41. package/src/graph/adapters/semantic.js +89 -0
  42. package/src/graph/crosswalk-builders.js +152 -0
  43. package/src/graph/crosswalk.js +146 -0
  44. package/src/graph/index.js +15 -0
  45. package/src/graph/observation.js +57 -0
  46. package/src/graph/refs.js +105 -0
  47. package/src/graph/registry.js +129 -0
  48. package/src/graph/relations.js +81 -0
  49. package/src/graph/scc.js +241 -0
  50. package/src/graph/view.js +254 -0
  51. package/src/ir/CLAUDE.md +4 -0
  52. package/src/ir/callgraph.js +36 -8
  53. package/src/ir/index.js +18 -4
  54. package/src/ir/parser-js.js +10 -0
  55. package/src/language/CLAUDE.md +13 -0
  56. package/src/language/nix-option-types.js +170 -0
  57. package/src/language/nix-script-taint.js +46 -3
  58. package/src/language/nix-secrets.js +20 -2
  59. package/src/lineage/CLAUDE.md +72 -8
  60. package/src/lineage/DESIGN_GRAPH_BUILDER.md +1 -1
  61. package/src/lineage/DESIGN_PATH_PROVENANCE.md +6 -0
  62. package/src/lineage/boundary-contract.js +273 -0
  63. package/src/lineage/completion.js +205 -0
  64. package/src/lineage/continuation.js +140 -0
  65. package/src/lineage/control-credit.js +313 -0
  66. package/src/lineage/correlation.js +199 -0
  67. package/src/lineage/coverage.js +8 -1
  68. package/src/lineage/cross-repo-link.js +22 -0
  69. package/src/lineage/cut-planner.js +548 -0
  70. package/src/lineage/driver.js +219 -66
  71. package/src/lineage/engine.js +166 -21
  72. package/src/lineage/export-json.js +5 -0
  73. package/src/lineage/graph-builder.js +84 -7
  74. package/src/lineage/haskell-view.js +59 -0
  75. package/src/lineage/index.js +3 -0
  76. package/src/lineage/nix-view.js +35 -0
  77. package/src/lineage/owned-build.js +77 -0
  78. package/src/lineage/owned-worker-entry.js +22 -0
  79. package/src/lineage/owned-worker.js +95 -0
  80. package/src/lineage/path-evidence.js +123 -0
  81. package/src/lineage/path-predicates.js +529 -0
  82. package/src/lineage/path-query.js +189 -20
  83. package/src/lineage/path-store.js +191 -17
  84. package/src/lineage/scc-solver.js +186 -0
  85. package/src/lineage/smt-orchestrator.js +174 -0
  86. package/src/lineage/smt-z3-backend.js +294 -0
  87. package/src/lineage/source-seeding.js +36 -2
  88. package/src/lineage/summaries.js +66 -4
  89. package/src/lineage/value-versions.js +130 -0
  90. package/src/lineage/witness-selection.js +95 -0
  91. package/src/pipeline/scan-health.js +18 -0
  92. package/src/posture/CLAUDE.md +17 -0
  93. package/src/posture/business-logic.js +16 -7
  94. package/src/posture/dag-commands.js +442 -0
  95. package/src/posture/evidence-bundle.js +1 -1
  96. package/src/posture/finding-defaults.js +19 -0
  97. package/src/posture/git-history.js +91 -1
  98. package/src/posture/witness-bundle.js +573 -0
  99. package/src/posture/witness-runtime.js +276 -0
  100. package/src/report/index.js +2 -0
  101. package/src/sast/CLAUDE.md +2 -0
  102. package/src/sast/_call-args.js +70 -0
  103. package/src/sast/init-scope.js +87 -0
  104. package/src/sast/listen-bind-address.js +49 -0
  105. package/src/sast/redirect-target.js +106 -0
  106. package/src/sast/reflected-input.js +76 -0
  107. package/src/sast/send-file-root.js +27 -0
  108. package/src/sast/sensitive-data-logging.js +38 -1
  109. package/dist/119.index.js +0 -197
  110. package/dist/128.index.js +0 -129
  111. package/dist/1293.index.js +0 -5689
  112. package/dist/1336.index.js +0 -16182
  113. package/dist/1351.index.js +0 -133
  114. package/dist/1387.index.js +0 -271
  115. package/dist/1437.index.js +0 -23
  116. package/dist/1445.index.js +0 -110
  117. package/dist/1505.index.js +0 -151
  118. package/dist/1557.index.js +0 -293
  119. package/dist/1621.index.js +0 -311
  120. package/dist/1751.index.js +0 -160
  121. package/dist/1765.index.js +0 -727
  122. package/dist/1861.index.js +0 -690
  123. package/dist/1906.index.js +0 -433
  124. package/dist/208.index.js +0 -1631
  125. package/dist/2223.index.js +0 -159
  126. package/dist/2238.index.js +0 -295
  127. package/dist/2530.index.js +0 -152
  128. package/dist/2565.index.js +0 -116
  129. package/dist/2574.index.js +0 -127
  130. package/dist/2620.index.js +0 -345
  131. package/dist/2773.index.js +0 -98
  132. package/dist/2775.index.js +0 -32
  133. package/dist/2966.index.js +0 -171
  134. package/dist/2972.index.js +0 -87
  135. package/dist/2991.index.js +0 -131
  136. package/dist/3063.index.js +0 -833
  137. package/dist/3075.index.js +0 -227
  138. package/dist/3121.index.js +0 -297
  139. package/dist/3314.index.js +0 -263
  140. package/dist/3480.index.js +0 -1822
  141. package/dist/3481.index.js +0 -24
  142. package/dist/3506.index.js +0 -268
  143. package/dist/3533.index.js +0 -591
  144. package/dist/3653.index.js +0 -450
  145. package/dist/3740.index.js +0 -722
  146. package/dist/3822.index.js +0 -131
  147. package/dist/3868.index.js +0 -292
  148. package/dist/3918.index.js +0 -192
  149. package/dist/4090.index.js +0 -288
  150. package/dist/4113.index.js +0 -1286
  151. package/dist/4121.index.js +0 -1190
  152. package/dist/4142.index.js +0 -690
  153. package/dist/4168.index.js +0 -509
  154. package/dist/4281.index.js +0 -993
  155. package/dist/4336.index.js +0 -436
  156. package/dist/4387.index.js +0 -532
  157. package/dist/4413.index.js +0 -302
  158. package/dist/4476.index.js +0 -4510
  159. package/dist/4491.index.js +0 -133
  160. package/dist/4538.index.js +0 -185
  161. package/dist/4684.index.js +0 -319
  162. package/dist/4740.index.js +0 -165
  163. package/dist/4780.index.js +0 -209
  164. package/dist/4802.index.js +0 -225
  165. package/dist/4812.index.js +0 -379
  166. package/dist/4835.index.js +0 -126
  167. package/dist/494.index.js +0 -226
  168. package/dist/4991.index.js +0 -549
  169. package/dist/5199.index.js +0 -305
  170. package/dist/5216.index.js +0 -109
  171. package/dist/5439.index.js +0 -301
  172. package/dist/5443.index.js +0 -97
  173. package/dist/5496.index.js +0 -377
  174. package/dist/5738.index.js +0 -240
  175. package/dist/5801.index.js +0 -588
  176. package/dist/5817.index.js +0 -714
  177. package/dist/5858.index.js +0 -223
  178. package/dist/5933.index.js +0 -143
  179. package/dist/601.index.js +0 -23
  180. package/dist/6173.index.js +0 -273
  181. package/dist/6291.index.js +0 -1871
  182. package/dist/6312.index.js +0 -666
  183. package/dist/634.index.js +0 -16182
  184. package/dist/644.index.js +0 -87
  185. package/dist/6508.index.js +0 -283
  186. package/dist/6560.index.js +0 -645
  187. package/dist/6576.index.js +0 -422
  188. package/dist/6579.index.js +0 -271
  189. package/dist/6655.index.js +0 -3221
  190. package/dist/6745.index.js +0 -305
  191. package/dist/6818.index.js +0 -21642
  192. package/dist/6932.index.js +0 -1190
  193. package/dist/6972.index.js +0 -167
  194. package/dist/6995.index.js +0 -163
  195. package/dist/7249.index.js +0 -191
  196. package/dist/7372.index.js +0 -1197
  197. package/dist/7546.index.js +0 -541
  198. package/dist/76.index.js +0 -448
  199. package/dist/7879.index.js +0 -163
  200. package/dist/8025.index.js +0 -130
  201. package/dist/8054.index.js +0 -248
  202. package/dist/808.index.js +0 -1330
  203. package/dist/8097.index.js +0 -201
  204. package/dist/8126.index.js +0 -191
  205. package/dist/8178.index.js +0 -78
  206. package/dist/8382.index.js +0 -93
  207. package/dist/8408.index.js +0 -261
  208. package/dist/8540.index.js +0 -174
  209. package/dist/8635.index.js +0 -802
  210. package/dist/8688.index.js +0 -410
  211. package/dist/8699.index.js +0 -779
  212. package/dist/8891.index.js +0 -457
  213. package/dist/8916.index.js +0 -885
  214. package/dist/9146.index.js +0 -170
  215. package/dist/9168.index.js +0 -841
  216. package/dist/9292.index.js +0 -369
  217. package/dist/9422.index.js +0 -866
  218. package/dist/9440.index.js +0 -453
  219. package/dist/9479.index.js +0 -152
  220. package/dist/9481.index.js +0 -220
  221. package/dist/9539.index.js +0 -100
  222. package/dist/9547.index.js +0 -103
  223. package/dist/9692.index.js +0 -90
  224. package/dist/9800.index.js +0 -462
  225. package/dist/9899.index.js +0 -138
  226. package/dist/9981.index.js +0 -206
  227. package/dist/999.index.js +0 -117
  228. package/dist/9999.index.js +0 -1698
@@ -1,433 +0,0 @@
1
- export const id = 1906;
2
- export const ids = [1906];
3
- export const modules = {
4
-
5
- /***/ 61906:
6
- /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
-
8
- /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
- /* harmony export */ xU: () => (/* binding */ buildResolvedGraph)
10
- /* harmony export */ });
11
- /* unused harmony exports RESOLVED_SCHEMA_VERSION, RESOLVED_BUDGETS, linkModulesToPackages, analyzeResolvedHaskell */
12
- /* harmony import */ var node_fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(73024);
13
- /* harmony import */ var node_path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(76760);
14
- /* harmony import */ var _haskell_manifests_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(60377);
15
- // Resolved Haskell dependency graph (HS-008).
16
- //
17
- // Reads an EXPLICIT cabal install plan (dist-newstyle/cache/plan.json) or a Stack
18
- // resolved export (`stack ls dependencies json`) and joins it with the declared
19
- // manifest inventory from haskell-manifests.js. Static only: no solver, no
20
- // `cabal`/`stack` process, no network, no snapshot fetch.
21
- //
22
- // The rules this module exists to enforce:
23
- //
24
- // * a resolved graph is a CLAIM about one input state. It is checked against the
25
- // manifests, compiler, flags and targets it is meant to describe; a mismatch
26
- // marks it `stale` and DOWNGRADES it (no versions are copied into the declared
27
- // inventory, no complete closure is claimed);
28
- // * a freeze file or a stack.yaml.lock is a set of pins, not a closure. Alone, it
29
- // never yields `closure.complete`;
30
- // * missing resolved data leaves `units`/`edges` as null (unknown), never [] (none),
31
- // and every declared dependency keeps `resolvedVersion: null` with a reason;
32
- // * a local package is labelled `local`, never `hackage`; compiler-shipped packages
33
- // are `compiler` with `boot:true`; units are keyed by unit id so two builds of one
34
- // package under different flags stay distinct.
35
-
36
-
37
-
38
-
39
- const RESOLVED_SCHEMA_VERSION = 1;
40
- const RESOLVED_BUDGETS = Object.freeze({ maxBytes: 32 * 1024 * 1024, maxUnits: 100000, maxEdges: 1000000 });
41
-
42
- const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
43
- const gap = (kind, message, extra = {}) => ({ kind, message, ...extra });
44
-
45
- // Packages that ship with GHC. Used ONLY as a labelled fallback when a Stack export
46
- // carries no location data; a cabal plan states `pre-existing` itself.
47
- const BOOT_FALLBACK = new Set(['base', 'ghc-prim', 'ghc-bignum', 'integer-gmp', 'template-haskell', 'ghc', 'rts', 'ghc-boot', 'ghc-boot-th', 'ghc-heap', 'ghci', 'array', 'bytestring', 'containers', 'deepseq', 'directory', 'exceptions', 'filepath', 'haskeline', 'hpc', 'mtl', 'parsec', 'pretty', 'process', 'stm', 'text', 'time', 'transformers', 'unix', 'Win32', 'binary', 'Cabal', 'Cabal-syntax', 'terminfo', 'xhtml', 'semaphore-compat', 'system-cxx-std-lib']);
48
-
49
- function componentScope(componentName) {
50
- const c = typeof componentName === 'string' ? componentName : 'lib';
51
- const kind = c.split(':')[0];
52
- const name = c.includes(':') ? c.slice(c.indexOf(':') + 1) : null;
53
- const scope = { lib: 'runtime', exe: 'runtime', flib: 'runtime', test: 'test', bench: 'benchmark', setup: 'setup' }[kind] || 'runtime';
54
- return { kind, name, scope, sublibrary: kind === 'lib' && name ? name : null };
55
- }
56
-
57
- function planOrigin(u, localNames) {
58
- if (u.type === 'pre-existing') return { origin: 'compiler', boot: true, evidence: 'plan-type:pre-existing' };
59
- const src = isObj(u['pkg-src']) ? u['pkg-src'] : null;
60
- const st = src && typeof src.type === 'string' ? src.type : null;
61
- if (u.style === 'local' || st === 'local') return { origin: 'local', boot: false, evidence: u.style === 'local' ? 'style:local' : 'pkg-src:local' };
62
- if (st === 'repo-tar') return { origin: 'hackage', boot: false, evidence: `pkg-src:repo-tar${src.repo && src.repo.type ? `/${src.repo.type}` : ''}` };
63
- if (st === 'source-repo') return { origin: 'vcs', boot: false, evidence: 'pkg-src:source-repo' };
64
- if (st === 'remote-tarball') return { origin: 'url', boot: false, evidence: 'pkg-src:remote-tarball' };
65
- if (localNames.has(u['pkg-name']) && !st) return { origin: 'unknown', boot: false, evidence: 'name matches a local package but plan does not say local' };
66
- return { origin: 'unknown', boot: false, evidence: st ? `pkg-src:${st}` : 'no pkg-src' };
67
- }
68
-
69
- function depIds(u) {
70
- const dep = []; const exe = [];
71
- const take = (arr, into) => { if (Array.isArray(arr)) for (const x of arr) if (typeof x === 'string') into.push(x); };
72
- take(u.depends, dep); take(u['exe-depends'], exe);
73
- if (isObj(u.components)) for (const c of Object.values(u.components)) if (isObj(c)) { take(c.depends, dep); take(c['exe-depends'], exe); }
74
- return { dep: [...new Set(dep)], exe: [...new Set(exe)] };
75
- }
76
-
77
- /** Local package facts derived from the declared manifest analysis. */
78
- function manifestFacts(manifests) {
79
- const m = manifests || {};
80
- const locals = new Map(); // name -> {version}
81
- for (const p of m.packages || []) if (p.name) locals.set(p.name, { version: p.version || null });
82
- for (const p of m.hpack || []) if (p.name && !locals.has(p.name)) locals.set(p.name, { version: p.version || null });
83
- const withCompiler = (m.projects || []).map((p) => p.withCompiler).find((c) => typeof c === 'string' && /^ghc-\d/.test(c)) || null;
84
- const stackFile = (m.stack || [])[0] || null;
85
- let stackCompiler = null;
86
- if (stackFile) {
87
- if (typeof stackFile.compiler === 'string') stackCompiler = stackFile.compiler;
88
- else if (stackFile.snapshot && stackFile.snapshot.kind === 'compiler') stackCompiler = stackFile.snapshot.selector;
89
- }
90
- return {
91
- locals, compiler: withCompiler || stackCompiler, stackFile,
92
- flagSettings: m.flagSettings || [], flags: m.flags || [], dependencies: m.dependencies || [],
93
- relaxed: (m.projects || []).some((p) => (p.allowRelaxations || []).length > 0),
94
- };
95
- }
96
-
97
- // ---------------------------------------------------------------------------
98
- // Cabal plan.json
99
- // ---------------------------------------------------------------------------
100
-
101
- function propagateScopes(units, edges) {
102
- const byId = new Map(units.map((u) => [u.id, u]));
103
- const out = new Map();
104
- for (const e of edges) { if (!out.has(e.from)) out.set(e.from, []); out.get(e.from).push(e); }
105
- const seen = new Map(units.map((u) => [u.id, new Set()]));
106
- const queue = [];
107
- for (const u of units) if (u.origin === 'local') { seen.get(u.id).add(u.ownScope); queue.push([u.id, u.ownScope]); }
108
- while (queue.length) {
109
- const [id, scope] = queue.shift();
110
- for (const e of out.get(id) || []) {
111
- const s = e.kind === 'exe-depends' ? 'build-tool' : scope;
112
- const set = seen.get(e.to);
113
- if (set && !set.has(s)) { set.add(s); queue.push([e.to, s]); }
114
- }
115
- }
116
- for (const [id, set] of seen) byId.get(id).scopes = [...set].sort();
117
- }
118
-
119
- function parsePlan(plan, facts, budgets) {
120
- const gaps = [];
121
- if (!isObj(plan) || !Array.isArray(plan['install-plan'])) {
122
- return { units: null, edges: null, gaps: [gap('malformed_plan', 'plan.json has no `install-plan` array; no graph could be read')], compiler: null, platform: null, malformed: true };
123
- }
124
- const raw = plan['install-plan'];
125
- let truncated = false;
126
- const list = raw.length > budgets.maxUnits ? (truncated = true, raw.slice(0, budgets.maxUnits)) : raw;
127
- if (truncated) gaps.push(gap('budget_exceeded', `plan has ${raw.length} units; only ${budgets.maxUnits} were read`));
128
- const localNames = new Set(facts.locals.keys());
129
- const units = [];
130
- const ids = new Set();
131
- for (const [i, u] of list.entries()) {
132
- if (!isObj(u) || typeof u.id !== 'string' || typeof u['pkg-name'] !== 'string') {
133
- gaps.push(gap('malformed_unit', `install-plan entry ${i} lacks id or pkg-name; skipped`, { index: i }));
134
- continue;
135
- }
136
- if (ids.has(u.id)) { gaps.push(gap('duplicate_unit_id', `unit id ${u.id} appears twice; first kept`, { unit: u.id })); continue; }
137
- ids.add(u.id);
138
- const o = planOrigin(u, localNames);
139
- const comp = componentScope(u['component-name']);
140
- units.push({
141
- id: u.id, name: u['pkg-name'], version: typeof u['pkg-version'] === 'string' ? u['pkg-version'] : null,
142
- origin: o.origin, boot: o.boot, originEvidence: o.evidence, planType: u.type || null, style: u.style || null,
143
- component: { kind: comp.kind, name: comp.name }, sublibrary: comp.sublibrary, ownScope: comp.scope, scopes: [],
144
- flags: isObj(u.flags) ? { ...u.flags } : {},
145
- sha256: typeof u['pkg-src-sha256'] === 'string' ? u['pkg-src-sha256'] : null,
146
- repo: isObj(u['pkg-src']) && isObj(u['pkg-src'].repo) ? { type: u['pkg-src'].repo.type || null, uri: u['pkg-src'].repo.uri || null } : null,
147
- _deps: depIds(u),
148
- });
149
- }
150
- const edges = [];
151
- for (const u of units) {
152
- for (const [kind, arr] of [['depends', u._deps.dep], ['exe-depends', u._deps.exe]]) {
153
- for (const to of arr) {
154
- if (!ids.has(to)) { gaps.push(gap('dangling_edge', `unit ${u.id} depends on ${to}, which the plan does not contain`, { from: u.id, to })); continue; }
155
- if (edges.length >= budgets.maxEdges) { gaps.push(gap('budget_exceeded', 'edge budget reached')); break; }
156
- edges.push({ from: u.id, to, kind });
157
- }
158
- }
159
- delete u._deps;
160
- }
161
- propagateScopes(units, edges);
162
- for (const u of units) delete u.ownScope;
163
- const cid = typeof plan['compiler-id'] === 'string' ? plan['compiler-id'] : null;
164
- return {
165
- units, edges, gaps, malformed: false,
166
- compiler: cid ? { id: cid, name: cid.replace(/-\d.*$/, ''), version: (/-(\d+(?:\.\d+)*)$/.exec(cid) || [])[1] || null } : null,
167
- platform: { os: plan.os || null, arch: plan.arch || null },
168
- };
169
- }
170
-
171
- function flagMap(manifestFlagSettings, flagDecls, pkgName) {
172
- // expected explicit settings for one local package, plus manual-flag defaults
173
- const exp = new Map();
174
- for (const f of flagDecls) if (f.package === pkgName && f.manual && typeof f.default === 'boolean') exp.set(f.name, { value: f.default, from: 'manual-default' });
175
- for (const s of manifestFlagSettings) {
176
- if (s.scope === 'stack' || typeof s.value !== 'boolean') continue;
177
- if (s.package === pkgName || s.package === null) exp.set(s.name, { value: s.value, from: `${s.scope}-setting` });
178
- }
179
- return exp;
180
- }
181
-
182
- function checkPlanFreshness(parsed, facts, expected) {
183
- const reasons = [];
184
- const stale = (kind, message, extra = {}) => reasons.push({ kind, message, ...extra });
185
- const wantCompiler = expected.compiler || facts.compiler;
186
- if (wantCompiler && /^ghc-\d/.test(wantCompiler)) {
187
- if (!parsed.compiler) stale('compiler_unknown', 'plan records no compiler-id; compiler match cannot be shown', { severity: 'unverified' });
188
- else if (parsed.compiler.id !== wantCompiler) stale('compiler_mismatch', `plan was generated for ${parsed.compiler.id} but the project targets ${wantCompiler}`);
189
- }
190
- if (expected.os && parsed.platform && parsed.platform.os && parsed.platform.os !== expected.os) stale('platform_mismatch', `plan os ${parsed.platform.os} differs from expected ${expected.os}`);
191
- if (expected.arch && parsed.platform && parsed.platform.arch && parsed.platform.arch !== expected.arch) stale('platform_mismatch', `plan arch ${parsed.platform.arch} differs from expected ${expected.arch}`);
192
- if (Number.isFinite(expected.planMtimeMs) && Number.isFinite(expected.manifestMtimeMs) && expected.planMtimeMs < expected.manifestMtimeMs) stale('plan_older_than_manifest', 'plan.json is older than the newest manifest; it predates the current declarations');
193
-
194
- const units = parsed.units || [];
195
- const localUnits = units.filter((u) => u.origin === 'local');
196
- const planLocalNames = new Set(localUnits.map((u) => u.name));
197
- for (const [name, info] of facts.locals) {
198
- if (!planLocalNames.has(name)) stale('local_package_missing', `manifest declares local package ${name} but the plan has no local unit for it`, { package: name });
199
- else if (info.version) {
200
- const vs = localUnits.filter((u) => u.name === name).map((u) => u.version);
201
- if (!vs.includes(info.version)) stale('local_version_mismatch', `manifest says ${name} ${info.version} but the plan built ${vs.join(', ')}`, { package: name });
202
- }
203
- }
204
- if (facts.locals.size) for (const n of planLocalNames) if (!facts.locals.has(n)) stale('unknown_local_package', `plan builds local package ${n} that no manifest declares`, { package: n });
205
-
206
- for (const u of localUnits) {
207
- for (const [flag, e] of flagMap(facts.flagSettings, facts.flags, u.name)) {
208
- if (flag in u.flags && u.flags[flag] !== e.value) stale('flags_mismatch', `plan built ${u.name} with ${flag}=${u.flags[flag]} but the manifest/project sets ${e.value} (${e.from})`, { package: u.name, flag });
209
- }
210
- }
211
- if (Array.isArray(expected.targets)) {
212
- const have = new Set(units.map((u) => (u.origin === 'local' ? `${u.name}:${u.component.kind}${u.component.name ? `:${u.component.name}` : ''}` : null)).filter(Boolean));
213
- for (const t of expected.targets) if (!have.has(t)) stale('target_missing', `expected target ${t} is not in the plan`, { target: t });
214
- }
215
- // Declared, unconditional runtime dependencies must be explainable by the plan.
216
- const names = new Set(units.map((u) => u.name));
217
- const byName = new Map();
218
- for (const u of units) { if (!byName.has(u.name)) byName.set(u.name, []); byName.get(u.name).push(u); }
219
- for (const d of facts.dependencies) {
220
- if (d.sublibrary || !d.package || !facts.locals.has(d.package) || (d.conditions || []).length) continue;
221
- if (d.manifestType === 'cabal' && d.shadowedBy) continue;
222
- if (d.scope !== 'runtime') continue;
223
- if (!names.has(d.name)) { stale('declared_dependency_missing', `${d.package} declares ${d.name} but the plan contains no such package`, { package: d.package, dependency: d.name }); continue; }
224
- if (!facts.relaxed && d.rangeKind !== 'unparsed' && d.declaredRange && d.rangeError == null) {
225
- const ok = byName.get(d.name).some((u) => { try { return u.version && (0,_haskell_manifests_js__WEBPACK_IMPORTED_MODULE_2__/* .versionSatisfies */ .Kw)(d.declaredRange, u.version); } catch { return true; } });
226
- if (!ok) stale('bounds_violated', `plan has ${d.name} ${byName.get(d.name).map((u) => u.version).join(', ')}, outside declared bound ${d.declaredRange} of ${d.package}`, { package: d.package, dependency: d.name });
227
- }
228
- }
229
- return reasons;
230
- }
231
-
232
- // ---------------------------------------------------------------------------
233
- // Stack export
234
- // ---------------------------------------------------------------------------
235
-
236
- function parseStackExport(doc, facts, budgets) {
237
- const envelope = isObj(doc) && Array.isArray(doc.packages) ? doc : null;
238
- const list = Array.isArray(doc) ? doc : (envelope ? envelope.packages : null);
239
- if (!Array.isArray(list)) return { units: null, edges: null, gaps: [gap('malformed_stack_export', 'stack export is neither an array nor an object with `packages`; no graph could be read')], malformed: true, envelope: null };
240
- const gaps = [];
241
- const scope = envelope && typeof envelope.scope === 'string' ? envelope.scope : null;
242
- const units = [];
243
- const idOf = new Map();
244
- for (const [i, e] of list.slice(0, budgets.maxUnits).entries()) {
245
- if (!isObj(e) || typeof e.name !== 'string') { gaps.push(gap('malformed_unit', `stack export entry ${i} has no name; skipped`, { index: i })); continue; }
246
- const version = typeof e.version === 'string' ? e.version : null;
247
- const id = `${e.name}-${version || '?'}`;
248
- if (idOf.has(e.name)) { gaps.push(gap('duplicate_unit_id', `package ${e.name} appears twice; first kept`, { unit: id })); continue; }
249
- idOf.set(e.name, id);
250
- const loc = isObj(e.location) ? e.location : null;
251
- let origin; let boot = false; let evidence;
252
- if (facts.locals.has(e.name)) { origin = 'local'; evidence = 'name matches a local manifest package'; }
253
- else if (loc && loc.type === 'hackage') { origin = 'hackage'; evidence = 'location:hackage'; }
254
- else if (loc && typeof loc.type === 'string') { origin = ['git', 'hg', 'github'].includes(loc.type) ? 'vcs' : (loc.type === 'archive' || loc.type === 'url' ? 'url' : 'unknown'); evidence = `location:${loc.type}`; }
255
- else if (BOOT_FALLBACK.has(e.name)) { origin = 'compiler'; boot = true; evidence = 'boot-name-fallback (export carries no location)'; }
256
- else { origin = 'unknown'; evidence = 'export carries no location'; }
257
- units.push({
258
- id, name: e.name, version, origin, boot, originEvidence: evidence, planType: null, style: null,
259
- component: { kind: null, name: null }, sublibrary: null, scopes: [scope || 'unspecified'], flags: {}, sha256: null, repo: null,
260
- _names: Array.isArray(e.dependencies) ? e.dependencies.filter((x) => typeof x === 'string') : [],
261
- });
262
- }
263
- const edges = [];
264
- for (const u of units) {
265
- for (const n of u._names) {
266
- const to = idOf.get(n);
267
- if (!to) { gaps.push(gap('dangling_edge', `${u.id} depends on ${n}, which the stack export does not contain`, { from: u.id, to: n })); continue; }
268
- if (edges.length < budgets.maxEdges) edges.push({ from: u.id, to, kind: 'depends' });
269
- }
270
- delete u._names;
271
- }
272
- return { units, edges, gaps, malformed: false, envelope };
273
- }
274
-
275
- function checkStackFreshness(parsed, facts, expected) {
276
- const reasons = [];
277
- const env = parsed.envelope;
278
- const stale = (kind, message, extra = {}) => reasons.push({ kind, message, ...extra });
279
- if (!env) { stale('stack_export_unlabelled', 'a bare stack export carries no snapshot or compiler, so it cannot be matched to stack.yaml', { severity: 'unverified' }); }
280
- else {
281
- const sf = facts.stackFile;
282
- const snap = typeof env.snapshot === 'string' ? env.snapshot : (typeof env.resolver === 'string' ? env.resolver : null);
283
- const wantSnap = expected.snapshot || (sf && sf.snapshot ? sf.snapshot.selector : null);
284
- if (wantSnap) { if (!snap) stale('snapshot_unknown', 'export records no snapshot', { severity: 'unverified' }); else if (snap !== wantSnap) stale('snapshot_mismatch', `export was made for snapshot ${snap} but stack.yaml selects ${wantSnap}`); }
285
- const wantCompiler = expected.compiler || facts.compiler;
286
- const comp = typeof env.compiler === 'string' ? env.compiler : null;
287
- if (wantCompiler && /^ghc-\d/.test(wantCompiler) && comp && comp !== wantCompiler) stale('compiler_mismatch', `export compiler ${comp} differs from ${wantCompiler}`);
288
- if (!snap && !comp) stale('stack_export_unlabelled', 'export envelope carries neither snapshot nor compiler', { severity: 'unverified' });
289
- }
290
- const units = parsed.units || [];
291
- const names = new Set(units.map((u) => u.name));
292
- for (const n of facts.locals.keys()) if (!names.has(n)) stale('local_package_missing', `manifest declares local package ${n} but the stack export lacks it`, { package: n });
293
- const extra = facts.stackFile ? (facts.stackFile.extraDeps || []).filter((e) => e.kind === 'hackage' && e.version) : [];
294
- for (const e of extra) {
295
- const u = units.find((x) => x.name === e.name);
296
- if (u && u.version && u.version !== e.version) stale('extra_dep_mismatch', `stack.yaml pins ${e.name}-${e.version} but the export has ${u.version}`, { package: e.name });
297
- }
298
- return reasons;
299
- }
300
-
301
- // ---------------------------------------------------------------------------
302
- // Declared inventory join
303
- // ---------------------------------------------------------------------------
304
-
305
- function joinDeclared(manifests, graph) {
306
- const byName = new Map();
307
- for (const u of graph.units || []) { if (!byName.has(u.name)) byName.set(u.name, []); byName.get(u.name).push(u); }
308
- const locked = new Map();
309
- for (const l of (manifests && manifests.lockedPackages) || []) if (l.version && !locked.has(l.name)) locked.set(l.name, l.version);
310
- const usable = graph.graphAvailable && graph.freshness.status === 'fresh';
311
- const why = !graph.graphAvailable ? 'no_resolved_data' : (graph.freshness.status === 'stale' ? 'stale_resolved_data' : 'unverified_resolved_data');
312
- return ((manifests && manifests.dependencies) || []).map((d) => {
313
- const base = { ...d, resolvedVersion: null, resolution: { status: 'unresolved', reason: why, unitIds: [] } };
314
- if (locked.has(d.name)) base.lockedVersion = locked.get(d.name);
315
- if (!usable) return base;
316
- const cands = (byName.get(d.name) || []).filter((u) => !d.sublibrary || u.sublibrary === d.sublibrary || u.sublibrary === null);
317
- if (!cands.length) return { ...base, resolution: { status: 'unresolved', reason: 'not_in_resolved_graph', unitIds: [] } };
318
- const versions = [...new Set(cands.map((u) => u.version))];
319
- if (versions.length > 1) return { ...base, resolution: { status: 'ambiguous', reason: 'multiple_versions_in_graph', unitIds: cands.map((u) => u.id) } };
320
- return { ...base, resolvedVersion: versions[0], resolution: { status: 'resolved', reason: null, unitIds: cands.map((u) => u.id), origin: cands[0].origin } };
321
- });
322
- }
323
-
324
- /**
325
- * Build the resolved graph.
326
- * input: { manifests (analyzeHaskellManifests output), plan (parsed plan.json),
327
- * stackExport (parsed JSON), expected: {compiler, os, arch, targets, snapshot,
328
- * planMtimeMs, manifestMtimeMs}, rejectStale, budgets }
329
- */
330
- function buildResolvedGraph(input = {}) {
331
- const budgets = { ...RESOLVED_BUDGETS, ...(input.budgets || {}) };
332
- const expected = input.expected || {};
333
- const facts = manifestFacts(input.manifests);
334
- const result = {
335
- schemaVersion: RESOLVED_SCHEMA_VERSION, source: 'none', graphAvailable: false,
336
- freshness: { status: 'unverified', reasons: [] }, compiler: null,
337
- units: null, edges: null, gaps: [], closure: { complete: false, reason: 'no_resolved_data' }, declared: [],
338
- };
339
- let parsed = null;
340
- let reasons = [];
341
- if (input.plan !== undefined && input.plan !== null) {
342
- result.source = 'cabal-plan';
343
- parsed = parsePlan(input.plan, facts, budgets);
344
- result.compiler = parsed.compiler || null;
345
- if (!parsed.malformed) reasons = checkPlanFreshness(parsed, facts, expected);
346
- } else if (input.stackExport !== undefined && input.stackExport !== null) {
347
- result.source = 'stack-export';
348
- parsed = parseStackExport(input.stackExport, facts, budgets);
349
- if (!parsed.malformed) reasons = checkStackFreshness(parsed, facts, expected);
350
- }
351
-
352
- const lockOnly = !parsed && (((input.manifests || {}).lockedPackages || []).length > 0);
353
- if (!parsed) {
354
- result.gaps.push(gap('no_resolved_data', 'no plan.json or stack export was supplied; the dependency graph is unknown (not empty). Nothing was fetched.'));
355
- if (lockOnly) {
356
- result.closure = { complete: false, reason: 'lock_only' };
357
- result.gaps.push(gap('lock_is_not_closure', 'freeze/lock pins name versions but not edges or the full transitive set; a closure is not claimed'));
358
- }
359
- } else {
360
- result.gaps.push(...parsed.gaps);
361
- if (parsed.malformed) result.closure = { complete: false, reason: 'malformed_resolved_data' };
362
- else {
363
- const hardStale = reasons.filter((r) => r.severity !== 'unverified');
364
- result.freshness = { status: hardStale.length ? 'stale' : (reasons.length ? 'unverified' : 'fresh'), reasons };
365
- for (const r of reasons) result.gaps.push(gap(r.severity === 'unverified' ? 'freshness_unverified' : 'stale_resolved_data', r.message, { reason: r.kind }));
366
- if (result.freshness.status === 'stale' && input.rejectStale) {
367
- result.closure = { complete: false, reason: 'rejected_stale' };
368
- } else {
369
- result.graphAvailable = true;
370
- result.units = parsed.units.sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
371
- result.edges = parsed.edges.sort((a, b) => `${a.from}\0${a.to}\0${a.kind}`.localeCompare(`${b.from}\0${b.to}\0${b.kind}`));
372
- const dangling = parsed.gaps.some((g) => ['dangling_edge', 'malformed_unit', 'budget_exceeded', 'duplicate_unit_id'].includes(g.kind));
373
- if (result.freshness.status === 'fresh' && !dangling) result.closure = { complete: true, reason: null };
374
- else result.closure = { complete: false, reason: result.freshness.status === 'fresh' ? 'graph_gaps' : `${result.freshness.status}_resolved_data` };
375
- }
376
- }
377
- }
378
- result.declared = joinDeclared(input.manifests, result);
379
- return result;
380
- }
381
-
382
- /**
383
- * Link imported module names to packages. A link is `verified` only when `moduleIndex`
384
- * (unit id or package name -> exposed modules, from package metadata) lists the module
385
- * AND that package is in the graph. Anything else is reported unlinked, never guessed.
386
- */
387
- function linkModulesToPackages(graph, moduleIndex, imports) {
388
- const idx = isObj(moduleIndex) ? moduleIndex : {};
389
- return (imports || []).map((mod) => {
390
- if (!graph || !graph.graphAvailable) return { module: mod, status: 'unlinked', reason: 'no_resolved_graph' };
391
- const hits = graph.units.filter((u) => (idx[u.id] || idx[u.name] || []).includes(mod));
392
- if (!hits.length) return { module: mod, status: 'unlinked', reason: 'no_metadata_for_module' };
393
- return { module: mod, status: hits.length === 1 ? 'verified' : 'ambiguous', units: hits.map((u) => u.id), packages: [...new Set(hits.map((u) => u.name))] };
394
- });
395
- }
396
-
397
- function readBounded(root, rel, budgets) {
398
- const abs = path.resolve(root, rel);
399
- const base = path.resolve(root);
400
- if (abs !== base && !abs.startsWith(base + path.sep)) return { error: gap('path_outside_root', `${rel} escapes the project root; not read`) };
401
- let st;
402
- try { st = fs.statSync(abs); } catch { return { error: gap('resolved_file_missing', `${rel} does not exist`) }; }
403
- if (!st.isFile()) return { error: gap('resolved_file_missing', `${rel} is not a regular file`) };
404
- if (st.size > budgets.maxBytes) return { error: gap('budget_exceeded', `${rel} exceeds ${budgets.maxBytes} bytes; not read`) };
405
- try { return { text: fs.readFileSync(abs, 'utf8'), mtimeMs: st.mtimeMs }; } catch (e) { return { error: gap('read_failed', String(e && e.message).slice(0, 120)) }; }
406
- }
407
-
408
- /**
409
- * Directory entry point. Plan and stack export are read ONLY from the explicit
410
- * relative paths given (`planPath`, `stackExportPath`); nothing is discovered or run.
411
- */
412
- function analyzeResolvedHaskell(root, opts = {}) {
413
- const budgets = { ...RESOLVED_BUDGETS, ...(opts.budgets || {}) };
414
- const manifests = analyzeHaskellManifestsInDir(root, opts);
415
- const extraGaps = [];
416
- const input = { manifests, expected: { ...(opts.expected || {}) }, rejectStale: opts.rejectStale, budgets };
417
- const load = (rel, key) => {
418
- const r = readBounded(root, rel, budgets);
419
- if (r.error) { extraGaps.push(r.error); return null; }
420
- try { input[key] = JSON.parse(r.text); } catch (e) { extraGaps.push(gap(key === 'plan' ? 'malformed_plan' : 'malformed_stack_export', `${rel} is not valid JSON: ${String(e && e.message).slice(0, 100)}`)); return null; }
421
- return r;
422
- };
423
- if (opts.planPath) { const r = load(opts.planPath, 'plan'); if (r && input.expected.planMtimeMs === undefined) input.expected.planMtimeMs = r.mtimeMs; }
424
- if (opts.stackExportPath && input.plan === undefined) load(opts.stackExportPath, 'stackExport');
425
- const g = buildResolvedGraph(input);
426
- g.gaps.unshift(...extraGaps);
427
- return { manifests, graph: g };
428
- }
429
-
430
-
431
- /***/ })
432
-
433
- };