swipium 1.5.0 → 2.0.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 (498) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +173 -280
  3. package/THREAT_MODEL.md +223 -47
  4. package/dist/appMap/automationLink.js +30 -27
  5. package/dist/appMap/automationLink.js.map +1 -1
  6. package/dist/appMap/build.js +47 -21
  7. package/dist/appMap/build.js.map +1 -1
  8. package/dist/appMap/codeIndex.js +2 -2
  9. package/dist/appMap/codeIndex.js.map +1 -1
  10. package/dist/appMap/featureIndex.js +3 -3
  11. package/dist/appMap/featureIndex.js.map +1 -1
  12. package/dist/appMap/featureModel.js +2 -2
  13. package/dist/appMap/featureModel.js.map +1 -1
  14. package/dist/appMap/firstRunApply.js +2 -2
  15. package/dist/appMap/firstRunApply.js.map +1 -1
  16. package/dist/appMap/issues.js +10 -10
  17. package/dist/appMap/issues.js.map +1 -1
  18. package/dist/appMap/migrations.js +5 -5
  19. package/dist/appMap/migrations.js.map +1 -1
  20. package/dist/appMap/prelaunch.js +2 -2
  21. package/dist/appMap/prelaunch.js.map +1 -1
  22. package/dist/appMap/projectRegistry.js +77 -11
  23. package/dist/appMap/projectRegistry.js.map +1 -1
  24. package/dist/appMap/provenance.js +2 -2
  25. package/dist/appMap/provenance.js.map +1 -1
  26. package/dist/appMap/query.js +2 -2
  27. package/dist/appMap/query.js.map +1 -1
  28. package/dist/appMap/runtimeMerge.js +3 -3
  29. package/dist/appMap/runtimeMerge.js.map +1 -1
  30. package/dist/appMap/schema.js +1 -1
  31. package/dist/appMap/schema.js.map +1 -1
  32. package/dist/appMap/screenMatch.js +1 -1
  33. package/dist/appMap/screenMatch.js.map +1 -1
  34. package/dist/appMap/staticScan.js +17 -17
  35. package/dist/appMap/staticScan.js.map +1 -1
  36. package/dist/appMap/store.js +55 -7
  37. package/dist/appMap/store.js.map +1 -1
  38. package/dist/appMap/tsAstScan.js +3 -3
  39. package/dist/appMap/tsAstScan.js.map +1 -1
  40. package/dist/artifacts/bundletool.js +5 -5
  41. package/dist/artifacts/bundletool.js.map +1 -1
  42. package/dist/artifacts/resolve.js +10 -10
  43. package/dist/artifacts/resolve.js.map +1 -1
  44. package/dist/automation/capabilities.js +3 -3
  45. package/dist/automation/capabilities.js.map +1 -1
  46. package/dist/automation/gestures.js +1 -1
  47. package/dist/automation/gestures.js.map +1 -1
  48. package/dist/automation/plan.js +7 -7
  49. package/dist/automation/plan.js.map +1 -1
  50. package/dist/automation/report.js +3 -3
  51. package/dist/automation/report.js.map +1 -1
  52. package/dist/automation/selectors.js +14 -25
  53. package/dist/automation/selectors.js.map +1 -1
  54. package/dist/automation/types.js +1 -1
  55. package/dist/automation/types.js.map +1 -1
  56. package/dist/automation/waits.js +5 -5
  57. package/dist/automation/waits.js.map +1 -1
  58. package/dist/automation/webview.js +2 -2
  59. package/dist/automation/webview.js.map +1 -1
  60. package/dist/automationGen/appiumModel.js +13 -7
  61. package/dist/automationGen/appiumModel.js.map +1 -1
  62. package/dist/automationGen/ciEmitter.js +5 -5
  63. package/dist/automationGen/ciEmitter.js.map +1 -1
  64. package/dist/automationGen/identifiers.js +224 -0
  65. package/dist/automationGen/identifiers.js.map +1 -0
  66. package/dist/automationGen/jsEmitter.js +265 -106
  67. package/dist/automationGen/jsEmitter.js.map +1 -1
  68. package/dist/automationGen/packagePatch.js +2 -2
  69. package/dist/automationGen/packagePatch.js.map +1 -1
  70. package/dist/automationGen/platformResolve.js +36 -0
  71. package/dist/automationGen/platformResolve.js.map +1 -0
  72. package/dist/automationGen/projectProfile.js +71 -26
  73. package/dist/automationGen/projectProfile.js.map +1 -1
  74. package/dist/automationGen/pythonEmitter.js +276 -105
  75. package/dist/automationGen/pythonEmitter.js.map +1 -1
  76. package/dist/automationGen/readmeEmitter.js +7 -6
  77. package/dist/automationGen/readmeEmitter.js.map +1 -1
  78. package/dist/{tools/automationGenerate.js → automationGen/run.js} +72 -113
  79. package/dist/automationGen/run.js.map +1 -0
  80. package/dist/automationGen/suitePlan.js +6 -4
  81. package/dist/automationGen/suitePlan.js.map +1 -1
  82. package/dist/automationGen/validation.js +20 -8
  83. package/dist/automationGen/validation.js.map +1 -1
  84. package/dist/build/parseBuildLog.js +2 -2
  85. package/dist/build/parseBuildLog.js.map +1 -1
  86. package/dist/build/plan.js +7 -7
  87. package/dist/build/plan.js.map +1 -1
  88. package/dist/ci/preflight.js +13 -4
  89. package/dist/ci/preflight.js.map +1 -1
  90. package/dist/cli/gc.js +74 -0
  91. package/dist/cli/gc.js.map +1 -0
  92. package/dist/cli/init.js +217 -26
  93. package/dist/cli/init.js.map +1 -1
  94. package/dist/cli/main.js +50 -0
  95. package/dist/cli/main.js.map +1 -0
  96. package/dist/cli/report.js +218 -0
  97. package/dist/cli/report.js.map +1 -0
  98. package/dist/cli/scan.js +35 -14
  99. package/dist/cli/scan.js.map +1 -1
  100. package/dist/cli/suite.js +6 -6
  101. package/dist/cli/suite.js.map +1 -1
  102. package/dist/cli/verify.js +7 -7
  103. package/dist/cli/verify.js.map +1 -1
  104. package/dist/consent/consent.js +162 -5
  105. package/dist/consent/consent.js.map +1 -1
  106. package/dist/context/detect.js +45 -14
  107. package/dist/context/detect.js.map +1 -1
  108. package/dist/context/findApps.js +6 -6
  109. package/dist/context/findApps.js.map +1 -1
  110. package/dist/context/projectRoot.js +174 -19
  111. package/dist/context/projectRoot.js.map +1 -1
  112. package/dist/context/scan.js +1 -1
  113. package/dist/context/scan.js.map +1 -1
  114. package/dist/core/capabilityGroups.js +90 -0
  115. package/dist/core/capabilityGroups.js.map +1 -0
  116. package/dist/core/target.js +3 -3
  117. package/dist/core/target.js.map +1 -1
  118. package/dist/core/targetPlan.js +65 -62
  119. package/dist/core/targetPlan.js.map +1 -1
  120. package/dist/drivers/DirectDriver.js +300 -36
  121. package/dist/drivers/DirectDriver.js.map +1 -1
  122. package/dist/drivers/SimctlDriver.js +120 -7
  123. package/dist/drivers/SimctlDriver.js.map +1 -1
  124. package/dist/drivers/WdaDriver.js +350 -50
  125. package/dist/drivers/WdaDriver.js.map +1 -1
  126. package/dist/explore/candidates.js +2 -2
  127. package/dist/explore/candidates.js.map +1 -1
  128. package/dist/explore/graph.js +5 -5
  129. package/dist/explore/graph.js.map +1 -1
  130. package/dist/explore/policy.js +5 -5
  131. package/dist/explore/policy.js.map +1 -1
  132. package/dist/explore/runner.js +301 -284
  133. package/dist/explore/runner.js.map +1 -1
  134. package/dist/explore/signatures.js +1 -1
  135. package/dist/featureTesting/executionBootstrap.js +29 -30
  136. package/dist/featureTesting/executionBootstrap.js.map +1 -1
  137. package/dist/featureTesting/featureMap.js +2 -2
  138. package/dist/featureTesting/featureMap.js.map +1 -1
  139. package/dist/featureTesting/featureScope.js +12 -12
  140. package/dist/featureTesting/featureScope.js.map +1 -1
  141. package/dist/featureTesting/mapFeatureScope.js +7 -7
  142. package/dist/featureTesting/mapFeatureScope.js.map +1 -1
  143. package/dist/featureTesting/objectiveModel.js +10 -10
  144. package/dist/featureTesting/objectiveModel.js.map +1 -1
  145. package/dist/featureTesting/resultMerge.js +6 -6
  146. package/dist/featureTesting/resultMerge.js.map +1 -1
  147. package/dist/featureTesting/sources.js +4 -4
  148. package/dist/featureTesting/sources.js.map +1 -1
  149. package/dist/featureTesting/suiteBridge.js +1 -1
  150. package/dist/featureTesting/suiteBridge.js.map +1 -1
  151. package/dist/featureTesting/synonyms.js +1 -1
  152. package/dist/featureTesting/synonyms.js.map +1 -1
  153. package/dist/featureTesting/testCaseFactory.js +11 -11
  154. package/dist/featureTesting/testCaseFactory.js.map +1 -1
  155. package/dist/featureTesting/testPlan.js +3 -3
  156. package/dist/featureTesting/testPlan.js.map +1 -1
  157. package/dist/firstRun/authStateMachine.js +1 -1
  158. package/dist/firstRun/authStateMachine.js.map +1 -1
  159. package/dist/firstRun/classifyScreen.js +5 -5
  160. package/dist/firstRun/classifyScreen.js.map +1 -1
  161. package/dist/firstRun/firstRunPlanner.js +11 -11
  162. package/dist/firstRun/firstRunPlanner.js.map +1 -1
  163. package/dist/firstRun/firstRunRunner.js +8 -8
  164. package/dist/firstRun/firstRunRunner.js.map +1 -1
  165. package/dist/firstRun/generatedDataPolicy.js +9 -9
  166. package/dist/firstRun/generatedDataPolicy.js.map +1 -1
  167. package/dist/firstRun/inputPlanner.js +5 -5
  168. package/dist/firstRun/inputPlanner.js.map +1 -1
  169. package/dist/firstRun/onboardingStateMachine.js +1 -1
  170. package/dist/firstRun/onboardingStateMachine.js.map +1 -1
  171. package/dist/firstRun/paywallPolicy.js +3 -3
  172. package/dist/firstRun/paywallPolicy.js.map +1 -1
  173. package/dist/firstRun/types.js +1 -1
  174. package/dist/firstRun/types.js.map +1 -1
  175. package/dist/fixtures/catalog.js +33 -3
  176. package/dist/fixtures/catalog.js.map +1 -1
  177. package/dist/fixtures/load.js +50 -0
  178. package/dist/fixtures/load.js.map +1 -0
  179. package/dist/flows/discover.js +2 -2
  180. package/dist/flows/discover.js.map +1 -1
  181. package/dist/flows/generate.js +20 -10
  182. package/dist/flows/generate.js.map +1 -1
  183. package/dist/flows/pack.js +3 -3
  184. package/dist/flows/pack.js.map +1 -1
  185. package/dist/flows/paths.js +57 -0
  186. package/dist/flows/paths.js.map +1 -0
  187. package/dist/flows/repair.js +111 -23
  188. package/dist/flows/repair.js.map +1 -1
  189. package/dist/flows/run.js +153 -57
  190. package/dist/flows/run.js.map +1 -1
  191. package/dist/flows/schema.js +50 -5
  192. package/dist/flows/schema.js.map +1 -1
  193. package/dist/flows/seedExec.js +4 -4
  194. package/dist/flows/seedExec.js.map +1 -1
  195. package/dist/flows/templates.js +5 -5
  196. package/dist/index.js +50 -8
  197. package/dist/index.js.map +1 -1
  198. package/dist/issues/classify.js +16 -16
  199. package/dist/issues/classify.js.map +1 -1
  200. package/dist/issues/fingerprint.js +24 -4
  201. package/dist/issues/fingerprint.js.map +1 -1
  202. package/dist/issues/index.js +94 -31
  203. package/dist/issues/index.js.map +1 -1
  204. package/dist/issues/metrics.js +4 -4
  205. package/dist/issues/metrics.js.map +1 -1
  206. package/dist/issues/recurrence.js +35 -8
  207. package/dist/issues/recurrence.js.map +1 -1
  208. package/dist/issues/report.js +4 -4
  209. package/dist/issues/report.js.map +1 -1
  210. package/dist/issues/reportBridge.js +4 -4
  211. package/dist/issues/reportBridge.js.map +1 -1
  212. package/dist/issues/schema.js +2 -2
  213. package/dist/issues/schema.js.map +1 -1
  214. package/dist/issues/sourceRevision.js +2 -2
  215. package/dist/issues/sourceRevision.js.map +1 -1
  216. package/dist/issues/store.js +110 -32
  217. package/dist/issues/store.js.map +1 -1
  218. package/dist/lib/abortScope.js +48 -0
  219. package/dist/lib/abortScope.js.map +1 -0
  220. package/dist/lib/android.js +75 -7
  221. package/dist/lib/android.js.map +1 -1
  222. package/dist/lib/coordSpace.js +1 -1
  223. package/dist/lib/coordSpace.js.map +1 -1
  224. package/dist/lib/device.js +32 -7
  225. package/dist/lib/device.js.map +1 -1
  226. package/dist/lib/gestures.js +141 -0
  227. package/dist/lib/gestures.js.map +1 -0
  228. package/dist/lib/gitignore.js +4 -3
  229. package/dist/lib/gitignore.js.map +1 -1
  230. package/dist/lib/image.js +1 -1
  231. package/dist/lib/image.js.map +1 -1
  232. package/dist/lib/lockfile.js +370 -31
  233. package/dist/lib/lockfile.js.map +1 -1
  234. package/dist/lib/needsInput.js +3 -3
  235. package/dist/lib/needsInput.js.map +1 -1
  236. package/dist/lib/png.js +2 -2
  237. package/dist/lib/png.js.map +1 -1
  238. package/dist/lib/redact.js +170 -9
  239. package/dist/lib/redact.js.map +1 -1
  240. package/dist/lib/result.js +79 -10
  241. package/dist/lib/result.js.map +1 -1
  242. package/dist/lib/schemaHash.js +3 -3
  243. package/dist/lib/schemaHash.js.map +1 -1
  244. package/dist/lib/sensitive.js +3 -2
  245. package/dist/lib/sensitive.js.map +1 -1
  246. package/dist/lib/simctl.js +3 -3
  247. package/dist/lib/simctl.js.map +1 -1
  248. package/dist/lib/spawn.js +52 -10
  249. package/dist/lib/spawn.js.map +1 -1
  250. package/dist/lib/toolAnnotations.js +114 -0
  251. package/dist/lib/toolAnnotations.js.map +1 -0
  252. package/dist/lib/wda.js +294 -15
  253. package/dist/lib/wda.js.map +1 -1
  254. package/dist/mobileAudit/checks.js +12 -12
  255. package/dist/mobileAudit/checks.js.map +1 -1
  256. package/dist/mobileAudit/evidence.js +4 -4
  257. package/dist/mobileAudit/evidence.js.map +1 -1
  258. package/dist/mobileAudit/profiles.js +1 -1
  259. package/dist/mobileAudit/profiles.js.map +1 -1
  260. package/dist/mobileAudit/results.js +3 -3
  261. package/dist/mobileAudit/results.js.map +1 -1
  262. package/dist/mobileAudit/runner.js +72 -18
  263. package/dist/mobileAudit/runner.js.map +1 -1
  264. package/dist/oracle/auth.js +1 -1
  265. package/dist/oracle/auth.js.map +1 -1
  266. package/dist/oracle/failures.js +219 -36
  267. package/dist/oracle/failures.js.map +1 -1
  268. package/dist/oracle/health.js +63 -17
  269. package/dist/oracle/health.js.map +1 -1
  270. package/dist/oracle/locator.js +13 -13
  271. package/dist/oracle/locator.js.map +1 -1
  272. package/dist/oracle/record.js +3 -3
  273. package/dist/oracle/record.js.map +1 -1
  274. package/dist/orchestration/envelope.js +2 -2
  275. package/dist/orchestration/envelope.js.map +1 -1
  276. package/dist/orchestration/goal.js +4 -4
  277. package/dist/orchestration/goal.js.map +1 -1
  278. package/dist/orchestration/testThis/execute.js +62 -21
  279. package/dist/orchestration/testThis/execute.js.map +1 -1
  280. package/dist/orchestration/testThis/pipeline.js +119 -30
  281. package/dist/orchestration/testThis/pipeline.js.map +1 -1
  282. package/dist/orchestration/testThis/plan.js +162 -73
  283. package/dist/orchestration/testThis/plan.js.map +1 -1
  284. package/dist/orchestration/testThis/sessionIntent.js +72 -0
  285. package/dist/orchestration/testThis/sessionIntent.js.map +1 -0
  286. package/dist/orchestration/testThis/terminal.js +34 -16
  287. package/dist/orchestration/testThis/terminal.js.map +1 -1
  288. package/dist/orchestration/testThis/types.js +1 -1
  289. package/dist/orchestration/testThis/types.js.map +1 -1
  290. package/dist/plan/plan.js +9 -9
  291. package/dist/plan/plan.js.map +1 -1
  292. package/dist/prompts/index.js +22 -22
  293. package/dist/prompts/index.js.map +1 -1
  294. package/dist/report/export.js +392 -37
  295. package/dist/report/export.js.map +1 -1
  296. package/dist/report/findingsDedupe.js +41 -0
  297. package/dist/report/findingsDedupe.js.map +1 -0
  298. package/dist/report/policy.js +7 -4
  299. package/dist/report/policy.js.map +1 -1
  300. package/dist/report/qaLevel.js +8 -8
  301. package/dist/report/qaLevel.js.map +1 -1
  302. package/dist/report/sarifSources.js +160 -0
  303. package/dist/report/sarifSources.js.map +1 -0
  304. package/dist/report/summary.js +2 -2
  305. package/dist/report/summary.js.map +1 -1
  306. package/dist/report/testCatalog.js +4 -4
  307. package/dist/report/testCatalog.js.map +1 -1
  308. package/dist/report/toolHealth.js +124 -0
  309. package/dist/report/toolHealth.js.map +1 -0
  310. package/dist/server.js +472 -45
  311. package/dist/server.js.map +1 -1
  312. package/dist/services/automationGenerate.js +8 -2
  313. package/dist/services/automationGenerate.js.map +1 -1
  314. package/dist/services/build.js +2 -2
  315. package/dist/services/build.js.map +1 -1
  316. package/dist/{tools → services}/flowGenerate.js +29 -13
  317. package/dist/services/flowGenerate.js.map +1 -0
  318. package/dist/services/preflight.js +4 -4
  319. package/dist/services/preflight.js.map +1 -1
  320. package/dist/services/prepareAndroid.js +46 -8
  321. package/dist/services/prepareAndroid.js.map +1 -1
  322. package/dist/services/prepareIos.js +24 -9
  323. package/dist/services/prepareIos.js.map +1 -1
  324. package/dist/services/report.js +100 -45
  325. package/dist/services/report.js.map +1 -1
  326. package/dist/services/smoke.js +32 -16
  327. package/dist/services/smoke.js.map +1 -1
  328. package/dist/services/suiteGenerate.js +28 -3
  329. package/dist/services/suiteGenerate.js.map +1 -1
  330. package/dist/services/testSuiteKnowledge.js +40 -27
  331. package/dist/services/testSuiteKnowledge.js.map +1 -1
  332. package/dist/session/attach.js +270 -6
  333. package/dist/session/attach.js.map +1 -1
  334. package/dist/session/processRegistry.js +317 -48
  335. package/dist/session/processRegistry.js.map +1 -1
  336. package/dist/session/progress.js +1 -1
  337. package/dist/session/retention.js +201 -0
  338. package/dist/session/retention.js.map +1 -0
  339. package/dist/session/store.js +275 -48
  340. package/dist/session/store.js.map +1 -1
  341. package/dist/snapshot/overlays.js +53 -8
  342. package/dist/snapshot/overlays.js.map +1 -1
  343. package/dist/snapshot/parse.js +28 -4
  344. package/dist/snapshot/parse.js.map +1 -1
  345. package/dist/snapshot/present.js +11 -5
  346. package/dist/snapshot/present.js.map +1 -1
  347. package/dist/snapshot/settle.js +39 -9
  348. package/dist/snapshot/settle.js.map +1 -1
  349. package/dist/state/consent.js +99 -0
  350. package/dist/state/consent.js.map +1 -0
  351. package/dist/state/profile.js +46 -2
  352. package/dist/state/profile.js.map +1 -1
  353. package/dist/suite/compile.js +9 -5
  354. package/dist/suite/compile.js.map +1 -1
  355. package/dist/suite/lint.js +4 -4
  356. package/dist/suite/lint.js.map +1 -1
  357. package/dist/suite/pom.js +50 -41
  358. package/dist/suite/pom.js.map +1 -1
  359. package/dist/suite/secretGuard.js +266 -0
  360. package/dist/suite/secretGuard.js.map +1 -0
  361. package/dist/suite/testcase.js +9 -7
  362. package/dist/suite/testcase.js.map +1 -1
  363. package/dist/testSuite/exporter.js +4 -4
  364. package/dist/testSuite/exporter.js.map +1 -1
  365. package/dist/testSuite/generator.js +13 -9
  366. package/dist/testSuite/generator.js.map +1 -1
  367. package/dist/testSuite/history.js +1 -1
  368. package/dist/testSuite/history.js.map +1 -1
  369. package/dist/testSuite/issueLinks.js +3 -3
  370. package/dist/testSuite/issueLinks.js.map +1 -1
  371. package/dist/testSuite/lint.js +4 -4
  372. package/dist/testSuite/lint.js.map +1 -1
  373. package/dist/testSuite/merge.js +5 -5
  374. package/dist/testSuite/merge.js.map +1 -1
  375. package/dist/testSuite/schema.js +5 -5
  376. package/dist/testSuite/schema.js.map +1 -1
  377. package/dist/testSuite/store.js +3 -3
  378. package/dist/testSuite/store.js.map +1 -1
  379. package/dist/testSuite/traceability.js +2 -2
  380. package/dist/testSuite/traceability.js.map +1 -1
  381. package/dist/tools/act.js +794 -164
  382. package/dist/tools/act.js.map +1 -1
  383. package/dist/tools/agent.js +451 -128
  384. package/dist/tools/agent.js.map +1 -1
  385. package/dist/tools/appControl.js +69 -19
  386. package/dist/tools/appControl.js.map +1 -1
  387. package/dist/tools/appMap.js +188 -130
  388. package/dist/tools/appMap.js.map +1 -1
  389. package/dist/tools/build.js +32 -42
  390. package/dist/tools/build.js.map +1 -1
  391. package/dist/tools/bundletool.js +21 -31
  392. package/dist/tools/bundletool.js.map +1 -1
  393. package/dist/tools/clearOverlay.js +55 -12
  394. package/dist/tools/clearOverlay.js.map +1 -1
  395. package/dist/tools/device.js +76 -24
  396. package/dist/tools/device.js.map +1 -1
  397. package/dist/tools/doctor.js +68 -28
  398. package/dist/tools/doctor.js.map +1 -1
  399. package/dist/tools/explore.js +41 -47
  400. package/dist/tools/explore.js.map +1 -1
  401. package/dist/tools/featureTesting.js +31 -48
  402. package/dist/tools/featureTesting.js.map +1 -1
  403. package/dist/tools/firstRun.js +34 -41
  404. package/dist/tools/firstRun.js.map +1 -1
  405. package/dist/tools/flow.js +183 -103
  406. package/dist/tools/flow.js.map +1 -1
  407. package/dist/tools/flowRepair.js +25 -13
  408. package/dist/tools/flowRepair.js.map +1 -1
  409. package/dist/tools/generate.js +52 -89
  410. package/dist/tools/generate.js.map +1 -1
  411. package/dist/tools/getArtifact.js +16 -5
  412. package/dist/tools/getArtifact.js.map +1 -1
  413. package/dist/tools/health.js +15 -12
  414. package/dist/tools/health.js.map +1 -1
  415. package/dist/tools/ios.js +45 -223
  416. package/dist/tools/ios.js.map +1 -1
  417. package/dist/tools/issues.js +251 -35
  418. package/dist/tools/issues.js.map +1 -1
  419. package/dist/tools/jobs.js +41 -16
  420. package/dist/tools/jobs.js.map +1 -1
  421. package/dist/tools/metro.js +36 -35
  422. package/dist/tools/metro.js.map +1 -1
  423. package/dist/tools/mobileAudit.js +50 -45
  424. package/dist/tools/mobileAudit.js.map +1 -1
  425. package/dist/tools/network.js +31 -14
  426. package/dist/tools/network.js.map +1 -1
  427. package/dist/tools/note.js +26 -26
  428. package/dist/tools/note.js.map +1 -1
  429. package/dist/tools/prepareIosTarget.js +10 -16
  430. package/dist/tools/prepareIosTarget.js.map +1 -1
  431. package/dist/tools/prepareTarget.js +117 -50
  432. package/dist/tools/prepareTarget.js.map +1 -1
  433. package/dist/tools/report.js +11 -22
  434. package/dist/tools/report.js.map +1 -1
  435. package/dist/tools/resolveArtifact.js +11 -15
  436. package/dist/tools/resolveArtifact.js.map +1 -1
  437. package/dist/tools/resolveTarget.js +78 -24
  438. package/dist/tools/resolveTarget.js.map +1 -1
  439. package/dist/tools/screenRecord.js +23 -21
  440. package/dist/tools/screenRecord.js.map +1 -1
  441. package/dist/tools/screenshot.js +23 -16
  442. package/dist/tools/screenshot.js.map +1 -1
  443. package/dist/tools/smoke.js +20 -16
  444. package/dist/tools/smoke.js.map +1 -1
  445. package/dist/tools/snapshot.js +155 -118
  446. package/dist/tools/snapshot.js.map +1 -1
  447. package/dist/tools/startSession.js +104 -116
  448. package/dist/tools/startSession.js.map +1 -1
  449. package/dist/tools/suite.js +72 -43
  450. package/dist/tools/suite.js.map +1 -1
  451. package/dist/tools/testSuite.js +30 -20
  452. package/dist/tools/testSuite.js.map +1 -1
  453. package/dist/tools/testThis.js +24 -28
  454. package/dist/tools/testThis.js.map +1 -1
  455. package/dist/tools/visual.js +686 -210
  456. package/dist/tools/visual.js.map +1 -1
  457. package/dist/tools/wait.js +9 -38
  458. package/dist/tools/wait.js.map +1 -1
  459. package/dist/tools/wda.js +187 -57
  460. package/dist/tools/wda.js.map +1 -1
  461. package/dist/version.js +31 -22
  462. package/dist/version.js.map +1 -1
  463. package/dist/visual/ocr.js +53 -6
  464. package/dist/visual/ocr.js.map +1 -1
  465. package/dist/visual/provider.js +80 -12
  466. package/dist/visual/provider.js.map +1 -1
  467. package/docs/README.md +24 -10
  468. package/docs/ci-reports.md +328 -0
  469. package/docs/concepts.md +227 -0
  470. package/docs/flows.md +153 -0
  471. package/docs/mcp-server.md +194 -128
  472. package/docs/physical-devices.md +84 -0
  473. package/docs/tools.md +812 -152
  474. package/package.json +3 -2
  475. package/dist/tools/assertVisual.js +0 -84
  476. package/dist/tools/assertVisual.js.map +0 -1
  477. package/dist/tools/automationGenerate.js.map +0 -1
  478. package/dist/tools/capabilities.js +0 -179
  479. package/dist/tools/capabilities.js.map +0 -1
  480. package/dist/tools/detectContext.js +0 -41
  481. package/dist/tools/detectContext.js.map +0 -1
  482. package/dist/tools/flowGenerate.js.map +0 -1
  483. package/dist/tools/history.js +0 -86
  484. package/dist/tools/history.js.map +0 -1
  485. package/dist/tools/locator.js +0 -85
  486. package/dist/tools/locator.js.map +0 -1
  487. package/dist/tools/permissions.js +0 -174
  488. package/dist/tools/permissions.js.map +0 -1
  489. package/dist/tools/plan.js +0 -52
  490. package/dist/tools/plan.js.map +0 -1
  491. package/dist/tools/screenInfo.js +0 -76
  492. package/dist/tools/screenInfo.js.map +0 -1
  493. package/dist/tools/seed.js +0 -133
  494. package/dist/tools/seed.js.map +0 -1
  495. package/dist/tools/state.js +0 -217
  496. package/dist/tools/state.js.map +0 -1
  497. package/dist/tools/visualText.js +0 -106
  498. package/dist/tools/visualText.js.map +0 -1
