@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
package/CHANGELOG.md CHANGED
@@ -9,6 +9,43 @@
9
9
  > make the history less accurate, not more.
10
10
 
11
11
 
12
+ ## 0.186.1 - Git history annotation blames once per file, which restores scan speed (fix)
13
+
14
+ 0.186.0 was tagged but never published: its release workflow stopped at the time-to-first-finding gate (15 to 18 s against a 13 s limit), so the first npm release carrying that work is 0.186.1. Nothing in 0.186.0's content changed apart from the fix below.
15
+
16
+ - **Why it got slower.** Findings that used to carry no line number now carry one, so the git history step, which asks git who introduced each finding, ran for about twice as many findings. It ran one `git blame` per finding and one `git show` per commit, about 14 ms each. Measured on this repository's own source: 7.9 s on 0.185.0, 10.9 s on 0.186.0.
17
+ - **The fix.** One `git blame` per file for all of its lines and one `git log --no-walk` for all commit messages. A file whose batch fails is blamed line by line, so a bad line number cannot lose the others. The annotations are the same; `test/git-history-batch.test.js` checks the batched answers against per-line `git blame`. Measured after the change: 8.3 s locally.
18
+ - **Also in this release.** Two test fixes found by hosted CI: the artifact CLI tests now warm the OSV cache once, because a cold runner has none, and `owned-build/6` accepts a watchdog stop on a slow runner.
19
+
20
+ ## 0.186.0 - The graph-precision capabilities are reachable from the CLI, a z3 backend checks path feasibility, and demand queries and incremental analysis got faster (Preview)
21
+
22
+ Everything new is Preview, opt-in and off by default. A default scan's output is unchanged by the new commands and by the solver. No GA capability changed its contract.
23
+
24
+ - **Reachable from the CLI.** `dataflow view`, `dataflow contracts`, `dataflow sink-query`, `dataflow plan-cuts`, `witness build` and `witness verify` expose the typed graph views, boundary contracts, demand reverse taint, min-cut control planner and signed witness bundles. Every report carries a completion status (`complete`, `partial`, `cancelled`, `unavailable`, `invalid`) and `negativeClaimAllowed`; "no modeled path" is never reported as "no vulnerability". `witness build` exits 1 and writes nothing when no modeled path exists. The graph views other than the dataflow view and a tool in the MCP server remain library-only.
25
+ - **A real z3 feasibility backend (`AGENTIC_SECURITY_LINEAGE_SMT=z3`).** Path conditions are translated to SMT-LIB and checked with a per-query timeout and memory cap. Only a proven `unsat` over a verified encoding can reduce a path's exploitability; a timeout, an unknown result, a missing solver or a condition the encoding cannot express changes nothing. The synchronous graph build does not call it: it is reached through the async path-evidence entry points. NaN and loose-equality coercion are not modeled.
26
+ - **Demand queries are faster.** A query with the index built per query is 23.4x faster than full analysis on this repository (was 1.9x), and 3.7x averaged over a 30-query sample that includes fallbacks. Instance-field reads in JS and TS no longer force a full fallback; 75 of 982 sink sites still use the full path, all at the caller-depth limit. Findings equal full analysis on all 982 sites. Call-graph name resolution got a name index that also speeds up the cold engine.
27
+ - **Incremental analysis is faster.** A per-file IR cache (JS and TS, under `AGENTIC_SECURITY_INCREMENTAL`) removes re-parsing. End to end it is now 4.3x on `scanner/src`; on the two synthetic projects it is 2.5x and 2.4x, still short of the 3x target. Projects above about 5,000 functions still run cold at the default summary budget.
28
+ - **Fewer false positives on the labeled samples.** Flagged sites that were true rose from 5 of 25 to 5 of 8 for JavaScript and from 6 of 17 to 6 of 8 for Nix, with recall unchanged (5 of 5 and 6 of 6). Part of the JavaScript gain is a measurement correction: line-less findings were credited to every same-class label in their file. The rules are general (a literal origin prefix on a redirect, request-input-only reflection, module-load blocking calls, confined file serving, bounded Nix option types) and each has a metamorphic test; the held-out split was not independent, which the benchmark summary now says. The multi-service fixture still finds 3 of 8: the misses need the cross-service join in a normal scan and a queue-consumer source model.
29
+ - **The state-machine heuristic is narrowed.** A `.status` write is compared only with status sets declared in the same file, and test files are skipped. Before, the first set found anywhere in a project was applied to every write.
30
+ - **Fixed.** The lineage deadline tests no longer assume which of two valid cancellations wins on a slow runner, and the regression report no longer embeds the exact bundle size, so a rebuild cannot make it stale. `@babel/core` and `z3-solver` moved to their latest releases and `@azure/msal-node` is pinned past an advisory in the extension's dev tooling.
31
+ - **Self-scan baseline.** The scanner's own source falls to 688 findings; the only increases are three low advisories in the two new files that read files from disk.
32
+ - **Not claimed.** Reachability, satisfiability and runtime observation are not exploit proof. A signature on a witness bundle proves integrity, not that the transfer models are right.
33
+
34
+ ## 0.185.0 - Graph analysis says when it did not finish, and gains recursion handling, call-site correlation, demand queries, incremental invalidation, boundary contracts, control planning and replayable witnesses (Preview)
35
+
36
+ All new behavior is Preview and is off by default or purely additive. No GA capability changed its contract.
37
+
38
+ - **A graph analysis now says whether it finished.** The field analysis, the call-graph driver, source seeding and reverse selection return a completion record (`complete`, `partial` or `cancelled`) with reasons. A stopped analysis, a coarser fallback context, a construction limit or a cancellation shows in `graph.scanHealth`, `coverage.analysisCompletion`, the limitations list, the signed export and scan health, even when zero flows were projected. A query that did not finish never says "no path" (`negativeClaimAllowed: false`). Invalid query budgets (NaN, Infinity, negative, fractional, over the bound) return `status: "invalid"`. `AGENTIC_SECURITY_LINEAGE_DEADLINE_MS` and the `signal` and `deadline` options cancel a build; from the bundle the cooperative deadline applies and the watchdog for a worker that never stops is source-only.
39
+ - **Recursion.** The call graph is condensed into strongly connected components, callees are solved first, and recursive groups iterate to a fixed point or report `partial`. Reconstruction walks one shared path, so a 20,000-hop chain went from 8.9 s to 22 ms and a 400,000-hop chain completes without a stack overflow.
40
+ - **Call-site correlation (`correlationVersion: 2`).** Value versions and invocation identity keep two calls to one helper, sibling fields and overwritten values apart, with balanced call and return witnesses. Version 1 output is unchanged.
41
+ - **Demand-driven reverse queries (`AGENTIC_SECURITY_DEMAND=1`).** A selector narrows analysis to the functions that can feed one sink argument and the forward engine corroborates. Parity with full analysis holds on 21 programs. The 2x target was missed on this repository's own source when the index is built per query (1.8x); with a warm index a query is about 277x faster. 10 of 30 own-tree queries fell back to full analysis.
42
+ - **Path predicates and control credit (`pathEvidence: true`).** Branch conditions travel with each witness. A control earns credit only for its own sink family and value version, and an all-path claim is refused on any truncated sample.
43
+ - **Dependency-complete incremental analysis (`AGENTIC_SECURITY_INCREMENTAL=1`).** Cached summaries are invalidated through the reverse-dependency closure, including deleted and renamed functions. Corrupt or interrupted caches fall back to a cold scan and say why. A key bug is fixed: summaries for qualified names (those containing `::`) were persisted under the file path, so reuse was effectively inert. The deep stage meets 3x on every project measured; end to end the median is 1.8x to 2.9x, and projects of about 5,000 functions or more run cold at the default summary budget.
44
+ - **Boundary contracts, graph views, control planning and witness bundles.** HTTP, queue and storage contracts seed a consumer's parameter or record field (library entry point). Read-only graph views join findings, call sites, witnesses and flows. `dataflow plan-cuts` computes minimum cuts over the modeled flow network. Signed witness bundles replay offline, with four evidence statuses that are never merged. Witness replay succeeded on 64 of 64 bundles built from real hop streams, and all four tamper kinds tried were rejected 64 of 64.
45
+ - **Measured, with the limits stated.** `bench/dag/` pins six real projects and a labeled multi-service fixture. Precision on the labeled JS and Nix samples is low (5 of 25 and 6 of 17 flagged sites were true), and the fixture finds 3 of 8 labeled positives. Impossible call and return witnesses: 6 of 65 under version 1 and 0 of 64 under version 2 on the fixtures. Labels were written after seeing scanner output and are not independent.
46
+ - **Not claimed.** Reachability, satisfiability and runtime observation are not exploit proof. A signature on a witness bundle proves integrity, not that the transfer models are right. No real SMT solver ships. Boundary contracts, witness building, graph views and demand queries are not wired into a normal scan.
47
+ - **Self-scan baseline.** The scanner's own source gained 18 findings from existing heuristics (14 from the state-machine heuristic, which applies the first declared status set in a project to every `.status` write). The baseline was re-recorded with that reason. Narrowing that heuristic is a follow-up.
48
+
12
49
  ## 0.184.0 - Runtime corroboration reaches GA on real exports, dependency and taint findings get contracts, and a scan says when it could not consult advisories
