@clear-capabilities/agentic-security-scanner 0.185.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 (167) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/agentic-security.js +24 -1
  3. package/dist/1128.index.js +25 -21
  4. package/dist/2584.index.js +1 -1
  5. package/dist/4171.index.js +2 -2
  6. package/dist/5752.index.js +466 -0
  7. package/dist/7405.index.js +1 -1
  8. package/dist/8158.index.js +1713 -0
  9. package/dist/8798.index.js +1 -1
  10. package/dist/9382.index.js +2 -2
  11. package/dist/9579.index.js +1 -1
  12. package/dist/agentic-security.mjs +29 -29
  13. package/dist/agentic-security.mjs.sha256 +1 -1
  14. package/dist/{0b301d45b231f144a1b4.json → ca62ee93079be4c24eee.json} +7 -7
  15. package/package.json +7 -7
  16. package/src/dataflow/CLAUDE.md +6 -3
  17. package/src/dataflow/access-paths.js +12 -2
  18. package/src/dataflow/demand-query.js +279 -40
  19. package/src/dataflow/dep-fingerprint.js +1 -1
  20. package/src/dataflow/exploit-prover.js +6 -2
  21. package/src/dataflow/ir-cache.js +268 -0
  22. package/src/dataflow/privacy-taint.js +7 -0
  23. package/src/engine.js +62 -5
  24. package/src/ir/CLAUDE.md +4 -0
  25. package/src/ir/callgraph.js +36 -8
  26. package/src/ir/index.js +18 -4
  27. package/src/ir/parser-js.js +10 -0
  28. package/src/language/CLAUDE.md +13 -0
  29. package/src/language/nix-option-types.js +170 -0
  30. package/src/language/nix-script-taint.js +46 -3
  31. package/src/language/nix-secrets.js +20 -2
  32. package/src/lineage/CLAUDE.md +2 -1
  33. package/src/lineage/smt-orchestrator.js +47 -11
  34. package/src/lineage/smt-z3-backend.js +294 -0
  35. package/src/posture/CLAUDE.md +5 -1
  36. package/src/posture/business-logic.js +16 -7
  37. package/src/posture/dag-commands.js +442 -0
  38. package/src/posture/finding-defaults.js +19 -0
  39. package/src/posture/git-history.js +91 -1
  40. package/src/sast/CLAUDE.md +2 -0
  41. package/src/sast/_call-args.js +70 -0
  42. package/src/sast/init-scope.js +87 -0
  43. package/src/sast/listen-bind-address.js +49 -0
  44. package/src/sast/redirect-target.js +106 -0
  45. package/src/sast/reflected-input.js +76 -0
  46. package/src/sast/send-file-root.js +27 -0
  47. package/src/sast/sensitive-data-logging.js +38 -1
  48. package/dist/119.index.js +0 -197
  49. package/dist/128.index.js +0 -129
  50. package/dist/1293.index.js +0 -5689
  51. package/dist/1336.index.js +0 -16331
  52. package/dist/1351.index.js +0 -133
  53. package/dist/1387.index.js +0 -271
  54. package/dist/1437.index.js +0 -23
  55. package/dist/1445.index.js +0 -110
  56. package/dist/1505.index.js +0 -151
  57. package/dist/1557.index.js +0 -293
  58. package/dist/1621.index.js +0 -311
  59. package/dist/1751.index.js +0 -160
  60. package/dist/1765.index.js +0 -727
  61. package/dist/1861.index.js +0 -690
  62. package/dist/1906.index.js +0 -433
  63. package/dist/208.index.js +0 -1631
  64. package/dist/2223.index.js +0 -159
  65. package/dist/2238.index.js +0 -295
  66. package/dist/2530.index.js +0 -152
  67. package/dist/2565.index.js +0 -116
  68. package/dist/2574.index.js +0 -127
  69. package/dist/2620.index.js +0 -345
  70. package/dist/2773.index.js +0 -98
  71. package/dist/2775.index.js +0 -32
  72. package/dist/2966.index.js +0 -171
  73. package/dist/2972.index.js +0 -87
  74. package/dist/2991.index.js +0 -131
  75. package/dist/3063.index.js +0 -833
  76. package/dist/3075.index.js +0 -227
  77. package/dist/3121.index.js +0 -297
  78. package/dist/3314.index.js +0 -263
  79. package/dist/3480.index.js +0 -1822
  80. package/dist/3481.index.js +0 -24
  81. package/dist/3506.index.js +0 -268
  82. package/dist/3533.index.js +0 -591
  83. package/dist/3653.index.js +0 -450
  84. package/dist/3740.index.js +0 -722
  85. package/dist/3822.index.js +0 -131
  86. package/dist/3868.index.js +0 -292
  87. package/dist/3918.index.js +0 -192
  88. package/dist/4090.index.js +0 -288
  89. package/dist/4113.index.js +0 -1286
  90. package/dist/4121.index.js +0 -1190
  91. package/dist/4142.index.js +0 -690
  92. package/dist/4168.index.js +0 -509
  93. package/dist/4281.index.js +0 -993
  94. package/dist/4336.index.js +0 -436
  95. package/dist/4387.index.js +0 -532
  96. package/dist/4413.index.js +0 -302
  97. package/dist/4476.index.js +0 -4510
  98. package/dist/4491.index.js +0 -133
  99. package/dist/4538.index.js +0 -185
  100. package/dist/4684.index.js +0 -319
  101. package/dist/4740.index.js +0 -165
  102. package/dist/4780.index.js +0 -209
  103. package/dist/4802.index.js +0 -225
  104. package/dist/4812.index.js +0 -379
  105. package/dist/4835.index.js +0 -126
  106. package/dist/494.index.js +0 -226
  107. package/dist/4991.index.js +0 -549
  108. package/dist/5199.index.js +0 -305
  109. package/dist/5216.index.js +0 -109
  110. package/dist/5439.index.js +0 -301
  111. package/dist/5443.index.js +0 -97
  112. package/dist/5496.index.js +0 -377
  113. package/dist/5738.index.js +0 -240
  114. package/dist/5801.index.js +0 -588
  115. package/dist/5817.index.js +0 -714
  116. package/dist/5858.index.js +0 -223
  117. package/dist/5933.index.js +0 -143
  118. package/dist/601.index.js +0 -23
  119. package/dist/6173.index.js +0 -273
  120. package/dist/6291.index.js +0 -1871
  121. package/dist/6312.index.js +0 -666
  122. package/dist/634.index.js +0 -16182
  123. package/dist/644.index.js +0 -87
  124. package/dist/6508.index.js +0 -283
  125. package/dist/6560.index.js +0 -645
  126. package/dist/6576.index.js +0 -422
  127. package/dist/6579.index.js +0 -271
  128. package/dist/6655.index.js +0 -3221
  129. package/dist/6745.index.js +0 -305
  130. package/dist/6818.index.js +0 -21642
  131. package/dist/6932.index.js +0 -1190
  132. package/dist/6972.index.js +0 -167
  133. package/dist/6995.index.js +0 -163
  134. package/dist/7249.index.js +0 -191
  135. package/dist/7372.index.js +0 -1197
  136. package/dist/7546.index.js +0 -541
  137. package/dist/76.index.js +0 -448
  138. package/dist/7879.index.js +0 -163
  139. package/dist/8025.index.js +0 -130
  140. package/dist/8054.index.js +0 -248
  141. package/dist/808.index.js +0 -1330
  142. package/dist/8097.index.js +0 -201
  143. package/dist/8126.index.js +0 -191
  144. package/dist/8178.index.js +0 -78
  145. package/dist/8382.index.js +0 -93
  146. package/dist/8408.index.js +0 -261
  147. package/dist/8540.index.js +0 -174
  148. package/dist/8635.index.js +0 -802
  149. package/dist/8688.index.js +0 -410
  150. package/dist/8699.index.js +0 -779
  151. package/dist/8891.index.js +0 -457
  152. package/dist/8916.index.js +0 -885
  153. package/dist/9146.index.js +0 -170
  154. package/dist/9168.index.js +0 -841
  155. package/dist/9292.index.js +0 -369
  156. package/dist/9422.index.js +0 -866
  157. package/dist/9440.index.js +0 -453
  158. package/dist/9479.index.js +0 -152
  159. package/dist/9481.index.js +0 -220
  160. package/dist/9539.index.js +0 -100
  161. package/dist/9547.index.js +0 -103
  162. package/dist/9692.index.js +0 -90
  163. package/dist/9800.index.js +0 -462
  164. package/dist/9899.index.js +0 -138
  165. package/dist/9981.index.js +0 -206
  166. package/dist/999.index.js +0 -117
  167. 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
- };