@@ -1,20 +1,24 @@
1
- // Session + job + artifact store with DISK PERSISTENCE (DESIGN §3, M6).
1
+ // Session + job + artifact store with DISK PERSISTENCE.
2
2
  // Live, non-serializable bits (driver, lastSnapshot, abort controllers) stay in memory;
3
3
  // a serializable subset is written to <sessionDir>/state.json on mutation (debounced;
4
4
  // synchronous for the mutation ledger + session creation + shutdown flush), and a small
5
- // registry under ~/.swipium/registry.json — lock-guarded, lazily loaded on first access —
5
+ // registry under ~/.swipium/registry.json (lock-guarded, lazily loaded on first access)
6
6
  // lets a fresh server instance reload prior sessions so artifacts/reports/job-status survive
7
- // a server restart. (A restart does NOT resurrect a running child process — such jobs are
7
+ // a server restart. (A restart does NOT resurrect a running child process: such jobs are
8
8
  // marked failed on reload, and orphaned Metro pids are verified via `ps` and reaped.)
9
9
  import { createHash, randomUUID } from 'node:crypto';
10
- import { mkdirSync, writeFileSync, readFileSync, existsSync, renameSync } from 'node:fs';
11
- import { join, resolve } from 'node:path';
10
+ import { chmodSync, mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
11
+ import { extname, join, resolve, sep } from 'node:path';
12
12
  import { homedir } from 'node:os';
13
13
  import { DEFAULT_RESPONSE_MODE } from '../lib/result.js';
14
- import { makeRedactor } from '../lib/redact.js';
15
- import { withFileLock } from '../lib/lockfile.js';
14
+ import { makeRedactor, redactDeep, redactStructuredText, structuredKindOf } from '../lib/redact.js';
15
+ import { generatedOutputRedactor, inputBindings, inputPlaceholderFor, secretSafeActions, secretSafeNotes, swipiumVarName, } from '../suite/secretGuard.js';
16
+ import { withFileLock, writeFileAtomicSync } from '../lib/lockfile.js';
16
17
  import { log } from '../lib/logger.js';
17
18
  import { pidOwnedByLiveServer, reclaimPid } from './processRegistry.js';
19
+ import { fixtureMetadata, rehydrateFixtures } from '../fixtures/load.js';
20
+ import { scheduleStartupPrune } from './retention.js';
21
+ import { approvalMechanismFor } from '../consent/consent.js';
18
22
  // Phase 2.2 P1.6: budget classes (minutes) keyed by workflow ambition.
19
23
  export const BUDGET_PROFILES = {
20
24
  guardrail: 8, // guardrail/setup validation
@@ -22,7 +26,7 @@ export const BUDGET_PROFILES = {
22
26
  full_smoke: 15, // full authenticated smoke
23
27
  install_smoke: 20, // install/boot/rebuild involved
24
28
  };
25
- // Security model (REQ-02): raw secret values (generated passwords/OTPs/tokens) must never touch
29
+ // Security model: raw secret values (generated passwords/OTPs/tokens) must never touch
26
30
  // disk. In memory we keep the raw value (for same-session reuse via inputValues), but the
27
31
  // serialized form sent to state.json redacts secret values while preserving all reproducibility
28
32
  // metadata (varName/field/generator/secret/artifactUri/timestamps). Non-secret generated values
@@ -30,6 +34,36 @@ export const BUDGET_PROFILES = {
30
34
  export function serializeGeneratedValues(records) {
31
35
  return records.map((r) => (r.secret ? { ...r, value: '<redacted>' } : r));
32
36
  }
37
+ /** state.json form of the session's free-text records: raw registered secret values never touch
38
+ * disk. Recorded actions go through the same rewrite the generators use (a secret literal typed into
39
+ * a non-secure field becomes a secret step with no text); note/finding prose is redacted. URIs,
40
+ * paths and numbers are left alone. */
41
+ export function serializeSecretSafe(s) {
42
+ const toolErrors = s.toolErrors ?? [];
43
+ const jobs = [...(s.jobs?.values() ?? [])];
44
+ const envChanges = s.envChanges ?? [];
45
+ const mutations = s.mutations ?? [];
46
+ // Fixture values and seed specs are live config, never persisted (not even redacted): a
47
+ // rehydrated session re-reads them from .swipium/fixtures.json (see fixtures/load.ts).
48
+ const fixtures = fixtureMetadata(s.fixtures ?? []);
49
+ if (!s.secrets.size)
50
+ return { recordedActions: s.recordedActions, notes: s.notes, findings: s.findings, toolErrors, jobs, envChanges, mutations, fixtures };
51
+ const redact = makeRedactor(s.secrets);
52
+ const r = (v) => (v ? (redact(v) ?? v) : v);
53
+ // Structured records (job results, mutation targets, fixtures): strong secrets scrubbed anywhere,
54
+ // weak (dictionary-like) ones only as a whole value, so package ids / selectors stay intact.
55
+ const structured = generatedOutputRedactor(s.secrets);
56
+ return {
57
+ recordedActions: secretSafeActions(s.recordedActions, s.secrets).actions,
58
+ notes: secretSafeNotes(s.notes, s.secrets),
59
+ findings: s.findings.map((f) => ({ ...f, detail: r(f.detail) ?? f.detail, evidence: r(f.evidence) })),
60
+ toolErrors: toolErrors.map((t) => ({ ...t, message: r(t.message) ?? t.message })),
61
+ jobs: jobs.map((j) => ({ ...redactDeep(j, structured), error: r(j.error), progress: r(j.progress), resultText: r(j.resultText) })),
62
+ envChanges: envChanges.map((e) => r(e) ?? e),
63
+ mutations: mutations.map((m) => ({ ...redactDeep(m, structured), detail: r(m.detail) })),
64
+ fixtures: redactDeep(fixtures, structured),
65
+ };
66
+ }
33
67
  export const DEFAULT_BUDGET = {
34
68
  maxMinutes: 8,
35
69
  maxActions: 20,
@@ -58,6 +92,73 @@ export function artifactDirectoryName(kind) {
58
92
  return kind;
59
93
  }
60
94
  }
95
+ /** Filesystem-safe artifact file name (review B5): path separators, NUL/control chars and `..`
96
+ * runs are replaced, so a caller-supplied name (e.g. a visual baseline name) can never escape
97
+ * the session directory. Long names are truncated, keeping the extension. */
98
+ export function safeArtifactName(name) {
99
+ let n = String(name)
100
+ // eslint-disable-next-line no-control-regex
101
+ .replace(/[\\/\x00-\x1f]/g, '_')
102
+ .replace(/\.{2,}/g, '_')
103
+ .trim();
104
+ if (!n || n === '.')
105
+ n = 'artifact';
106
+ if (n.length > 180) {
107
+ const ext = extname(n).slice(0, 16);
108
+ n = n.slice(0, 180 - ext.length) + ext;
109
+ }
110
+ return n;
111
+ }
112
+ /** Percent-encode the characters that would stop a URI segment from matching the
113
+ * swipium:// resource templates (`/`, `,`, space, `%`, `?`, `#`, non-ASCII…). Readable ids such as
114
+ * `feature:login` stay as-is; pair with decodeUriSegment on the read side. */
115
+ export function encodeUriSegment(value) {
116
+ return value.replace(/[^A-Za-z0-9\-._~:@!$&'()*+;=]/gu, (c) => encodeURIComponent(c));
117
+ }
118
+ /** Inverse of encodeUriSegment; returns the input unchanged if it is not valid percent-encoding. */
119
+ export function decodeUriSegment(value) {
120
+ try {
121
+ return decodeURIComponent(value);
122
+ }
123
+ catch {
124
+ return value;
125
+ }
126
+ }
127
+ /** Canonical form of a swipium:// URI for equality: every path segment decoded. */
128
+ function canonicalUri(uri) {
129
+ return uri.split('/').map(decodeUriSegment).join('/');
130
+ }
131
+ /** True when `child` is `parent` or lies inside it (both resolved). */
132
+ export function isWithinRoot(child, parent) {
133
+ const c = resolve(child);
134
+ const p = resolve(parent);
135
+ return c === p || c.startsWith(p.endsWith(sep) ? p : p + sep);
136
+ }
137
+ /** Owner-only permissions for ~/.swipium/runs session data (dirs 0700, files 0600). POSIX only;
138
+ * best-effort (a chmod failure never breaks a write). */
139
+ export const PRIVATE_DIR_MODE = 0o700;
140
+ export const PRIVATE_FILE_MODE = 0o600;
141
+ function restrictFile(path) {
142
+ if (process.platform === 'win32')
143
+ return;
144
+ try {
145
+ chmodSync(path, PRIVATE_FILE_MODE);
146
+ }
147
+ catch {
148
+ /* best-effort */
149
+ }
150
+ }
151
+ function mkdirPrivate(dir) {
152
+ mkdirSync(dir, { recursive: true, mode: PRIVATE_DIR_MODE });
153
+ if (process.platform === 'win32')
154
+ return;
155
+ try {
156
+ chmodSync(dir, PRIVATE_DIR_MODE); // mode above only applies to NEWLY created dirs (and umask)
157
+ }
158
+ catch {
159
+ /* best-effort */
160
+ }
161
+ }
61
162
  function defaultSessionDir(root, id) {
62
163
  const projectHash = createHash('sha256').update(resolve(root)).digest('hex').slice(0, 16);
63
164
  return join(REGISTRY_DIR, 'runs', projectHash, id);
@@ -73,17 +174,22 @@ export class SessionStore {
73
174
  registryLoaded = false;
74
175
  dirty = new Set();
75
176
  flushTimer;
177
+ // Sessions created or looked up by id in THIS process (see activeRoots).
178
+ activeIds = new Set();
76
179
  ensureRegistryLoaded() {
77
180
  if (this.registryLoaded)
78
181
  return;
79
182
  this.registryLoaded = true;
80
183
  this.loadRegistry();
184
+ // Retention (session/retention.ts): one non-blocking background prune of old ~/.swipium/runs
185
+ // session dirs per process, never a registered or live session. SWIPIUM_RETENTION_DAYS=off disables.
186
+ scheduleStartupPrune(() => [...this.sessions.values()].map((x) => x.dir));
81
187
  }
82
188
  create(root, budget, opts) {
83
189
  this.ensureRegistryLoaded();
84
190
  const id = randomUUID().slice(0, 8);
85
191
  const dir = opts?.sessionDir ?? defaultSessionDir(root, id);
86
- mkdirSync(dir, { recursive: true });
192
+ mkdirPrivate(dir);
87
193
  const now = Date.now();
88
194
  const s = {
89
195
  id,
@@ -104,6 +210,7 @@ export class SessionStore {
104
210
  notes: [],
105
211
  mutations: [],
106
212
  recordedActions: [],
213
+ toolErrors: [],
107
214
  fixtures: opts?.fixtures ?? [],
108
215
  auth: {},
109
216
  milestones: { session_start: now },
@@ -115,13 +222,17 @@ export class SessionStore {
115
222
  aborts: new Map(),
116
223
  };
117
224
  this.sessions.set(id, s);
225
+ this.activeIds.add(id);
118
226
  this.appendRegistry(id, dir);
119
227
  this.persistNow(s); // synchronous: a concurrent instance / crash must see the new session
120
228
  return s;
121
229
  }
122
230
  get(id) {
123
231
  this.ensureRegistryLoaded();
124
- return this.sessions.get(id);
232
+ const s = this.sessions.get(id);
233
+ if (s)
234
+ this.activeIds.add(id);
235
+ return s;
125
236
  }
126
237
  list() {
127
238
  this.ensureRegistryLoaded();
@@ -163,47 +274,61 @@ export class SessionStore {
163
274
  }
164
275
  writeState(s) {
165
276
  try {
277
+ const safe = serializeSecretSafe(s); // registered secret values never reach state.json
278
+ // Keep the plain transport fields in sync with the live driver (duck-typed: no driver import),
279
+ // except while the live driver is a fallback for an unreachable WDA: the persisted 'wda'
280
+ // transport stays authoritative so a later restart retries it.
281
+ if (s.driver && !(s.transportFallback && s.driver.kind === 'simulator')) {
282
+ s.driverKind = s.driver.kind;
283
+ const baseUrl = s.driver.baseUrl;
284
+ if (s.driver.kind === 'wda' && typeof baseUrl === 'string')
285
+ s.wdaUrl = baseUrl;
286
+ }
166
287
  const state = {
167
288
  id: s.id,
168
289
  root: s.root,
169
290
  dir: s.dir,
170
291
  createdAt: s.createdAt,
171
292
  device: s.device,
293
+ driverKind: s.driverKind,
294
+ wdaUrl: s.wdaUrl,
172
295
  appId: s.appId,
173
296
  headless: s.headless,
174
297
  metroPid: s.metroPid,
175
298
  screenshotCount: s.screenshotCount,
176
299
  network: s.network,
177
- envChanges: s.envChanges,
300
+ envChanges: safe.envChanges,
178
301
  workarounds: s.workarounds,
302
+ chosenTarget: s.chosenTarget,
303
+ lastTestThisArgs: s.lastTestThisArgs && s.secrets.size ? redactDeep(s.lastTestThisArgs, makeRedactor(s.secrets)) : s.lastTestThisArgs,
179
304
  mode: s.mode,
180
305
  responseMode: s.responseMode,
181
306
  sensitive: s.sensitive,
182
307
  budget: s.budget,
183
308
  counters: s.counters,
184
- jobs: [...s.jobs.values()],
309
+ jobs: safe.jobs,
185
310
  artifacts: s.artifacts,
186
- findings: s.findings,
187
- notes: s.notes,
188
- mutations: s.mutations,
189
- recordedActions: s.recordedActions,
190
- fixtures: s.fixtures,
311
+ findings: safe.findings,
312
+ notes: safe.notes,
313
+ mutations: safe.mutations,
314
+ recordedActions: safe.recordedActions,
315
+ toolErrors: safe.toolErrors,
316
+ fixtures: safe.fixtures,
191
317
  auth: s.auth,
192
318
  milestones: s.milestones,
193
319
  budgetProfile: s.budgetProfile,
194
- inputs: s.inputs, // METADATA only — never the values
320
+ inputs: s.inputs, // METADATA only, never the values
195
321
  generatedValues: serializeGeneratedValues(s.generatedValues), // secret raw values redacted before disk
196
322
  exploration: s.exploration,
197
323
  };
198
324
  // Atomic write (tmp + rename) so a crash mid-write never leaves a truncated state.json.
199
325
  const target = join(s.dir, 'state.json');
200
- const tmp = `${target}.${process.pid}.tmp`;
201
- writeFileSync(tmp, JSON.stringify(state, null, 2));
202
- renameSync(tmp, target);
326
+ writeFileAtomicSync(target, JSON.stringify(state, null, 2));
327
+ restrictFile(target); // owner-only: state.json carries run history / paths
203
328
  }
204
329
  catch (e) {
205
330
  // A silent failure here means session state (jobs/artifacts/findings) is lost on restart.
206
- log('error', 'failed to persist session state.json — this session will not survive a server restart', {
331
+ log('error', 'failed to persist session state.json; this session will not survive a server restart', {
207
332
  sessionId: s.id,
208
333
  dir: s.dir,
209
334
  err: String(e),
@@ -226,6 +351,20 @@ export class SessionStore {
226
351
  s.mode = mode;
227
352
  this.persist(s);
228
353
  }
354
+ /**
355
+ * A structured UI dump just succeeded on this session: visual-fallback is per-screen, never
356
+ * permanent. Switch back to 'structured' and restart the consecutive-failure count. Returns
357
+ * true when the session was in visual-fallback (the caller may tell the agent it recovered).
358
+ */
359
+ noteStructuredDump(s) {
360
+ const recovered = s.mode === 'visual-fallback';
361
+ if (!recovered && s.counters.snapshotFailures === 0)
362
+ return false;
363
+ s.mode = 'structured';
364
+ s.counters.snapshotFailures = 0;
365
+ this.persist(s);
366
+ return recovered;
367
+ }
229
368
  bump(s, key, by = 1) {
230
369
  s.counters[key] += by;
231
370
  this.persist(s);
@@ -242,14 +381,14 @@ export class SessionStore {
242
381
  if (c.screenshots >= b.maxScreenshots)
243
382
  return `screenshot budget reached (${c.screenshots}/${b.maxScreenshots})`;
244
383
  if (c.noChangeActions >= b.maxNoChangeActions)
245
- return `repeated no-change actions (${c.noChangeActions}/${b.maxNoChangeActions}) — likely wrong coords / disabled element / auth wall`;
384
+ return `repeated no-change actions (${c.noChangeActions}/${b.maxNoChangeActions}): likely wrong coords / disabled element / auth wall`;
246
385
  return null;
247
386
  }
248
387
  updateJob(s, job, patch) {
249
388
  Object.assign(job, patch);
250
389
  this.persist(s);
251
390
  }
252
- /** Apply a patch ONLY while the job is still running — so a cancelled (or otherwise
391
+ /** Apply a patch ONLY while the job is still running, so a cancelled (or otherwise
253
392
  * terminal) job is never overwritten back to done/failed by a racing worker. */
254
393
  updateJobIfRunning(s, job, patch) {
255
394
  const cur = s.jobs.get(job.jobId);
@@ -278,7 +417,7 @@ export class SessionStore {
278
417
  s.envChanges.push(`${new Date().toISOString()} ${note}`);
279
418
  this.persist(s);
280
419
  }
281
- /** Record a safe fallback Swipium chose (roadmap §11 "workarounds attempted"). De-duped. */
420
+ /** Record a safe fallback Swipium chose. De-duped. */
282
421
  addWorkaround(s, note) {
283
422
  if (!s.workarounds.includes(note)) {
284
423
  s.workarounds.push(note);
@@ -290,15 +429,43 @@ export class SessionStore {
290
429
  this.persist(s);
291
430
  }
292
431
  recordMutation(s, mutation) {
293
- const rec = { id: randomUUID().slice(0, 8), at: Date.now(), ...mutation };
432
+ // Audit trail (THREAT_MODEL): stamp HOW a consented action was approved (elicitation vs
433
+ // client re-call assertion) from the consent module, so every call site gets it for free.
434
+ let consent = mutation.consent;
435
+ if (consent?.approved && consent.consentId && !consent.approvalMechanism) {
436
+ const mechanism = approvalMechanismFor(consent.consentId);
437
+ if (mechanism)
438
+ consent = { ...consent, approvalMechanism: mechanism };
439
+ }
440
+ const rec = { id: randomUUID().slice(0, 8), at: Date.now(), ...mutation, ...(consent ? { consent } : {}) };
294
441
  s.mutations.push(rec);
295
442
  if (s.mutations.length > 500)
296
443
  s.mutations.splice(0, s.mutations.length - 500);
297
- this.persistNow(s); // the mutation ledger is an audit trail — never leave it debounce-only
444
+ this.persistNow(s); // the mutation ledger is an audit trail; never leave it debounce-only
298
445
  return rec;
299
446
  }
447
+ /** Record a tool call that returned an error for this session (bounded to the last 200). */
448
+ recordToolError(s, rec) {
449
+ if (rec.failureCode === 'CANCELLED')
450
+ return; // cancelled work is not a tool error
451
+ const list = (s.toolErrors ??= []);
452
+ list.push({ at: rec.at ?? Date.now(), tool: rec.tool, failureCode: rec.failureCode, message: rec.message.slice(0, 500) });
453
+ if (list.length > 200)
454
+ list.splice(0, list.length - 200);
455
+ this.persist(s);
456
+ }
300
457
  /** Append an action-IR step (bounded) for qa_generate target:"flow". */
301
458
  addRecordedAction(s, ra) {
459
+ // Typed text equal to a stored session input (e.g. the email from qa_continue_from_blocker) is
460
+ // recorded as that input's ${VAR} placeholder, never the literal (generators do this too).
461
+ const bound = ra.action === 'type' ? inputPlaceholderFor(ra.text, inputBindings(s)) : undefined;
462
+ if (bound) {
463
+ ra = {
464
+ ...ra,
465
+ text: `\${${swipiumVarName(bound.varName)}}`,
466
+ ...(bound.secret ? { secret: true, exportability: 'needs-human-data' } : {}),
467
+ };
468
+ }
302
469
  s.recordedActions.push(ra);
303
470
  if (s.recordedActions.length > 300)
304
471
  s.recordedActions.splice(0, s.recordedActions.length - 300);
@@ -324,7 +491,9 @@ export class SessionStore {
324
491
  this.persist(s);
325
492
  }
326
493
  /** Store a user-provided input (hardening P0.5). The VALUE stays in-memory only; secrets are
327
- * added to the redaction set. Persists metadata so reports can say it was provided. */
494
+ * added to the redaction set. Persists metadata so reports can say it was provided.
495
+ * Secrets of 3+ characters are redacted (short ones as whole tokens); 1-2 character values are
496
+ * not matched and are reported as partial redaction (src/lib/redact.ts). */
328
497
  setInput(s, varName, value, secret, source) {
329
498
  s.inputValues.set(varName, value);
330
499
  if (secret)
@@ -340,7 +509,7 @@ export class SessionStore {
340
509
  }
341
510
  this.persist(s);
342
511
  }
343
- /** Variable map (varName → value) for the flow runner. In-memory values only. */
512
+ /** Variable map (varName > value) for the flow runner. In-memory values only. */
344
513
  inputVariables(s) {
345
514
  return Object.fromEntries(s.inputValues);
346
515
  }
@@ -348,22 +517,37 @@ export class SessionStore {
348
517
  clearInputValues(s) {
349
518
  s.inputValues.clear();
350
519
  }
520
+ /** Remember the ORIGINAL qa_test_this arguments (goal, goalText, flags) of this session's run. */
521
+ setLastTestThisArgs(s, args) {
522
+ s.lastTestThisArgs = { ...args, at: args.at ?? Date.now() };
523
+ this.persist(s);
524
+ }
351
525
  /** Record the latest guided-exploration result (Phase 3.3) for qa_report. */
352
526
  setExploration(s, rec) {
353
527
  s.exploration = rec;
354
528
  this.persist(s);
355
529
  }
356
530
  saveArtifact(s, kind, name, data, mime, label) {
531
+ // B5: never trust kind/name as path components. Sanitize, then verify containment.
532
+ kind = safeArtifactName(kind);
533
+ name = safeArtifactName(name);
357
534
  const sub = join(s.dir, artifactDirectoryName(kind));
358
- mkdirSync(sub, { recursive: true });
359
535
  const path = join(sub, name);
536
+ if (!isWithinRoot(path, s.dir) || resolve(path) === resolve(s.dir)) {
537
+ throw new Error(`Refusing to write artifact outside the session directory: ${kind}/${name}`);
538
+ }
539
+ mkdirPrivate(sub);
360
540
  const redact = makeRedactor(s.secrets);
361
541
  const isRedactableText = typeof data === 'string' && isTextArtifactMime(mime);
362
- const storedData = isRedactableText ? (redact(data) ?? '') : data;
542
+ // JSON/XML artifacts are redacted structurally (string values / attribute values / text
543
+ // nodes only) so a short numeric secret (CVV "123") can never rewrite `{"actions":123}`.
544
+ const storedData = isRedactableText ? redactStructuredText(data, structuredKindOf(mime, name), redact) : data;
363
545
  const storedLabel = label ? redact(label) : label;
364
- writeFileSync(path, storedData);
365
- const uri = `swipium://session/${s.id}/${kind}/${name}`;
366
- // Binary artifacts (screenshots/recordings) cannot be redacted — tag them explicitly so
546
+ const skipped = redact.skippedShortSecrets ?? 0;
547
+ writeFileSync(path, storedData, { mode: PRIVATE_FILE_MODE });
548
+ restrictFile(path); // an overwritten artifact keeps its old mode otherwise
549
+ const uri = `swipium://session/${encodeUriSegment(s.id)}/${encodeUriSegment(kind)}/${encodeUriSegment(name)}`;
550
+ // Binary artifacts (screenshots/recordings) cannot be redacted, so tag them explicitly so
367
551
  // consumers (qa_get_artifact, reports) know pixels may show sensitive on-screen content.
368
552
  s.artifacts.push({
369
553
  uri,
@@ -372,7 +556,12 @@ export class SessionStore {
372
556
  kind,
373
557
  createdAt: Date.now(),
374
558
  label: storedLabel,
375
- redaction: isRedactableText ? 'applied' : 'not-applied',
559
+ redaction: !isRedactableText ? 'not-applied' : skipped > 0 ? 'partial' : 'applied',
560
+ ...(isRedactableText && skipped > 0
561
+ ? {
562
+ redactionNote: `${skipped} secret value(s) shorter than 3 characters were not redacted (matching them would corrupt ordinary text).`,
563
+ }
564
+ : {}),
376
565
  });
377
566
  this.persist(s);
378
567
  return uri;
@@ -384,9 +573,30 @@ export class SessionStore {
384
573
  if (rec)
385
574
  return { session: s, rec };
386
575
  }
576
+ // Encoding-insensitive fallback: a client (or URL normalisation) may percent-encode a URI
577
+ // that was stored raw (pre-1.6 records), or vice versa.
578
+ const want = canonicalUri(uri);
579
+ for (const s of this.sessions.values()) {
580
+ const rec = s.artifacts.find((a) => canonicalUri(a.uri) === want);
581
+ if (rec)
582
+ return { session: s, rec };
583
+ }
387
584
  return undefined;
388
585
  }
389
- // ---- persistence reload (lazy — triggered by the first get/list/find/create) ----
586
+ /** Project roots this server process is actively working on: roots of sessions created here
587
+ * or touched by a tool call (get) since start, NOT every prior session reloaded from the
588
+ * machine-wide registry. Used to scope resources/list to the current project(s). */
589
+ activeRoots() {
590
+ this.ensureRegistryLoaded();
591
+ const roots = new Set();
592
+ for (const id of this.activeIds) {
593
+ const s = this.sessions.get(id);
594
+ if (s)
595
+ roots.add(s.root);
596
+ }
597
+ return [...roots];
598
+ }
599
+ // ---- persistence reload (lazy, triggered by the first get/list/find/create) ----
390
600
  loadRegistry() {
391
601
  try {
392
602
  if (!existsSync(REGISTRY))
@@ -409,17 +619,26 @@ export class SessionStore {
409
619
  }
410
620
  }
411
621
  // Orphaned-process handling: a prior server persisted its Metro pid. If no live
412
- // server instance owns that pid, verify via `ps` that it still IS a bundler-ish
413
- // process and reap it; a recycled pid is never signalled. Either way the reloaded
414
- // session drops the pid — it is not ours to manage (or stop) anymore.
622
+ // server instance owns that pid, reclaimPid (processRegistry) reaps it ONLY when the
623
+ // registry's recorded fingerprint (start time + full command) still matches. A
624
+ // recycled or unverifiable pid is never signalled, and a process group is signalled
625
+ // only when Swipium spawned the child as a group leader. Either way the reloaded
626
+ // session drops the pid: it is not ours to manage (or stop) anymore.
415
627
  if (typeof st.metroPid === 'number' && st.metroPid > 0) {
416
628
  if (!pidOwnedByLiveServer(st.metroPid)) {
417
629
  const outcome = reclaimPid(st.metroPid, 'metro');
418
630
  if (outcome === 'killed')
419
631
  log('warn', 'reaped orphaned Metro bundler from a previous server run', { pid: st.metroPid, sessionId: st.id });
420
632
  }
421
- st.metroPid = undefined; // reaped, gone, recycled, or another live server's — never ours
633
+ st.metroPid = undefined; // reaped, gone, recycled, or another live server's; never ours
422
634
  }
635
+ // Raw secret values never touch disk, so a rehydrated session cannot rebuild
636
+ // its redaction set. If the persisted state shows prior secret-bearing activity
637
+ // (secret inputs, secret generated values, or a performed login), flag the session so
638
+ // qa_report discloses that artifacts written after the restart are no longer scrubbed.
639
+ const priorSecrets = (st.inputs ?? []).some((i) => i.secret) ||
640
+ (st.generatedValues ?? []).some((g) => g.secret) ||
641
+ st.auth?.loginPerformed === true;
423
642
  const s = {
424
643
  id: st.id,
425
644
  root: st.root,
@@ -433,6 +652,10 @@ export class SessionStore {
433
652
  network: st.network,
434
653
  envChanges: st.envChanges ?? [],
435
654
  workarounds: st.workarounds ?? [],
655
+ chosenTarget: typeof st.chosenTarget === 'string' ? st.chosenTarget : undefined,
656
+ lastTestThisArgs: st.lastTestThisArgs && typeof st.lastTestThisArgs === 'object' && !Array.isArray(st.lastTestThisArgs)
657
+ ? st.lastTestThisArgs
658
+ : undefined,
436
659
  mode: st.mode ?? 'structured',
437
660
  responseMode: st.responseMode ?? DEFAULT_RESPONSE_MODE,
438
661
  sensitive: st.sensitive ?? false,
@@ -444,11 +667,17 @@ export class SessionStore {
444
667
  notes: st.notes ?? [],
445
668
  mutations: st.mutations ?? [],
446
669
  recordedActions: st.recordedActions ?? [],
447
- fixtures: st.fixtures ?? [],
670
+ toolErrors: st.toolErrors ?? [],
671
+ driverKind: typeof st.driverKind === 'string' ? st.driverKind : undefined,
672
+ wdaUrl: typeof st.wdaUrl === 'string' ? st.wdaUrl : undefined,
673
+ // Live fixture values come from the project's fixtures.json (+ SWIPIUM_* env at use),
674
+ // never from state.json, which only holds value-less metadata.
675
+ fixtures: rehydrateFixtures(st.root, st.fixtures),
448
676
  auth: st.auth ?? {},
449
677
  milestones: st.milestones ?? { session_start: st.createdAt },
450
678
  budgetProfile: st.budgetProfile,
451
679
  secrets: new Set(),
680
+ redactionDegraded: priorSecrets || undefined,
452
681
  inputs: st.inputs ?? [],
453
682
  generatedValues: st.generatedValues ?? [],
454
683
  inputValues: new Map(),
@@ -458,13 +687,13 @@ export class SessionStore {
458
687
  this.sessions.set(s.id, s);
459
688
  }
460
689
  catch (e) {
461
- // Skip a corrupt session, but say so — otherwise prior artifacts/reports vanish silently.
690
+ // Skip a corrupt session, but say so, otherwise prior artifacts/reports vanish silently.
462
691
  log('warn', 'skipping corrupt prior session state.json on reload', { dir, err: String(e) });
463
692
  }
464
693
  }
465
694
  }
466
695
  catch (e) {
467
- log('warn', 'session registry unreadable — prior sessions will not be reloaded', { registry: REGISTRY, err: String(e) });
696
+ log('warn', 'session registry unreadable, prior sessions will not be reloaded', { registry: REGISTRY, err: String(e) });
468
697
  }
469
698
  }
470
699
  /** Append this session to the shared ~/.swipium/registry.json. Guarded by an advisory
@@ -482,20 +711,18 @@ export class SessionStore {
482
711
  reg = parsed;
483
712
  }
484
713
  catch (e) {
485
- log('warn', 'registry.json corrupt — rebuilding it (prior sessions may need re-registration)', {
714
+ log('warn', 'registry.json corrupt, rebuilding it (prior sessions may need re-registration)', {
486
715
  registry: REGISTRY,
487
716
  err: String(e),
488
717
  });
489
718
  }
490
719
  }
491
720
  reg.push({ id, dir });
492
- const tmp = `${REGISTRY}.${process.pid}.tmp`;
493
- writeFileSync(tmp, JSON.stringify(reg.slice(-200), null, 2));
494
- renameSync(tmp, REGISTRY); // atomic on the same filesystem
721
+ writeFileAtomicSync(REGISTRY, JSON.stringify(reg.slice(-200), null, 2)); // atomic on the same filesystem
495
722
  });
496
723
  }
497
724
  catch (e) {
498
- log('error', 'failed to append session to registry — it will not be reloadable after a restart', { id, dir, err: String(e) });
725
+ log('error', 'failed to append session to registry; it will not be reloadable after a restart', { id, dir, err: String(e) });
499
726
  }
500
727
  }
501
728
  }