13
50
 
14
51
  Runtime corroboration is the first Observe capability to earn GA. The other promotions of the programme's remaining items are contracts and corrections, not new maturity labels, and the ones that stay held say why.
@@ -203,6 +203,25 @@ Commands:
203
203
  --filter <path.json> {nodeIds,edgeIds} to scope the export (no-op + warning for csv/coverage)
204
204
  --audience board|ciso|privacy|compliance|regulator|technical
205
205
  (default: technical; briefing only — wording/verbosity, never facts)
206
+ dataflow plan-cuts [path] --candidates <file.json> --output <file> [--query <file.json>] [--format json|markdown]
207
+ Plan a minimum-cost, hypothetical control placement (graph cut) over the
208
+ already-scanned lineage graph. Never verifies a remediation.
209
+ The plan carries a completion contract (never a negative claim).
210
+ dataflow view [path] [--node <id> [--direction in|out|both] [--relations a,b] [--origins a,b]] [--output <file>]
211
+ Opt-in typed graph view over the already-scanned lineage graph: identity,
212
+ completion, SCC condensation of the data-flow relation, or one node's neighbours.
213
+ dataflow contracts [path] [--contracts <file>] [--output <file>]
214
+ Opt-in boundary contracts: bind declared HTTP/queue/storage edges to parsed
215
+ consumer sites and seed taint there. A declaration is a modeled input only.
216
+ dataflow sink-query [path] (--list | --file <rel> --line <n>) [--mode demand|full|compare] [--arg-index <n>]
217
+ [--sink-id <id>] [--access-path <p>] [--rule-family <f>] [--max-work <n>]
218
+ [--max-caller-depth <n>] [--on-limit full|partial] [--output <file>]
219
+ Opt-in demand-driven reverse taint for one sink site. Verdicts: flow-found,
220
+ no-flow-in-modeled-scope, unknown. There is no "safe".
221
+ witness build [path] --out <dir> [--sink <id>] | --list-sinks
222
+ witness verify <bundle.json> [--public-key <path>] [--artifacts <dir> --root <project>] [--json]
223
+ Opt-in signed, replayable path witness bundles (Ed25519, same key as attest).
224
+ A valid signature proves integrity only, never that the finding is real.
206
225
  dataflow scenario apply [path] --operations <file.json> --output <file> [--format json|markdown]
