@clear-capabilities/agentic-security-scanner 0.144.0 → 0.147.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 (209) hide show
  1. package/CHANGELOG.md +390 -0
  2. package/bin/agentic-security.js +3813 -83
  3. package/dist/1122.index.js +702 -0
  4. package/dist/{301.index.js → 1301.index.js} +2 -2
  5. package/dist/1379.index.js +591 -0
  6. package/dist/{444.index.js → 1444.index.js} +13 -4
  7. package/dist/{660.index.js → 1660.index.js} +2 -2
  8. package/dist/{700.index.js → 1700.index.js} +2 -2
  9. package/dist/{905.index.js → 1905.index.js} +3 -3
  10. package/dist/{920.index.js → 1920.index.js} +3 -3
  11. package/dist/{238.index.js → 2238.index.js} +3 -3
  12. package/dist/2271.index.js +165 -0
  13. package/dist/{985.index.js → 2376.index.js} +1260 -340
  14. package/dist/2432.index.js +793 -0
  15. package/dist/2659.index.js +93 -0
  16. package/dist/{826.index.js → 2826.index.js} +2 -2
  17. package/dist/{830.index.js → 2830.index.js} +2 -2
  18. package/dist/2923.index.js +298 -0
  19. package/dist/{1.index.js → 3001.index.js} +5 -5
  20. package/dist/{117.index.js → 3117.index.js} +3 -3
  21. package/dist/3180.index.js +307 -0
  22. package/dist/3276.index.js +117 -0
  23. package/dist/{415.index.js → 3415.index.js} +2 -2
  24. package/dist/{499.index.js → 3499.index.js} +2 -2
  25. package/dist/3518.index.js +450 -0
  26. package/dist/{526.index.js → 3526.index.js} +14 -6
  27. package/dist/{736.index.js → 3736.index.js} +4 -4
  28. package/dist/{839.index.js → 3839.index.js} +4 -4
  29. package/dist/{113.index.js → 4113.index.js} +14 -6
  30. package/dist/{265.index.js → 4265.index.js} +2 -2
  31. package/dist/{384.index.js → 4384.index.js} +3 -3
  32. package/dist/4547.index.js +268 -0
  33. package/dist/4863.index.js +422 -0
  34. package/dist/{970.index.js → 4970.index.js} +67 -3
  35. package/dist/5051.index.js +770 -0
  36. package/dist/{144.index.js → 5144.index.js} +5 -5
  37. package/dist/{333.index.js → 5333.index.js} +3 -3
  38. package/dist/5343.index.js +185 -0
  39. package/dist/5350.index.js +866 -0
  40. package/dist/5561.index.js +436 -0
  41. package/dist/{637.index.js → 5637.index.js} +29 -7
  42. package/dist/{449.index.js → 5830.index.js} +78 -14
  43. package/dist/6626.index.js +532 -0
  44. package/dist/6662.index.js +297 -0
  45. package/dist/{675.index.js → 6675.index.js} +5 -5
  46. package/dist/{730.index.js → 6730.index.js} +6 -6
  47. package/dist/6829.index.js +225 -0
  48. package/dist/6944.index.js +130 -0
  49. package/dist/{178.index.js → 7178.index.js} +26 -8
  50. package/dist/{227.index.js → 7227.index.js} +2 -2
  51. package/dist/7310.index.js +520 -0
  52. package/dist/{552.index.js → 7552.index.js} +4 -4
  53. package/dist/7709.index.js +78 -0
  54. package/dist/8218.index.js +160 -0
  55. package/dist/{476.index.js → 8476.index.js} +4 -4
  56. package/dist/{513.index.js → 8513.index.js} +5 -5
  57. package/dist/{520.index.js → 8520.index.js} +2 -2
  58. package/dist/{718.index.js → 8718.index.js} +2 -2
  59. package/dist/{752.index.js → 8752.index.js} +2 -2
  60. package/dist/8846.index.js +100 -0
  61. package/dist/{435.index.js → 9091.index.js} +580 -187
  62. package/dist/{207.index.js → 9207.index.js} +2 -2
  63. package/dist/{220.index.js → 9220.index.js} +2 -2
  64. package/dist/9390.index.js +163 -0
  65. package/dist/{503.index.js → 9503.index.js} +2 -2
  66. package/dist/{801.index.js → 9801.index.js} +2 -2
  67. package/dist/{824.index.js → 9824.index.js} +2 -2
  68. package/dist/agentic-security.mjs +16 -16
  69. package/dist/agentic-security.mjs.sha256 +1 -1
  70. package/dist/compliance-frameworks/hipaa-security-rule.json +3 -2
  71. package/package.json +23 -10
  72. package/src/compare.js +6 -1
  73. package/src/dataflow/CLAUDE.md +2 -2
  74. package/src/dataflow/catalog.js +42 -0
  75. package/src/dataflow/orm-write-catalog.js +175 -0
  76. package/src/engine.js +580 -30
  77. package/src/fix/apply-fix-service.js +1 -0
  78. package/src/history-scan.js +22 -5
  79. package/src/ir/CLAUDE.md +2 -1
  80. package/src/ir/chrome-probe.mjs +150 -0
  81. package/src/ir/parser-js.js +94 -7
  82. package/src/lineage/CLAUDE.md +1203 -0
  83. package/src/lineage/DESIGN_DESTINATION_RESOLVER.md +156 -0
  84. package/src/lineage/DESIGN_GRAPH_BUILDER.md +938 -0
  85. package/src/lineage/DESIGN_HANDLING_ANALYZER.md +355 -0
  86. package/src/lineage/DESIGN_INTRAPROCEDURAL.md +628 -0
  87. package/src/lineage/DESIGN_PATH_PROVENANCE.md +3451 -0
  88. package/src/lineage/DESIGN_QUEUE_DETAIL.md +120 -0
  89. package/src/lineage/DESIGN_REGISTRIES.md +880 -0
  90. package/src/lineage/DESIGN_STORE_DETAIL.md +143 -0
  91. package/src/lineage/DESIGN_TRANSIT_PROTECTION.md +245 -0
  92. package/src/lineage/classification.js +56 -0
  93. package/src/lineage/coverage.js +658 -0
  94. package/src/lineage/cross-repo-link.js +107 -0
  95. package/src/lineage/dataflow-graph.schema.json +184 -0
  96. package/src/lineage/decision-story.js +206 -0
  97. package/src/lineage/drift-policy.js +279 -0
  98. package/src/lineage/driver.js +135 -0
  99. package/src/lineage/engine.js +992 -0
  100. package/src/lineage/export-briefing.js +628 -0
  101. package/src/lineage/export-csv.js +62 -0
  102. package/src/lineage/export-json.js +238 -0
  103. package/src/lineage/export-privacy.js +258 -0
  104. package/src/lineage/federation-loader.js +111 -0
  105. package/src/lineage/field-identity.js +78 -0
  106. package/src/lineage/fixtures/build-flagship-fixture.mjs +272 -0
  107. package/src/lineage/fixtures/flagship-graph.json +1453 -0
  108. package/src/lineage/flow-grade.js +221 -0
  109. package/src/lineage/governance-edit.js +169 -0
  110. package/src/lineage/graph-builder.js +1114 -0
  111. package/src/lineage/graph-diff.js +431 -0
  112. package/src/lineage/graph-snapshot.js +180 -0
  113. package/src/lineage/handling-analyzer.js +168 -0
  114. package/src/lineage/ids.js +349 -0
  115. package/src/lineage/impact-assessment.js +76 -0
  116. package/src/lineage/impact-engine.js +268 -0
  117. package/src/lineage/index.js +281 -0
  118. package/src/lineage/language-coverage-tiers.js +58 -0
  119. package/src/lineage/obligation-mapping.js +126 -0
  120. package/src/lineage/obligation-predicates.js +235 -0
  121. package/src/lineage/observation-adapters.js +282 -0
  122. package/src/lineage/observation-correlation.js +622 -0
  123. package/src/lineage/observation-store.js +497 -0
  124. package/src/lineage/path-query.js +410 -0
  125. package/src/lineage/path-store.js +400 -0
  126. package/src/lineage/protection.js +53 -0
  127. package/src/lineage/recipient-profile.js +192 -0
  128. package/src/lineage/recipient-registry.js +394 -0
  129. package/src/lineage/redact-graph.js +224 -0
  130. package/src/lineage/remediation.js +417 -0
  131. package/src/lineage/resolve-destination.js +91 -0
  132. package/src/lineage/runtime-observation.js +464 -0
  133. package/src/lineage/scenario-diff.js +84 -0
  134. package/src/lineage/scenario-engine.js +251 -0
  135. package/src/lineage/scenario.js +101 -0
  136. package/src/lineage/schema.js +167 -0
  137. package/src/lineage/sink-registry.js +427 -0
  138. package/src/lineage/source-registry.js +357 -0
  139. package/src/lineage/source-seeding.js +212 -0
  140. package/src/lineage/summaries.js +590 -0
  141. package/src/lineage/transform-catalog.js +397 -0
  142. package/src/lineage/transit-protection.js +150 -0
  143. package/src/lineage/validate.js +285 -0
  144. package/src/lsp/server.js +49 -2
  145. package/src/mcp/CLAUDE.md +7 -1
  146. package/src/mcp/dataflow-tools.js +160 -0
  147. package/src/mcp/server.js +1 -1
  148. package/src/mcp/tools.js +22 -1
  149. package/src/pipeline/assurance-mode.js +64 -1
  150. package/src/pipeline/finding-schema.js +8 -1
  151. package/src/pipeline/scan-health.js +19 -1
  152. package/src/posture/CLAUDE.md +140 -0
  153. package/src/posture/accuracy-scorecard.js +60 -0
  154. package/src/posture/artifact-registry.js +76 -0
  155. package/src/posture/auditor-walkthrough.js +192 -13
  156. package/src/posture/compliance-frameworks/hipaa-security-rule.json +3 -2
  157. package/src/posture/compliance-policy.js +12 -2
  158. package/src/posture/cross-repo-memory.js +7 -2
  159. package/src/posture/fix-history.js +25 -2
  160. package/src/posture/fix-verify.js +9 -1
  161. package/src/posture/fleet.js +0 -0
  162. package/src/posture/git-history.js +13 -5
  163. package/src/posture/material-change.js +21 -2
  164. package/src/posture/mttr.js +75 -12
  165. package/src/posture/obligation-evidence-pack.js +202 -0
  166. package/src/posture/pre-incident-archaeology.js +39 -7
  167. package/src/posture/privacy-framework.js +14 -0
  168. package/src/posture/provenance/ai-authorship.js +68 -0
  169. package/src/posture/provenance/branch-entry.js +80 -0
  170. package/src/posture/provenance/cache.js +143 -0
  171. package/src/posture/provenance/confidence.js +36 -0
  172. package/src/posture/provenance/coordinator.js +786 -0
  173. package/src/posture/provenance/dag-walk.js +249 -0
  174. package/src/posture/provenance/evidence-attribution.js +59 -0
  175. package/src/posture/provenance/git-evidence.js +310 -0
  176. package/src/posture/provenance/lifecycle.js +208 -0
  177. package/src/posture/provenance/missing-control-resolver.js +137 -0
  178. package/src/posture/provenance/origin-resolver.js +342 -0
  179. package/src/posture/provenance/predicate-replay.js +133 -0
  180. package/src/posture/provenance/providers/config.js +39 -0
  181. package/src/posture/provenance/providers/github.js +62 -0
  182. package/src/posture/provenance/providers/gitlab.js +58 -0
  183. package/src/posture/provenance/repo-lineage.js +74 -0
  184. package/src/posture/provenance/sca-origin.js +139 -0
  185. package/src/posture/provenance/schema.js +255 -0
  186. package/src/posture/provenance/transitive-sca.js +147 -0
  187. package/src/posture/provenance/validate.js +30 -0
  188. package/src/posture/provenance-evidence-bundle.js +144 -0
  189. package/src/posture/remediation-ledger.js +337 -0
  190. package/src/posture/sbom-diff.js +15 -2
  191. package/src/posture/secret-history.js +10 -2
  192. package/src/posture/state-dir.js +38 -14
  193. package/src/posture/vuln-archaeology.js +8 -2
  194. package/src/pr-delta.js +25 -4
  195. package/src/report/index.js +197 -3
  196. package/src/runScan.js +34 -5
  197. package/src/sast/rate-limit.js +33 -3
  198. package/src/server/CLAUDE.md +47 -0
  199. package/src/server/graph-loader.js +141 -0
  200. package/src/server/http-server.js +325 -0
  201. package/src/server/routes.js +129 -0
  202. package/src/server/security.js +111 -0
  203. package/src/server/static-assets.js +139 -0
  204. package/src/util/git-hardening.js +128 -0
  205. package/dist/11.index.js +0 -353
  206. package/dist/259.index.js +0 -975
  207. package/dist/317.index.js +0 -300
  208. package/dist/609.index.js +0 -741
  209. package/dist/838.index.js +0 -152
@@ -0,0 +1,532 @@
1
+ export const id = 6626;
2
+ export const ids = [6626,2659];
3
+ export const modules = {
4
+
5
+ /***/ 2659:
6
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
7
+
8
+ /* harmony export */ __webpack_require__.d(__webpack_exports__, {
9
+ /* harmony export */ qc: () => (/* binding */ IMPACT_VERSION),
10
+ /* harmony export */ validateImpactAssessment: () => (/* binding */ validateImpactAssessment)
11
+ /* harmony export */ });
12
+ /* unused harmony exports IMPACT_TARGET_KINDS, IMPACT_SCOPE_VALUES, IMPACT_TRACE_KINDS */
13
+ // impact-assessment.js — M5 deliverable #4 (FR-507 §10.10): the
14
+ // ImpactAssessment extension contract — the result of asking "what is
15
+ // reachable from this compromised node/edge/flow/data element, per the
16
+ // graph's own already-scanned evidence." NOT a DataFlowGraph v1 entity,
17
+ // mirrors recipient-profile.js's/scenario.js's own contract shape
18
+ // exactly (structural-only {valid, errors} validator, zero graph
19
+ // access at construction time).
20
+ //
21
+ // See docs/superpowers/plans/2026-09-02-data-flow-explorer-m5-blast-radius-impact-scoping.md
22
+ // for the full design reasoning, including why there is no
23
+ // affectedObligationIds field (ObligationMapping records are built on
24
+ // demand per compliance framework, not stored on the graph) and why
25
+ // `scope` is always 'possible' today (no runtime-corroboration layer
26
+ // exists yet).
27
+
28
+ const IMPACT_VERSION = '1.0.0';
29
+
30
+ const IMPACT_TARGET_KINDS = Object.freeze(['node', 'edge', 'flow', 'dataElement']);
31
+
32
+ // 'possible' is the only value any producer emits today — 'observed'
33
+ // is reserved for a future Digital Twin (M5 #7) increment with a real
34
+ // runtime-corroboration signal. Both are valid schema values now so
35
+ // that increment needs no breaking change to this contract later.
36
+ const IMPACT_SCOPE_VALUES = Object.freeze(['possible', 'observed']);
37
+
38
+ // Discloses WHICH of the two genuinely different traversal semantics
39
+ // produced this record (final-review I2 fix) — `scope` alone cannot
40
+ // distinguish them, since both families emit `scope: 'possible'`.
41
+ // 'topology_reachable': a `node` target's topology-wide showAllPaths
42
+ // BFS — "everything this compromised node could push to." 'flow_
43
+ // restricted': an `edge`/`flow`/`dataElement` target's direct trace
44
+ // over the flows that actually carry it — never the topology-wide BFS.
45
+ // Without this field a JSON consumer cannot tell "exact carrier trace"
46
+ // from "topological presumption" apart, the exact silent-conflation
47
+ // this codebase's own disclosure discipline exists to prevent.
48
+ const IMPACT_TRACE_KINDS = Object.freeze(['topology_reachable', 'flow_restricted']);
49
+
50
+ function _isNonEmptyString(v) { return typeof v === 'string' && v.length > 0; }
51
+ function _isStringArray(v) { return Array.isArray(v) && v.every((x) => typeof x === 'string'); }
52
+
53
+ /**
54
+ * Structural validation only — mirrors validateRecipientProfile's/
55
+ * validateScenario's own {valid, errors} shape and "never throws"
56
+ * contract.
57
+ */
58
+ function validateImpactAssessment(record) {
59
+ const errors = [];
60
+ const err = (p, message) => errors.push({ path: p, message });
61
+ if (!record || typeof record !== 'object' || Array.isArray(record)) {
62
+ err('$', 'ImpactAssessment record must be an object');
63
+ return { valid: false, errors };
64
+ }
65
+ if (!_isNonEmptyString(record.id) || !record.id.startsWith('impact:')) {
66
+ err('$.id', 'id is required and must start with "impact:"');
67
+ }
68
+ if (!_isNonEmptyString(record.version)) err('$.version', 'version is required');
69
+ if (!_isNonEmptyString(record.graphId)) err('$.graphId', 'graphId is required');
70
+ if (!_isNonEmptyString(record.graphDigest)) err('$.graphDigest', 'graphDigest is required');
71
+ if (!_isNonEmptyString(record.targetId)) err('$.targetId', 'targetId is required');
72
+ if (!IMPACT_TARGET_KINDS.includes(record.targetKind)) {
73
+ err('$.targetKind', `targetKind must be one of ${IMPACT_TARGET_KINDS.join('|')}`);
74
+ }
75
+ if (!IMPACT_SCOPE_VALUES.includes(record.scope)) {
76
+ err('$.scope', `scope must be one of ${IMPACT_SCOPE_VALUES.join('|')}`);
77
+ }
78
+ if (!IMPACT_TRACE_KINDS.includes(record.traceKind)) {
79
+ err('$.traceKind', `traceKind must be one of ${IMPACT_TRACE_KINDS.join('|')}`);
80
+ }
81
+ if (!_isStringArray(record.affectedNodeIds ?? [])) err('$.affectedNodeIds', 'affectedNodeIds must be an array of strings');
82
+ if (!_isStringArray(record.affectedEdgeIds ?? [])) err('$.affectedEdgeIds', 'affectedEdgeIds must be an array of strings');
83
+ if (!_isStringArray(record.affectedDataClasses ?? [])) err('$.affectedDataClasses', 'affectedDataClasses must be an array of strings');
84
+ if (!_isStringArray(record.affectedRecipientProfileIds ?? [])) err('$.affectedRecipientProfileIds', 'affectedRecipientProfileIds must be an array of strings');
85
+ if (!_isStringArray(record.coverageLimitations ?? [])) err('$.coverageLimitations', 'coverageLimitations must be an array of strings');
86
+ if (!_isNonEmptyString(record.generatedAt)) err('$.generatedAt', 'generatedAt is required');
87
+ return { valid: errors.length === 0, errors };
88
+ }
89
+
90
+
91
+ /***/ }),
92
+
93
+ /***/ 6626:
94
+ /***/ ((__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) => {
95
+
96
+
97
+ // EXPORTS
98
+ __webpack_require__.d(__webpack_exports__, {
99
+ computeImpactAssessment: () => (/* binding */ computeImpactAssessment)
100
+ });
101
+
102
+ ;// CONCATENATED MODULE: ../frontend/src/lib/focus-controls.js
103
+ // Milestone 3, sub-project M3-UX-Query, Task 3. Nine focus-control graph
104
+ // traversals over the SAME DataFlowGraph v1 structure the query language
105
+ // (lib/query-language.js) reads. Each function returns the SAME
106
+ // `{nodeIds: Set<string>, edgeIds: Set<string>}` shape
107
+ // `views/architecture-view.js`'s own `resolveSelection` already produces
108
+ // for `selection` — so the render layer needs zero new consumption code to
109
+ // accept output from any of these. `resetToOverview` is deliberately NOT
110
+ // implemented here: Task 4 wires the existing `resolveSelection(graph,
111
+ // null)` directly for that control (see this file's own header note in the
112
+ // task brief) — duplicating it here would just be a second, divergent copy
113
+ // of the same empty-selection shape.
114
+
115
+ function buildAdjacency(graph) {
116
+ const forward = new Map(); // nodeId -> [{edgeId, toId}]
117
+ const backward = new Map(); // nodeId -> [{edgeId, fromId}]
118
+ for (const n of graph.nodes) { forward.set(n.id, []); backward.set(n.id, []); }
119
+ for (const e of graph.edges) {
120
+ forward.get(e.from)?.push({ edgeId: e.id, toId: e.to });
121
+ backward.get(e.to)?.push({ edgeId: e.id, fromId: e.from });
122
+ }
123
+ return { forward, backward };
124
+ }
125
+
126
+ function bfsDirection(graph, startId, adjacencyKey, adjacency) {
127
+ const nodeIds = new Set([startId]);
128
+ const edgeIds = new Set();
129
+ const queue = [startId];
130
+ while (queue.length > 0) {
131
+ const current = queue.shift();
132
+ for (const { edgeId, toId, fromId } of adjacency.get(current) ?? []) {
133
+ const nextId = adjacencyKey === 'forward' ? toId : fromId;
134
+ edgeIds.add(edgeId);
135
+ if (!nodeIds.has(nextId)) { nodeIds.add(nextId); queue.push(nextId); }
136
+ }
137
+ }
138
+ return { nodeIds, edgeIds };
139
+ }
140
+
141
+ function showDownstream(graph, nodeId) {
142
+ const { forward } = buildAdjacency(graph);
143
+ return bfsDirection(graph, nodeId, 'forward', forward);
144
+ }
145
+
146
+ function showUpstream(graph, nodeId) {
147
+ const { backward } = buildAdjacency(graph);
148
+ return bfsDirection(graph, nodeId, 'backward', backward);
149
+ }
150
+
151
+ function showAllPaths(graph, nodeId) {
152
+ const down = showDownstream(graph, nodeId);
153
+ const up = showUpstream(graph, nodeId);
154
+ return {
155
+ nodeIds: new Set([...down.nodeIds, ...up.nodeIds]),
156
+ edgeIds: new Set([...down.edgeIds, ...up.edgeIds]),
157
+ };
158
+ }
159
+
160
+ function showShortestPath(graph, fromId, toId) {
161
+ const { forward } = buildAdjacency(graph);
162
+ const cameFrom = new Map(); // nodeId -> {viaEdgeId, fromId}
163
+ const visited = new Set([fromId]);
164
+ const queue = [fromId];
165
+ let found = false;
166
+ while (queue.length > 0 && !found) {
167
+ const current = queue.shift();
168
+ for (const { edgeId, toId: nextId } of forward.get(current) ?? []) {
169
+ if (visited.has(nextId)) continue;
170
+ visited.add(nextId);
171
+ cameFrom.set(nextId, { viaEdgeId: edgeId, fromId: current });
172
+ if (nextId === toId) { found = true; break; }
173
+ queue.push(nextId);
174
+ }
175
+ }
176
+ if (!found) return { nodeIds: new Set(), edgeIds: new Set() };
177
+ const nodeIds = new Set([toId]);
178
+ const edgeIds = new Set();
179
+ let cursor = toId;
180
+ while (cursor !== fromId) {
181
+ const step = cameFrom.get(cursor);
182
+ edgeIds.add(step.viaEdgeId);
183
+ nodeIds.add(step.fromId);
184
+ cursor = step.fromId;
185
+ }
186
+ return { nodeIds, edgeIds };
187
+ }
188
+
189
+ function showExternalPathsOnly(graph) {
190
+ const externalNodeIds = new Set(graph.nodes.filter((n) => n.externality?.value === 'external').map((n) => n.id));
191
+ const nodeIds = new Set();
192
+ const edgeIds = new Set();
193
+ for (const flow of graph.flows) {
194
+ const pathNodeIds = new Set([flow.source, flow.sink]);
195
+ for (const edgeId of flow.edgeIds) {
196
+ const edge = graph.edges.find((e) => e.id === edgeId);
197
+ if (edge) { pathNodeIds.add(edge.from); pathNodeIds.add(edge.to); }
198
+ }
199
+ if ([...pathNodeIds].some((id) => externalNodeIds.has(id))) {
200
+ for (const id of pathNodeIds) nodeIds.add(id);
201
+ for (const edgeId of flow.edgeIds) edgeIds.add(edgeId);
202
+ }
203
+ }
204
+ return { nodeIds, edgeIds };
205
+ }
206
+
207
+ const UNPROTECTED_VERDICTS = new Set(['unprotected', 'mixed', 'unknown']);
208
+ function showUnprotectedPathsOnly(graph) {
209
+ const nodeIds = new Set();
210
+ const edgeIds = new Set();
211
+ for (const edge of graph.edges) {
212
+ const verdicts = [edge.protection.transit.verdict, edge.protection.atRest.verdict, edge.protection.handling.verdict];
213
+ if (verdicts.some((v) => UNPROTECTED_VERDICTS.has(v))) {
214
+ edgeIds.add(edge.id);
215
+ nodeIds.add(edge.from);
216
+ nodeIds.add(edge.to);
217
+ }
218
+ }
219
+ return { nodeIds, edgeIds };
220
+ }
221
+
222
+ function showAliases(graph, nodeId) {
223
+ const node = graph.nodes.find((n) => n.id === nodeId);
224
+ const nodeIds = new Set([nodeId]);
225
+ // Real, honest implementation. This plan's own Global Constraints section
226
+ // disclosed node.aliases as "confirmed always empty in real scan output" —
227
+ // that claim does NOT hold against the real committed flagship fixture
228
+ // (Web App/Payment API/Analytics API all carry non-empty aliases arrays
229
+ // there; see test/focus-controls.test.js for the correction). What IS
230
+ // true, confirmed against that same fixture: every alias entry is an
231
+ // alternate DISPLAY NAME for the node itself (e.g. "Checkout Form" is
232
+ // another name for the Web App node), never a pointer to a distinct
233
+ // sibling node record — no alias string in the fixture matches any other
234
+ // real node's label or id. This loop is still real, correct logic (not a
235
+ // no-op stub): it looks up each alias against the graph's own nodes and
236
+ // only adds a match it actually finds, so it will do the right thing the
237
+ // moment (if ever) alias data that DOES reference a distinct node record
238
+ // shows up in a real scan — nothing here is invented alias data.
239
+ for (const alias of node?.aliases ?? []) {
240
+ const aliasNode = graph.nodes.find((n) => n.label === alias || n.id === alias);
241
+ if (aliasNode) nodeIds.add(aliasNode.id);
242
+ }
243
+ return { nodeIds, edgeIds: new Set() };
244
+ }
245
+
246
+ function showDisconnected(graph) {
247
+ const connectedIds = new Set();
248
+ for (const e of graph.edges) { connectedIds.add(e.from); connectedIds.add(e.to); }
249
+ const nodeIds = new Set(graph.nodes.filter((n) => !connectedIds.has(n.id)).map((n) => n.id));
250
+ return { nodeIds, edgeIds: new Set() };
251
+ }
252
+
253
+ // EXTERNAL MODULE: ./src/lineage/export-json.js
254
+ var export_json = __webpack_require__(859);
255
+ // EXTERNAL MODULE: ./src/lineage/ids.js
256
+ var ids = __webpack_require__(5034);
257
+ // EXTERNAL MODULE: ./src/lineage/impact-assessment.js
258
+ var impact_assessment = __webpack_require__(2659);
259
+ ;// CONCATENATED MODULE: ./src/lineage/impact-engine.js
260
+ // impact-engine.js — M5 deliverable #4 (FR-507): the pure read/
261
+ // aggregate computation behind "assess impact" from a compromised
262
+ // node/edge/flow/data element. Reuses the already-shipped, already-
263
+ // tested BFS traversal in frontend/src/lib/focus-controls.js — the
264
+ // established scanner/src/ -> frontend/src/ cross-import precedent
265
+ // (export-privacy.js's own computePrivacyViewModel import). No
266
+ // mutation, no hypothesis, no re-run of the taint/path pipeline — a
267
+ // pure filter/aggregate over the graph's own already-computed fields.
268
+
269
+
270
+
271
+
272
+
273
+
274
+ // dataElement's real stable-id prefix is 'data:' (see ids.js's own
275
+ // dataElementId(), which returns `data:${hash}` — every real
276
+ // DataFlowGraph v1 document's dataElements[] carries this prefix, not
277
+ // 'de:'). node/edge/flow below match ids.js's nodeId/edgeId/flowId
278
+ // prefixes exactly for the identical reason.
279
+ const _KIND_PREFIXES = Object.freeze({ node: 'node:', edge: 'edge:', flow: 'flow:', dataElement: 'data:' });
280
+
281
+ function _resolveTargetKind(targetId) {
282
+ if (typeof targetId !== 'string') return null;
283
+ for (const [kind, prefix] of Object.entries(_KIND_PREFIXES)) {
284
+ if (targetId.startsWith(prefix)) return kind;
285
+ }
286
+ return null;
287
+ }
288
+
289
+ // The seed set for showAllPaths, node targets only — a node target
290
+ // seeds itself; edge/flow/dataElement targets never reach this
291
+ // function any more (final-review I1 fix). See
292
+ // _flowRestrictedAffectedSet's header comment for why: compromising a
293
+ // NODE genuinely puts everything reachable from it in the blast
294
+ // radius, but compromising one EDGE/FLOW/data-element does not — the
295
+ // affected record still NAMES that edge/flow's own two endpoint nodes
296
+ // (you need them to know what the compromised channel actually
297
+ // touches), it just never treats either endpoint as ITSELF fully
298
+ // compromised for the purpose of finding everything ELSE reachable
299
+ // from it via unrelated edges — that further step is exactly the
300
+ // over-inclusion class the original dataElement fix removed.
301
+ function _seedNodeIds(graph, targetId, targetKind) {
302
+ if (targetKind === 'node') return [targetId];
303
+ return [];
304
+ }
305
+
306
+ // Generalizes the direct flow-based trace to all three "named
307
+ // relationship" target kinds (dataElement/edge/flow) — each is
308
+ // restricted to the REAL flows that carry it, never the topology-wide
309
+ // showAllPaths BFS below (that stays node-only). A compromised
310
+ // flow/edge/data-element never sweeps in an unrelated sibling flow
311
+ // that merely shares a node — the exact carrier path is already on
312
+ // each flow's own record (source/sink/edgeIds/dataElementIds), so
313
+ // approximating with topology reachability here would discard real
314
+ // data, not fill a gap (reproduced live by this task's own review on
315
+ // a branching-topology graph: one source, three sinks, one flow each
316
+ // carrying a distinct data element — a `flow:`/`edge:` target for the
317
+ // sink-A flow incorrectly swept in sinks B and C, and their unrelated
318
+ // data classes/recipients with them). `matchesFlow(flow) -> boolean`
319
+ // selects which flows carry the target. Also computes dataClasses
320
+ // directly from the SAME matched-flow set (closes a related leak: the
321
+ // edge-membership-based _affectedDataClasses below could pull in an
322
+ // unrelated flow that merely shares one edge with the target flow but
323
+ // carries a different data element — unreachable on any real
324
+ // graph-builder output today since ids.edgeId's own discriminator
325
+ // includes the data element id, but this closes the gap for a future
326
+ // builder or an externally-supplied graph). Never adds a dangling
327
+ // edge id that doesn't resolve to a real edge in graph.edges. Node
328
+ // targets keep the showAllPaths BFS above — for those, "everything
329
+ // topologically reachable from the compromised node's own endpoints"
330
+ // IS the intended pessimistic scope:'possible' semantics.
331
+ function _flowRestrictedAffectedSet(graph, matchesFlow) {
332
+ const nodeIds = new Set();
333
+ const edgeIds = new Set();
334
+ const dataClasses = new Set();
335
+ for (const f of graph.flows ?? []) {
336
+ if (!matchesFlow(f)) continue;
337
+ nodeIds.add(f.source);
338
+ nodeIds.add(f.sink);
339
+ for (const deId of f.dataElementIds ?? []) {
340
+ const de = (graph.dataElements ?? []).find((d) => d.id === deId);
341
+ for (const c of de?.dataClasses ?? []) dataClasses.add(c);
342
+ }
343
+ for (const eId of f.edgeIds ?? []) {
344
+ const edge = (graph.edges ?? []).find((e) => e.id === eId);
345
+ if (!edge) continue; // never surface a dangling/unresolved edge id
346
+ edgeIds.add(eId);
347
+ nodeIds.add(edge.from);
348
+ nodeIds.add(edge.to);
349
+ }
350
+ }
351
+ return { nodeIds, edgeIds, dataClasses };
352
+ }
353
+
354
+ // Edge-membership-based aggregation — kept ONLY as the `node` target's
355
+ // fallback (a topology-reachability target has no single "the matched
356
+ // flows" set to read dataClasses off directly, unlike the three named-
357
+ // relationship kinds above, which use _flowRestrictedAffectedSet's own
358
+ // dataClasses instead).
359
+ function _affectedDataClasses(graph, affectedEdgeIds) {
360
+ const classes = new Set();
361
+ for (const f of graph.flows ?? []) {
362
+ if (!(f.edgeIds ?? []).some((id) => affectedEdgeIds.has(id))) continue;
363
+ for (const deId of f.dataElementIds ?? []) {
364
+ const de = (graph.dataElements ?? []).find((d) => d.id === deId);
365
+ for (const c of de?.dataClasses ?? []) classes.add(c);
366
+ }
367
+ }
368
+ return [...classes].sort();
369
+ }
370
+
371
+ function _affectedRecipientProfileIds(graph, affectedNodeIds) {
372
+ return (graph.recipientProfiles ?? [])
373
+ .filter((rp) => (rp.contributingGraphIds ?? []).some((id) => affectedNodeIds.has(id)))
374
+ .map((rp) => rp.id)
375
+ .sort();
376
+ }
377
+
378
+ // Whole-graph, not scoped to the affected subgraph — no node carries a
379
+ // language field to filter by, so a per-language coverage gap is
380
+ // reported as a real, honest limitation on any assessment computed
381
+ // over this graph, not narrowed to the exact affected nodes. See this
382
+ // sub-project's own implementation plan for the full disclosed
383
+ // reasoning. Complemented, not replaced, by _affectedCoverageLimitations
384
+ // below — that function reports the real per-entity coverageStatus gaps
385
+ // among the entities THIS assessment actually affects, which this
386
+ // whole-graph language disclosure cannot express.
387
+ function _coverageLimitations(graph) {
388
+ return (graph.coverage?.languages ?? [])
389
+ .filter((l) => l.tier && l.tier !== 'full')
390
+ .map((l) => `${l.language}: coverage tier '${l.tier}'${typeof l.irTaintRecallPct === 'number' ? ` (${l.irTaintRecallPct}% measured recall)` : ''}`);
391
+ }
392
+
393
+ // Per-entity coverage gap among the entities THIS assessment actually
394
+ // affects, using the real, required `coverageStatus` field every
395
+ // node/edge/flow already carries (`modeled|partial|candidate|
396
+ // unsupported|manual` — populated by graph-builder.js, already
397
+ // consumed by export-csv.js/graph-diff.js). Complements (never
398
+ // replaces) _coverageLimitations' whole-graph language disclosure
399
+ // above — both are real, complementary limitations of the same
400
+ // assessment.
401
+ function _affectedCoverageLimitations(graph, affectedNodeIds, affectedEdgeIds) {
402
+ const limitations = [];
403
+ const nonModeledNodes = (graph.nodes ?? []).filter((n) => affectedNodeIds.has(n.id) && n.coverageStatus && n.coverageStatus !== 'modeled');
404
+ const nonModeledEdges = (graph.edges ?? []).filter((e) => affectedEdgeIds.has(e.id) && e.coverageStatus && e.coverageStatus !== 'modeled');
405
+ if (nonModeledNodes.length) {
406
+ limitations.push(`${nonModeledNodes.length} of ${affectedNodeIds.size} affected node(s) have less-than-modeled coverage (e.g. ${nonModeledNodes[0].id}: coverage tier '${nonModeledNodes[0].coverageStatus}')`);
407
+ }
408
+ if (nonModeledEdges.length) {
409
+ limitations.push(`${nonModeledEdges.length} of ${affectedEdgeIds.size} affected edge(s) have less-than-modeled coverage (e.g. ${nonModeledEdges[0].id}: coverage tier '${nonModeledEdges[0].coverageStatus}')`);
410
+ }
411
+ return limitations;
412
+ }
413
+
414
+ /**
415
+ * Compute an ImpactAssessment for `targetId` over `graph`. Throws when
416
+ * `graph` is structurally malformed (missing `nodes`/`edges` arrays —
417
+ * see `loadSignedGraph`'s own "signature-only, no schema validation"
418
+ * contract for why a signed-but-malformed graph can reach here) with a
419
+ * message prefixed `computeImpactAssessment: malformed graph — `, so a
420
+ * caller can distinguish that from the other thrown case: `targetId`
421
+ * has no recognized canonical-id prefix (a genuine caller error, not a
422
+ * missing-entity case). A well-formed targetId that does not exist in
423
+ * the graph degrades honestly to empty affected-* arrays, never an
424
+ * error — mirrors applyScenario's own skip-not-throw contract for a
425
+ * stale/missing target.
426
+ *
427
+ * `node` targets report a deliberately pessimistic "everything
428
+ * topologically reachable" blast radius (`traceKind:
429
+ * 'topology_reachable'`) — compromising a node genuinely puts
430
+ * everything it can reach in the blast radius. `edge`/`flow`/
431
+ * `dataElement` targets report only the flows/nodes/edges that
432
+ * actually carry that specific edge/flow/data element (`traceKind:
433
+ * 'flow_restricted'`) — the record still names that edge/flow's own
434
+ * two endpoint nodes (you need them to know what the channel actually
435
+ * touches), it just never treats either endpoint as itself fully
436
+ * compromised for finding everything ELSE reachable from it via
437
+ * unrelated edges. The two target-kind families answer genuinely
438
+ * different questions, both honestly disclosed via `traceKind` rather
439
+ * than silently conflated under one `scope` value.
440
+ */
441
+ function computeImpactAssessment(graph, targetId, opts = {}) {
442
+ if (!Array.isArray(graph?.nodes) || !Array.isArray(graph?.edges)) {
443
+ throw new Error('computeImpactAssessment: malformed graph — graph.nodes and graph.edges must both be arrays');
444
+ }
445
+
446
+ const targetKind = _resolveTargetKind(targetId);
447
+ if (!targetKind) {
448
+ throw new Error(`computeImpactAssessment: targetId "${targetId}" has no recognized prefix (expected one of node:/edge:/flow:/data:)`);
449
+ }
450
+
451
+ // showAllPaths (frontend/src/lib/focus-controls.js) unconditionally
452
+ // seeds its own result with the start id itself, even when that id
453
+ // does not exist in the graph at all (bfsDirection's `nodeIds = new
454
+ // Set([startId])`) — so a seed must be filtered against the graph's
455
+ // real node ids first, or a well-formed-but-nonexistent targetId
456
+ // would surface as a phantom single-node "affected" set instead of
457
+ // degrading honestly to empty arrays. The same filter is applied to
458
+ // _flowRestrictedAffectedSet's own node ids below, for the identical
459
+ // reason.
460
+ const realNodeIds = new Set((graph.nodes ?? []).map((n) => n.id));
461
+ const affectedNodeIds = new Set();
462
+ const affectedEdgeIds = new Set();
463
+ let affectedDataClasses;
464
+
465
+ if (targetKind === 'node') {
466
+ const seedNodeIds = _seedNodeIds(graph, targetId, targetKind).filter((id) => realNodeIds.has(id));
467
+ for (const seedId of seedNodeIds) {
468
+ const { nodeIds, edgeIds } = showAllPaths(graph, seedId);
469
+ for (const id of nodeIds) affectedNodeIds.add(id);
470
+ for (const id of edgeIds) affectedEdgeIds.add(id);
471
+ }
472
+ affectedDataClasses = _affectedDataClasses(graph, affectedEdgeIds);
473
+ } else {
474
+ // dataElement/edge/flow — direct flow-based trace via
475
+ // _flowRestrictedAffectedSet, never showAllPaths. See that
476
+ // function's own header comment for why.
477
+ const matchesFlow = targetKind === 'dataElement'
478
+ ? (f) => f.dataElementIds?.includes(targetId)
479
+ : targetKind === 'edge'
480
+ ? (f) => f.edgeIds?.includes(targetId)
481
+ : (f) => f.id === targetId; // flow
482
+ const { nodeIds, edgeIds, dataClasses } = _flowRestrictedAffectedSet(graph, matchesFlow);
483
+ for (const id of nodeIds) if (realNodeIds.has(id)) affectedNodeIds.add(id);
484
+ for (const id of edgeIds) affectedEdgeIds.add(id);
485
+ // N3 (final-review re-review): an edge: target that exists in
486
+ // graph.edges but appears in NO flow's own edgeIds (real for the
487
+ // real graph — an edge with zero flows using it is a legitimate
488
+ // graph shape, e.g. a dead-end connection no data currently
489
+ // traverses) must never come back fully empty — "this edge is
490
+ // compromised" means, at minimum, that edge and its own two
491
+ // endpoints are affected, flow or no flow. A nonexistent edge id
492
+ // still correctly degrades to fully empty (realNodeIds/the edge
493
+ // lookup below both filter it out).
494
+ if (targetKind === 'edge') {
495
+ const edge = (graph.edges ?? []).find((e) => e.id === targetId);
496
+ if (edge) {
497
+ affectedEdgeIds.add(edge.id);
498
+ if (realNodeIds.has(edge.from)) affectedNodeIds.add(edge.from);
499
+ if (realNodeIds.has(edge.to)) affectedNodeIds.add(edge.to);
500
+ }
501
+ }
502
+ affectedDataClasses = [...dataClasses].sort();
503
+ }
504
+
505
+ const graphDigest = (0,export_json.computeGraphDigest)(graph);
506
+ const generatedAt = opts.generatedAt ?? new Date().toISOString();
507
+
508
+ return {
509
+ id: (0,ids/* impactAssessmentId */.Oy)({ graphId: graph.graphId, graphDigest, targetId }, [generatedAt]),
510
+ version: impact_assessment/* IMPACT_VERSION */.qc,
511
+ graphId: graph.graphId,
512
+ graphDigest,
513
+ targetId,
514
+ targetKind,
515
+ scope: 'possible',
516
+ traceKind: targetKind === 'node' ? 'topology_reachable' : 'flow_restricted',
517
+ affectedNodeIds: [...affectedNodeIds].sort(),
518
+ affectedEdgeIds: [...affectedEdgeIds].sort(),
519
+ affectedDataClasses,
520
+ affectedRecipientProfileIds: _affectedRecipientProfileIds(graph, affectedNodeIds),
521
+ coverageLimitations: [
522
+ ..._coverageLimitations(graph),
523
+ ..._affectedCoverageLimitations(graph, affectedNodeIds, affectedEdgeIds),
524
+ ],
525
+ generatedAt,
526
+ };
527
+ }
528
+
529
+
530
+ /***/ })
531
+
532
+ };