207
226
  Simulate a hypothetical architecture change against the
208
227
  already-scanned lineage graph — never mutates the real scan.
@@ -3385,6 +3404,34 @@ async function cmdVerifyAttestation(args) {
3385
3404
  return 0;
3386
3405
  }
3387
3406
 
3407
+ // DAG-10: a replayable path witness (schema: agentic-security/path-witness@1). The signature alone proves
3408
+ // integrity; `--witness-artifacts <file.json>` ({graph, provenance, sources?, config?}) additionally replays
3409
+ // the recorded path offline against the artifacts it names. Building a witness is a module API
3410
+ // (posture/witness-bundle.js buildWitnessBundle): the hop stream it needs is not persisted by a scan.
3411
+ const { WITNESS_SCHEMA, verifyWitness } = await import('../src/posture/witness-bundle.js');
3412
+ if (bundle.schema === WITNESS_SCHEMA) {
3413
+ let artifacts = null;
3414
+ if (args.flags['witness-artifacts']) {
3415
+ try { artifacts = JSON.parse(fs.readFileSync(path.resolve(args.flags['witness-artifacts']), 'utf8')); }
3416
+ catch (e) { console.error(`Could not read witness artifacts: ${e.message}`); return 2; }
3417
+ }
3418
+ const w = verifyWitness(bundle, publicKeyPem, artifacts);
3419
+ if (!w.signature.ok) { console.error(`✗ INVALID — ${w.signature.reason}`); return 1; }
3420
+ if (w.replay.performed && !w.replay.ok) {
3421
+ console.error('✗ INVALID — the signature holds but replay against the supplied artifacts failed:');
3422
+ for (const x of w.replay.failures) console.error(` - ${x.code}: ${x.detail}`);
3423
+ return 1;
3424
+ }
3425
+ console.log(w.replay.performed
3426
+ ? '✓ VALID — signature holds and the recorded path replays against the supplied artifacts.'
3427
+ : '✓ SIGNATURE VALID (integrity only) — replay was not attempted; pass --witness-artifacts <file.json> to replay.');
3428
+ console.log(` completion: ${bundle.completion?.overall} evidence: ${Object.entries(bundle.evidence || {}).map(([k, v]) => `${k}=${v.status}`).join(' ')}`);
3429
+ console.log(` proves: ${w.statement.proves}`);
3430
+ console.log(` does NOT prove: ${w.statement.doesNotProve}`);
3431
+ console.log(` trust basis: ${w.signature.trustBasis}`);
3432
+ return 0;
3433
+ }
3434
+
3388
3435
  const r = verifyEvidenceBundle(bundle, publicKeyPem);
3389
3436
  if (!r.ok) {
3390
3437
  console.error(`✗ INVALID — ${r.reason}`);
@@ -4083,6 +4130,16 @@ async function cmdBoundaries(args) {
4083
4130
  // itself closes (idle-timeout auto-stop, or an external kill) — matching
4084
4131
  // every other cmdX(args) function's "resolves to an exit code" contract
4085
4132
  // while keeping the process alive for as long as the server is listening.
4133
+ // DAG-07 R07.2: show the graph's completion state wherever a graph is
4134
+ // loaded for display or export. Silent for a complete graph; for anything
4135
+ // else it says so on stderr, including when zero flows were projected.
4136
+ async function _printGraphCompletion(graph, who) {
4137
+ try {
4138
+ const { completionOfGraph, describeCompletion } = await import('../src/lineage/completion.js');
4139
+ for (const line of describeCompletion(completionOfGraph(graph))) process.stderr.write(`${who}: ${line}\n`);
4140
+ } catch { /* disclosure must never break the command */ }
4141
+ }
4142
+
4086
4143
  async function cmdExplore(args) {
4087
4144
  // `explore . --runtime`: the runtime views are one static, script-free page, not a route on the hardened local server.
4088
4145
  if (args.flags.runtime) return cmdRuntimeExplorer(args, args._[1] || '.');
@@ -4095,6 +4152,7 @@ async function cmdExplore(args) {
4095
4152
  process.stderr.write(`agentic-security explore: ${loaded.message}\n`);
4096
4153
  return 1;
4097
4154
  }
4155
+ await _printGraphCompletion(loaded.graph, 'agentic-security explore');
4098
4156
 
4099
4157
  let port = 0;
4100
4158
  if (args.flags.port !== undefined) {
@@ -4355,6 +4413,7 @@ async function cmdDataflowExport(args) {
4355
4413
  return 1;
4356
4414
  }
4357
4415
  const graph = loaded.graph;
4416
+ await _printGraphCompletion(graph, 'agentic-security dataflow export');
4358
4417
  // Task-3 review finding (non-blocking, fixed): emitGraphDpiaArtifact's
4359
4418
  // own opts.generatedAt fallback is `new Date()` — omitting it here made
4360
4419
  // dpia/ropa the only two formats whose content reflects EXPORT-time
@@ -5094,6 +5153,52 @@ async function cmdDataflowScenarioApply(args) {
5094
5153
  return 0;
5095
5154
  }
5096
5155
 
5156
+ // agentic-security dataflow plan-cuts [path] --candidates <file.json>
5157
+ // --output <file> [--query <file.json>] [--format json|markdown] — DAG-09.
5158
+ // Plans a minimum-cost control placement over the already-scanned,
5159
+ // already-signed graph. HYPOTHETICAL: never writes remediation state or
5160
+ // the scan artifact. Exit codes: 0 success, 1 graph-load failure, 2
5161
+ // argument problem.
5162
+ async function cmdDataflowPlanCuts(args) {
5163
+ const targetAbs = path.resolve(args._[2] || '.');
5164
+ const candFlag = args.flags.candidates;
5165
+ const outputPath = args.flags.output;
5166
+ const format = args.flags.format || 'json';
5167
+ if (!candFlag || typeof candFlag !== 'string') { process.stderr.write('agentic-security dataflow plan-cuts: --candidates <file> is required.\n'); return 2; }
5168
+ if (!outputPath || typeof outputPath !== 'string') { process.stderr.write('agentic-security dataflow plan-cuts: --output <file> is required.\n'); return 2; }
5169
+ if (format !== 'json' && format !== 'markdown') { process.stderr.write('agentic-security dataflow plan-cuts: --format must be one of json|markdown.\n'); return 2; }
5170
+ let candidates; let query = {};
5171
+ try {
5172
+ candidates = JSON.parse(await fsp.readFile(path.resolve(candFlag), 'utf8'));
5173
+ if (typeof args.flags.query === 'string') query = JSON.parse(await fsp.readFile(path.resolve(args.flags.query), 'utf8'));
5174
+ } catch (e) { process.stderr.write(`agentic-security dataflow plan-cuts: could not read/parse input: ${e.message}\n`); return 2; }
5175
+ if (!Array.isArray(candidates)) { process.stderr.write('agentic-security dataflow plan-cuts: --candidates must be a JSON array.\n'); return 2; }
5176
+ const { loadSignedGraph } = await import('../src/server/graph-loader.js');
5177
+ const loaded = loadSignedGraph(targetAbs);
5178
+ if (!loaded.ok) { process.stderr.write(`agentic-security dataflow plan-cuts: ${loaded.message}\n`); return 1; }
5179
+ const { planCuts } = await import('../src/lineage/cut-planner.js');
5180
+ const plan = planCuts(loaded.graph, query, candidates);
5181
+ // Completion contract: a plan is `complete` only for a finite cut over a graph known complete; never a negative claim.
5182
+ plan.contract = (await import('../src/posture/dag-commands.js')).planContract(plan);
5183
+ let data;
5184
+ if (format === 'json') data = JSON.stringify(plan, null, 2);
5185
+ else {
5186
+ const lines = ['# Control placement plan (hypothetical)', '', `Status: ${plan.status}. Total coverage: ${plan.completeness.totalCoverage}. Not verified.`, '', '## Proposed controls', ''];
5187
+ for (const c of plan.cut.controls) lines.push(`- ${c.location.type} \`${c.location.id}\` (${c.semantics}, owner ${c.owner}, cost ${c.cost})`);
5188
+ lines.push('', '## Residual bypass paths', '');
5189
+ for (const r of plan.validation?.residualPaths ?? []) lines.push(`- to \`${r.sink}\` via ${r.nodes.map((n) => `\`${n}\``).join(' -> ')}`);
5190
+ lines.push('', '## Limitations', '');
5191
+ for (const l of plan.limitations) lines.push(`- ${l}`);
5192
+ lines.push('', `Completion: ${plan.contract.status}. A negative claim is never allowed for a hypothetical plan.`);
5193
+ data = lines.join('\n') + '\n';
5194
+ }
5195
+ try {
5196
+ await fsp.mkdir(path.dirname(path.resolve(outputPath)), { recursive: true });
5197
+ await fsp.writeFile(path.resolve(outputPath), data);
5198
+ } catch (e) { process.stderr.write(`agentic-security dataflow plan-cuts: could not write --output: ${e.message}\n`); return 2; }
5199
+ return 0;
5200
+ }
5201
+
5097
5202
  // agentic-security dataflow impact assess [path] --target <canonical-id>
5098
5203
  // --output <file> [--format json|markdown] — M5 deliverable #4
5099
5204
  // (FR-507). Loads the already-scanned, already-signed graph via
@@ -7926,6 +8031,7 @@ async function main() {
7926
8031
  }
7927
8032
  case 'boundaries': process.exit(await cmdBoundaries(args));
7928
8033
  case 'explore': process.exit(await cmdExplore(args));
8034
+ case 'witness': process.exit(await (await import('../src/posture/dag-commands.js')).cmdWitness(args));
7929
8035
  case 'dataflow': {
7930
8036
  const sub = args._[1];
7931
8037
  if (sub === 'export') { process.exit(await cmdDataflowExport(args)); }
@@ -7966,6 +8072,10 @@ async function main() {
7966
8072
  }
7967
8073
  break;
7968
8074
  }
8075
+ else if (sub === 'plan-cuts') { process.exit(await cmdDataflowPlanCuts(args)); }
8076
+ else if (sub === 'view') { process.exit(await (await import('../src/posture/dag-commands.js')).cmdDataflowView(args)); }
8077
+ else if (sub === 'contracts') { process.exit(await (await import('../src/posture/dag-commands.js')).cmdDataflowContracts(args)); }
8078
+ else if (sub === 'sink-query') { process.exit(await (await import('../src/posture/dag-commands.js')).cmdDataflowSinkQuery(args)); }
7969
8079
  else if (sub === 'impact') {
7970
8080
  const impactSub = args._[2];
7971
8081
  if (impactSub === 'assess') { process.exit(await cmdDataflowImpactAssess(args)); }
@@ -7986,7 +8096,7 @@ async function main() {
7986
8096
  break;
7987
8097
  }
7988
8098
  else if (sub === 'twin') { process.exit(await cmdDataflowTwin(args)); }
7989
- process.stderr.write(`agentic-security dataflow: unknown subcommand "${sub}" — only "export", "diff", "watch", "scenario", "impact", "observations", and "twin" are supported.\n`);
8099
+ process.stderr.write(`agentic-security dataflow: unknown subcommand "${sub}" — only "export", "diff", "watch", "scenario", "plan-cuts", "view", "contracts", "sink-query", "impact", "observations", and "twin" are supported.\n`);
7990
8100
  process.exit(2);
7991
8101
  }
7992
8102
  case 'governance': {