@crvouga/atlas 0.3.0 → 0.5.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 (429) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +178 -604
  3. package/dist/bun/src/driver.d.ts +41 -7
  4. package/dist/bun/src/driver.js +47 -6
  5. package/dist/bun/src/index.d.ts +8 -0
  6. package/dist/bun/src/index.js +8 -0
  7. package/dist/core/src/cli.d.ts +14 -1
  8. package/dist/core/src/cli.js +30 -1
  9. package/dist/core/src/config.d.ts +70 -4
  10. package/dist/core/src/config.js +62 -3
  11. package/dist/core/src/events/cloudevents.d.ts +54 -9
  12. package/dist/core/src/events/cloudevents.js +60 -9
  13. package/dist/core/src/graph.d.ts +21 -0
  14. package/dist/core/src/graph.js +26 -0
  15. package/dist/core/src/index.d.ts +11 -0
  16. package/dist/core/src/index.js +11 -0
  17. package/dist/core/src/lint.d.ts +30 -0
  18. package/dist/core/src/lint.js +13 -0
  19. package/dist/core/src/plan.d.ts +39 -1
  20. package/dist/core/src/plan.js +28 -1
  21. package/dist/core/src/privacy.d.ts +29 -2
  22. package/dist/core/src/privacy.js +19 -2
  23. package/dist/core/src/report/formats.d.ts +34 -4
  24. package/dist/core/src/report/formats.js +24 -4
  25. package/dist/core/src/report/markdown.d.ts +15 -0
  26. package/dist/core/src/report/markdown.js +8 -0
  27. package/dist/core/src/run/clients.d.ts +44 -5
  28. package/dist/core/src/run/clients.js +46 -2
  29. package/dist/core/src/run/execute.d.ts +69 -16
  30. package/dist/core/src/run/execute.js +67 -16
  31. package/dist/core/src/run/frames.d.ts +13 -4
  32. package/dist/core/src/run/frames.js +19 -4
  33. package/dist/core/src/run/http.d.ts +34 -1
  34. package/dist/core/src/run/http.js +14 -1
  35. package/dist/core/src/run/types.d.ts +76 -3
  36. package/dist/core/src/run/types.js +19 -3
  37. package/dist/core/src/signals.d.ts +49 -5
  38. package/dist/core/src/signals.js +22 -5
  39. package/dist/core/src/spec/compose.d.ts +23 -1
  40. package/dist/core/src/spec/compose.js +10 -1
  41. package/dist/core/src/spec/index.d.ts +9 -1
  42. package/dist/core/src/spec/index.js +9 -1
  43. package/dist/core/src/spec/load.d.ts +22 -1
  44. package/dist/core/src/spec/load.js +31 -1
  45. package/dist/core/src/spec/parse.d.ts +16 -2
  46. package/dist/core/src/spec/parse.js +25 -2
  47. package/dist/core/src/spec/scxml.d.ts +25 -4
  48. package/dist/core/src/spec/scxml.js +36 -4
  49. package/dist/core/src/spec/types.d.ts +52 -1
  50. package/dist/core/src/spec/types.js +24 -0
  51. package/dist/detox/src/index.d.ts +54 -11
  52. package/dist/detox/src/index.js +45 -7
  53. package/dist/playwright/src/driver.d.ts +39 -3
  54. package/dist/playwright/src/driver.js +27 -3
  55. package/dist/playwright/src/index.d.ts +10 -0
  56. package/dist/playwright/src/index.js +10 -0
  57. package/dist/playwright/src/media.d.ts +50 -4
  58. package/dist/playwright/src/media.js +55 -4
  59. package/dist/playwright/src/signals.d.ts +44 -5
  60. package/dist/playwright/src/signals.js +29 -5
  61. package/dist/reports/src/attachment-files.d.ts +44 -0
  62. package/dist/reports/src/attachment-files.js +121 -0
  63. package/dist/reports/src/attachment-response.d.ts +31 -0
  64. package/dist/reports/src/attachment-response.js +55 -0
  65. package/dist/reports/src/aws-credentials.d.ts +48 -0
  66. package/dist/reports/src/aws-credentials.js +221 -0
  67. package/dist/reports/src/browser-navigation.d.ts +14 -0
  68. package/dist/reports/src/browser-navigation.js +7 -0
  69. package/dist/reports/src/bundle.d.ts +34 -2
  70. package/dist/reports/src/bundle.js +38 -2
  71. package/dist/reports/src/catalog.d.ts +34 -3
  72. package/dist/reports/src/catalog.js +17 -3
  73. package/dist/reports/src/cli-stores.d.ts +36 -0
  74. package/dist/reports/src/cli-stores.js +73 -0
  75. package/dist/reports/src/clock.d.ts +7 -2
  76. package/dist/reports/src/clock.js +5 -2
  77. package/dist/reports/src/completion-order.d.ts +18 -5
  78. package/dist/reports/src/completion-order.js +18 -5
  79. package/dist/reports/src/compose.d.ts +80 -0
  80. package/dist/reports/src/compose.js +117 -0
  81. package/dist/reports/src/copy-store.d.ts +29 -0
  82. package/dist/reports/src/copy-store.js +52 -0
  83. package/dist/reports/src/directory-outline.d.ts +38 -0
  84. package/dist/reports/src/directory-outline.js +103 -0
  85. package/dist/reports/src/directory-store.d.ts +20 -0
  86. package/dist/reports/src/directory-store.js +166 -0
  87. package/dist/reports/src/doctor.d.ts +29 -0
  88. package/dist/reports/src/doctor.js +83 -0
  89. package/dist/reports/src/effective-delta.d.ts +34 -0
  90. package/dist/reports/src/effective-delta.js +25 -0
  91. package/dist/reports/src/evidence-detail.d.ts +64 -0
  92. package/dist/reports/src/evidence-detail.js +92 -0
  93. package/dist/reports/src/evidence-live.d.ts +40 -4
  94. package/dist/reports/src/evidence-live.js +52 -4
  95. package/dist/reports/src/evidence-types.d.ts +70 -1
  96. package/dist/reports/src/evidence-types.js +5 -1
  97. package/dist/reports/src/evidence-view.d.ts +58 -6
  98. package/dist/reports/src/evidence-view.js +44 -5
  99. package/dist/reports/src/explorer-types.d.ts +40 -3
  100. package/dist/reports/src/explorer-types.js +8 -0
  101. package/dist/reports/src/explorer-versions.d.ts +9 -1
  102. package/dist/reports/src/explorer-versions.js +18 -1
  103. package/dist/reports/src/export-evidence.d.ts +16 -1
  104. package/dist/reports/src/export-evidence.js +52 -1
  105. package/dist/reports/src/fetcher.d.ts +27 -3
  106. package/dist/reports/src/fetcher.js +11 -3
  107. package/dist/reports/src/format-date.d.ts +6 -0
  108. package/dist/reports/src/format-date.js +6 -0
  109. package/dist/reports/src/format.d.ts +16 -0
  110. package/dist/reports/src/format.js +10 -0
  111. package/dist/reports/src/frames.d.ts +36 -3
  112. package/dist/reports/src/frames.js +22 -3
  113. package/dist/reports/src/hash.d.ts +18 -0
  114. package/dist/reports/src/hash.js +13 -0
  115. package/dist/reports/src/host.d.ts +29 -0
  116. package/dist/reports/src/host.js +64 -0
  117. package/dist/reports/src/html-frame.d.ts +5 -6
  118. package/dist/reports/src/html-frame.js +12 -1
  119. package/dist/reports/src/http-store.d.ts +52 -0
  120. package/dist/reports/src/http-store.js +145 -0
  121. package/dist/reports/src/http.d.ts +7 -0
  122. package/dist/reports/src/http.js +6 -0
  123. package/dist/reports/src/ingest.d.ts +54 -0
  124. package/dist/reports/src/ingest.js +210 -0
  125. package/dist/reports/src/init.d.ts +40 -0
  126. package/dist/reports/src/init.js +364 -0
  127. package/dist/reports/src/junit.d.ts +33 -6
  128. package/dist/reports/src/junit.js +87 -21
  129. package/dist/reports/src/keyboard.d.ts +20 -0
  130. package/dist/reports/src/keyboard.js +11 -0
  131. package/dist/reports/src/keys.d.ts +21 -3
  132. package/dist/reports/src/keys.js +21 -3
  133. package/dist/reports/src/layer-axes.d.ts +48 -0
  134. package/dist/reports/src/layer-axes.js +71 -0
  135. package/dist/reports/src/layer-catalog.d.ts +28 -1
  136. package/dist/reports/src/layer-catalog.js +19 -2
  137. package/dist/reports/src/layer-merge.d.ts +98 -9
  138. package/dist/reports/src/layer-merge.js +154 -33
  139. package/dist/reports/src/layer-source.d.ts +18 -0
  140. package/dist/reports/src/layer-source.js +14 -0
  141. package/dist/reports/src/layer-store.d.ts +42 -0
  142. package/dist/reports/src/layer-store.js +63 -5
  143. package/dist/reports/src/layer-types.d.ts +34 -1
  144. package/dist/reports/src/layers-route.d.ts +47 -4
  145. package/dist/reports/src/layers-route.js +88 -12
  146. package/dist/reports/src/media-types.d.ts +11 -0
  147. package/dist/reports/src/media-types.js +22 -0
  148. package/dist/reports/src/namespace-guard.d.ts +10 -1
  149. package/dist/reports/src/namespace-guard.js +10 -1
  150. package/dist/reports/src/navigation.d.ts +12 -0
  151. package/dist/reports/src/navigation.js +7 -0
  152. package/dist/reports/src/object-io.d.ts +11 -4
  153. package/dist/reports/src/object-io.js +4 -4
  154. package/dist/reports/src/object-store-conformance.d.ts +27 -0
  155. package/dist/reports/src/object-store-conformance.js +87 -0
  156. package/dist/reports/src/object-store.d.ts +33 -24
  157. package/dist/reports/src/object-store.js +17 -24
  158. package/dist/reports/src/parse-with.d.ts +16 -0
  159. package/dist/reports/src/parse-with.js +14 -0
  160. package/dist/reports/src/performance-part.d.ts +25 -1
  161. package/dist/reports/src/performance-part.js +15 -1
  162. package/dist/reports/src/performance.d.ts +22 -2
  163. package/dist/reports/src/performance.js +12 -2
  164. package/dist/reports/src/persist.d.ts +12 -3
  165. package/dist/reports/src/persist.js +12 -3
  166. package/dist/reports/src/phone.d.ts +18 -1
  167. package/dist/reports/src/phone.js +18 -1
  168. package/dist/reports/src/protocol.d.ts +30 -4
  169. package/dist/reports/src/protocol.js +15 -4
  170. package/dist/reports/src/publication.d.ts +21 -1
  171. package/dist/reports/src/publication.js +21 -1
  172. package/dist/reports/src/publish-checks.d.ts +35 -0
  173. package/dist/reports/src/publish-checks.js +122 -0
  174. package/dist/reports/src/publish.d.ts +44 -9
  175. package/dist/reports/src/publish.js +40 -7
  176. package/dist/reports/src/read.d.ts +46 -16
  177. package/dist/reports/src/read.js +38 -14
  178. package/dist/reports/src/recording-bundle.d.ts +13 -2
  179. package/dist/reports/src/recording-bundle.js +4 -2
  180. package/dist/reports/src/recording-keys.d.ts +15 -1
  181. package/dist/reports/src/recording-keys.js +15 -1
  182. package/dist/reports/src/remote-report.d.ts +28 -7
  183. package/dist/reports/src/remote-report.js +15 -7
  184. package/dist/reports/src/report-cli.d.ts +51 -0
  185. package/dist/reports/src/report-cli.js +485 -0
  186. package/dist/reports/src/report-index.d.ts +99 -0
  187. package/dist/reports/src/report-index.js +337 -0
  188. package/dist/reports/src/report-loader.d.ts +10 -0
  189. package/dist/reports/src/report-loader.js +6 -0
  190. package/dist/reports/src/report-parts.d.ts +29 -3
  191. package/dist/reports/src/report-parts.js +40 -3
  192. package/dist/reports/src/report-status.d.ts +48 -0
  193. package/dist/reports/src/report-status.js +156 -0
  194. package/dist/reports/src/report-types.d.ts +73 -0
  195. package/dist/reports/src/report-types.js +3 -0
  196. package/dist/reports/src/reporter.d.ts +67 -0
  197. package/dist/reports/src/reporter.js +128 -0
  198. package/dist/reports/src/reports-explorer-route.d.ts +541 -0
  199. package/dist/reports/src/reports-explorer-route.js +11 -0
  200. package/dist/reports/src/reports-list-route.d.ts +9 -0
  201. package/dist/reports/src/reports-list-route.js +9 -0
  202. package/dist/reports/src/reports-route.d.ts +561 -22
  203. package/dist/reports/src/reports-route.js +11 -3
  204. package/dist/reports/src/reports-store.d.ts +19 -2
  205. package/dist/reports/src/reports-store.js +10 -2
  206. package/dist/reports/src/resilient-cache.d.ts +14 -1
  207. package/dist/reports/src/resilient-cache.js +8 -1
  208. package/dist/reports/src/retention.d.ts +17 -1
  209. package/dist/reports/src/retention.js +5 -1
  210. package/dist/reports/src/run-bundle.d.ts +63 -0
  211. package/dist/reports/src/run-bundle.js +159 -0
  212. package/dist/reports/src/s3-store.d.ts +79 -0
  213. package/dist/reports/src/s3-store.js +295 -0
  214. package/dist/reports/src/scenario.d.ts +14 -4
  215. package/dist/reports/src/scenario.js +5 -4
  216. package/dist/reports/src/sigv4.d.ts +72 -0
  217. package/dist/reports/src/sigv4.js +96 -0
  218. package/dist/reports/src/snapshot-bundle.d.ts +24 -1
  219. package/dist/reports/src/snapshot-bundle.js +27 -2
  220. package/dist/reports/src/snapshot-surface.d.ts +20 -9
  221. package/dist/reports/src/snapshot-surface.js +32 -19
  222. package/dist/reports/src/source-content.d.ts +23 -0
  223. package/dist/reports/src/source-content.js +30 -1
  224. package/dist/reports/src/source-entries.d.ts +13 -0
  225. package/dist/reports/src/source-entries.js +6 -0
  226. package/dist/reports/src/source-part.d.ts +15 -0
  227. package/dist/reports/src/source-part.js +8 -0
  228. package/dist/reports/src/source-reader.d.ts +11 -0
  229. package/dist/reports/src/source-reader.js +5 -0
  230. package/dist/reports/src/spec-evidence.d.ts +12 -2
  231. package/dist/reports/src/spec-evidence.js +6 -2
  232. package/dist/reports/src/spec-inventory.d.ts +15 -1
  233. package/dist/reports/src/spec-inventory.js +22 -1
  234. package/dist/reports/src/spec-link.d.ts +28 -0
  235. package/dist/reports/src/spec-link.js +70 -0
  236. package/dist/reports/src/spec-set.d.ts +63 -0
  237. package/dist/reports/src/spec-set.js +177 -0
  238. package/dist/reports/src/status.d.ts +10 -0
  239. package/dist/reports/src/status.js +5 -0
  240. package/dist/reports/src/store-prefix.d.ts +28 -0
  241. package/dist/reports/src/store-prefix.js +22 -0
  242. package/dist/reports/src/store-url.d.ts +89 -0
  243. package/dist/reports/src/store-url.js +160 -0
  244. package/dist/reports/src/test-catalog-entity.d.ts +11 -0
  245. package/dist/reports/src/test-catalog-entity.js +6 -0
  246. package/dist/reports/src/types.d.ts +6 -1
  247. package/dist/reports/src/use-report-nav.d.ts +35 -0
  248. package/dist/reports/src/use-report-nav.js +12 -0
  249. package/dist/schema/src/api.d.ts +799 -0
  250. package/dist/schema/src/api.js +201 -0
  251. package/dist/schema/src/cli-results.d.ts +210 -0
  252. package/dist/schema/src/cli-results.js +124 -0
  253. package/dist/schema/src/common.d.ts +53 -0
  254. package/dist/schema/src/common.js +42 -0
  255. package/dist/schema/src/index.d.ts +14 -0
  256. package/dist/schema/src/index.js +14 -0
  257. package/dist/schema/src/layer.d.ts +2273 -0
  258. package/dist/schema/src/layer.js +462 -0
  259. package/dist/schema/src/manifest.d.ts +95 -0
  260. package/dist/schema/src/manifest.js +84 -0
  261. package/dist/schema/src/report-sources.d.ts +28 -0
  262. package/dist/schema/src/report-sources.js +25 -0
  263. package/dist/schema/src/runs-index.d.ts +16 -0
  264. package/dist/schema/src/runs-index.js +15 -0
  265. package/dist/schema/src/server-config.d.ts +93 -0
  266. package/dist/schema/src/server-config.js +73 -0
  267. package/dist/schema/src/spec.d.ts +69 -0
  268. package/dist/schema/src/spec.js +58 -0
  269. package/dist/server/src/app.d.ts +62 -0
  270. package/dist/server/src/app.js +419 -0
  271. package/dist/server/src/auth.d.ts +41 -0
  272. package/dist/server/src/auth.js +59 -0
  273. package/dist/server/src/config.d.ts +83 -0
  274. package/dist/server/src/config.js +187 -0
  275. package/dist/server/src/dev.d.ts +12 -0
  276. package/dist/server/src/dev.js +80 -0
  277. package/dist/server/src/events.d.ts +29 -0
  278. package/dist/server/src/events.js +101 -0
  279. package/dist/server/src/host.d.ts +30 -0
  280. package/dist/server/src/host.js +115 -0
  281. package/dist/server/src/index.d.ts +16 -0
  282. package/dist/server/src/index.js +16 -0
  283. package/dist/server/src/llms.d.ts +19 -0
  284. package/dist/server/src/llms.js +63 -0
  285. package/dist/server/src/main.d.ts +30 -0
  286. package/dist/server/src/main.js +112 -0
  287. package/dist/server/src/openapi.d.ts +59 -0
  288. package/dist/server/src/openapi.js +374 -0
  289. package/dist/server/src/problem.d.ts +8 -0
  290. package/dist/server/src/problem.js +39 -0
  291. package/dist/server/src/site.d.ts +13 -0
  292. package/dist/server/src/site.js +43 -0
  293. package/dist/server/src/store-api.d.ts +28 -0
  294. package/dist/server/src/store-api.js +159 -0
  295. package/dist/server/src/validate.d.ts +71 -0
  296. package/dist/server/src/validate.js +197 -0
  297. package/dist/vitest/src/index.d.ts +27 -5
  298. package/dist/vitest/src/index.js +18 -5
  299. package/dist/webdriver/src/driver.d.ts +63 -11
  300. package/dist/webdriver/src/driver.js +57 -6
  301. package/dist/webdriver/src/index.d.ts +8 -0
  302. package/dist/webdriver/src/index.js +1 -0
  303. package/dist/webdriver/src/protocol.d.ts +75 -6
  304. package/dist/webdriver/src/protocol.js +79 -6
  305. package/docs/agents.md +137 -0
  306. package/docs/deploying.md +345 -0
  307. package/docs/dogfooding.md +175 -0
  308. package/docs/extending.md +242 -0
  309. package/docs/integrating.md +485 -0
  310. package/docs/reference/bun.md +141 -0
  311. package/docs/reference/cli.md +146 -0
  312. package/docs/reference/core.md +2426 -0
  313. package/docs/reference/detox.md +150 -0
  314. package/docs/reference/http-api.md +303 -0
  315. package/docs/reference/playwright.md +303 -0
  316. package/docs/reference/reports.md +7098 -0
  317. package/docs/reference/schema.md +1651 -0
  318. package/docs/reference/server.md +685 -0
  319. package/docs/reference/vitest.md +45 -0
  320. package/docs/reference/webdriver.md +330 -0
  321. package/docs/report-backends.md +144 -0
  322. package/docs/report-contract.md +400 -0
  323. package/docs/reporting-issues.md +160 -0
  324. package/docs/server.md +285 -0
  325. package/docs/statecharts.md +641 -0
  326. package/llms-full.txt +3506 -0
  327. package/llms.txt +47 -0
  328. package/package.json +33 -5
  329. package/packages/core/README.md +2 -1
  330. package/packages/reports/README.md +69 -2
  331. package/packages/reports/STORAGE.md +5 -1
  332. package/packages/schema/README.md +14 -0
  333. package/packages/schema/schemas/check-detail.schema.json +1032 -0
  334. package/packages/schema/schemas/checks-result.schema.json +26 -0
  335. package/packages/schema/schemas/command-error.schema.json +35 -0
  336. package/packages/schema/schemas/doctor-result.schema.json +60 -0
  337. package/packages/schema/schemas/effective-delta.schema.json +490 -0
  338. package/packages/schema/schemas/effective-evidence.schema.json +471 -0
  339. package/packages/schema/schemas/evidence-page.schema.json +271 -0
  340. package/packages/schema/schemas/hello-event.schema.json +22 -0
  341. package/packages/schema/schemas/init-plan.schema.json +347 -0
  342. package/packages/schema/schemas/layer-rows.schema.json +199 -0
  343. package/packages/schema/schemas/layer.schema.json +2888 -0
  344. package/packages/schema/schemas/merge-result.schema.json +40 -0
  345. package/packages/schema/schemas/outline.schema.json +62 -0
  346. package/packages/schema/schemas/problem.schema.json +37 -0
  347. package/packages/schema/schemas/publish-checks.schema.json +1413 -0
  348. package/packages/schema/schemas/publish-result.schema.json +168 -0
  349. package/packages/schema/schemas/ready.schema.json +68 -0
  350. package/packages/schema/schemas/report-event.schema.json +100 -0
  351. package/packages/schema/schemas/report-status.schema.json +370 -0
  352. package/packages/schema/schemas/server-config.schema.json +194 -0
  353. package/packages/schema/schemas/server-info.schema.json +114 -0
  354. package/packages/schema/schemas/spec-set.schema.json +103 -0
  355. package/packages/schema/schemas/store-listing.schema.json +29 -0
  356. package/packages/schema/schemas/store-write-result.schema.json +28 -0
  357. package/packages/schema/schemas/stream-result.schema.json +59 -0
  358. package/packages/server/README.md +43 -0
  359. package/packages/server/openapi.json +6699 -0
  360. package/packages/visualizer/README.md +27 -5
  361. package/packages/visualizer/example/specs/coffee/ordering/cart.qnt +7 -0
  362. package/packages/visualizer/example/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  363. package/packages/visualizer/example/specs/coffee/ordering/drink-price.dmn +24 -0
  364. package/packages/visualizer/example/specs/coffee/ordering/loyalty.feature +7 -0
  365. package/packages/visualizer/example/specs/coffee/ordering/ordering.feature +36 -0
  366. package/packages/visualizer/example/specs/coffee/ordering/payment.als +4 -0
  367. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/cart.qnt +7 -0
  368. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  369. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/drink-price.dmn +24 -0
  370. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/loyalty.feature +7 -0
  371. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/ordering.feature +36 -0
  372. package/packages/visualizer/fixtures/failures/specs/coffee/ordering/payment.als +4 -0
  373. package/packages/visualizer/fixtures/full/specs/coffee/ordering/cart.qnt +7 -0
  374. package/packages/visualizer/fixtures/full/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  375. package/packages/visualizer/fixtures/full/specs/coffee/ordering/drink-price.dmn +24 -0
  376. package/packages/visualizer/fixtures/full/specs/coffee/ordering/loyalty.feature +7 -0
  377. package/packages/visualizer/fixtures/full/specs/coffee/ordering/ordering.feature +36 -0
  378. package/packages/visualizer/fixtures/full/specs/coffee/ordering/payment.als +4 -0
  379. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/cart.qnt +7 -0
  380. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  381. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/drink-price.dmn +24 -0
  382. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/loyalty.feature +7 -0
  383. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/ordering.feature +36 -0
  384. package/packages/visualizer/fixtures/malformed/specs/coffee/ordering/payment.als +4 -0
  385. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/cart.qnt +7 -0
  386. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  387. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/drink-price.dmn +24 -0
  388. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/loyalty.feature +7 -0
  389. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/ordering.feature +36 -0
  390. package/packages/visualizer/fixtures/partial/specs/coffee/ordering/payment.als +4 -0
  391. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/cart.qnt +7 -0
  392. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  393. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/drink-price.dmn +24 -0
  394. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/loyalty.feature +7 -0
  395. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/ordering.feature +36 -0
  396. package/packages/visualizer/fixtures/removed/specs/coffee/ordering/payment.als +4 -0
  397. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/cart.qnt +7 -0
  398. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/checkout.openslo.yaml +10 -0
  399. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/drink-price.dmn +24 -0
  400. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/loyalty.feature +7 -0
  401. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/ordering.feature +36 -0
  402. package/packages/visualizer/fixtures/spec-only/specs/coffee/ordering/payment.als +4 -0
  403. package/packages/visualizer/scripts/make-fixtures.ts +2 -0
  404. package/packages/visualizer/scripts/publish-data.ts +1 -2
  405. package/packages/visualizer/src/app/AppShell.tsx +9 -2
  406. package/packages/visualizer/src/app/report-sources.tsx +6 -4
  407. package/packages/visualizer/src/app/router.tsx +3 -3
  408. package/packages/visualizer/src/components/Header.module.css +35 -0
  409. package/packages/visualizer/src/components/Header.tsx +41 -4
  410. package/packages/visualizer/src/data/adapters/adapter.ts +5 -17
  411. package/packages/visualizer/src/data/adapters/index.ts +10 -1
  412. package/packages/visualizer/src/evidence/api.ts +14 -2
  413. package/packages/visualizer/src/evidence/artifacts.module.css +213 -0
  414. package/packages/visualizer/src/evidence/artifacts.tsx +272 -0
  415. package/packages/visualizer/src/evidence/check-detail.tsx +38 -4
  416. package/packages/visualizer/src/evidence/evidence-context.tsx +74 -12
  417. package/packages/visualizer/src/evidence/evidence.module.css +20 -0
  418. package/packages/visualizer/src/evidence/marks.tsx +2 -0
  419. package/packages/visualizer/src/evidence/overview.tsx +3 -3
  420. package/packages/visualizer/src/evidence/sources-panel.tsx +88 -34
  421. package/packages/visualizer/src/evidence/spec-page.tsx +23 -16
  422. package/packages/visualizer/src/find/Finder.tsx +1 -7
  423. package/packages/visualizer/src/find/open.ts +4 -0
  424. package/packages/visualizer/src/live/live-stream.ts +204 -0
  425. package/packages/visualizer/src/runtime.ts +19 -0
  426. package/packages/visualizer/src/state/ui-store.ts +6 -2
  427. package/packages/visualizer/vite/atlas-server-proxy.ts +36 -0
  428. package/packages/visualizer/vite/example-reports.ts +475 -0
  429. package/packages/visualizer/vite.config.ts +18 -2
@@ -0,0 +1,38 @@
1
+ import type { ReportOutline } from './layer-types.js';
2
+ /** Every file the viewer reads from a specs directory: charts, journeys, notations and notes. */
3
+ export declare function isSpecFile(file: string): boolean;
4
+ /** The notation of a spec file. Anything else is named by its extension. */
5
+ export declare function specKindOf(file: string): string;
6
+ /** The title of a spec: the `Feature:` line of a `.feature`, the `module` name of a `.qnt`, otherwise the file's base name. */
7
+ export declare function specTitleOf(file: string, text: string): string;
8
+ /** One spec file: its `specs/<relative path>` name and its UTF-8 text. */
9
+ export type SpecFileText = {
10
+ readonly path: string;
11
+ readonly text: string;
12
+ };
13
+ /**
14
+ * The spec files of a directory, named `specs/<path inside it>`: the one definition of what a spec is,
15
+ * shared by the outline a report is laid over and by the specs that are published. Hidden files and
16
+ * folders, `node_modules`, `dist`, files over 2 MiB and files of other types (pictures, build output) are
17
+ * left out. Sorted by path. Rejects if `directory` does not exist.
18
+ */
19
+ export declare function collectSpecFiles(directory: string): Promise<SpecFileText[]>;
20
+ /**
21
+ * The specs of a directory as the outline a report is laid over. Each spec is named
22
+ * `specs/<its path inside the directory>`: that is the `specPath` a check uses to say which spec it
23
+ * belongs to. `domain` groups specs in the viewer; it defaults to the project's folder name.
24
+ *
25
+ * `repoRoot` is where `git rev-parse HEAD` is asked for the outline's `sha` (default: `directory`); the `sha` is
26
+ * null outside git. `changedFiles` is always empty. The returned `readSpec(specPath)` answers the text read here,
27
+ * or null for a path that is not in the outline.
28
+ *
29
+ * @example
30
+ * const outline = await loadDirectoryOutline('./specs', { repoRoot: process.cwd() });
31
+ * outline.specs.map(spec => spec.path); // ['specs/join.feature', ...]
32
+ */
33
+ export declare function loadDirectoryOutline(directory: string, options?: {
34
+ readonly domain?: string;
35
+ readonly repoRoot?: string;
36
+ }): Promise<ReportOutline & {
37
+ readSpec: (specPath: string) => Promise<string | null>;
38
+ }>;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * @module Spec outline from a directory
3
+ *
4
+ * Defines what a spec file is and reads a specs directory into the outline a report is laid over: one entry per
5
+ * spec, named `specs/<path inside the directory>`, with its notation, title and a fingerprint of its text. The same
6
+ * definition feeds the specs that are published, so the outline and the published spec set agree. Node-only
7
+ * (reads the file system and asks `git` for the commit); nothing is written.
8
+ *
9
+ * @see docs/report-contract.md (the layer document's `specs`, and `specPath`)
10
+ */
11
+ import { execFile } from 'node:child_process';
12
+ import { readdir, readFile, stat } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { promisify } from 'node:util';
15
+ import { isStatechartFile } from '../../schema/src/spec.js';
16
+ import { textFingerprint } from './layer-merge.js';
17
+ const exec = promisify(execFile);
18
+ const SKIPPED = new Set(['node_modules', 'dist', '.git', '.cache']);
19
+ const MAX_BYTES = 2 * 1024 * 1024;
20
+ /** Every file the viewer reads from a specs directory: charts, journeys, notations and notes. */
21
+ export function isSpecFile(file) {
22
+ return /\.scxml$/i.test(file) || /(^|\/)([\w.-]+\.)?machine\.(ya?ml|json)$/.test(file) || isStatechartFile(file) || /\.(feature|qnt|dmn|als|md|ya?ml|json|xml|csv|txt)$/i.test(file);
23
+ }
24
+ /** The notation of a spec file. Anything else is named by its extension. */
25
+ export function specKindOf(file) {
26
+ if (/\.feature$/i.test(file))
27
+ return 'gherkin';
28
+ if (/\.qnt$/i.test(file))
29
+ return 'quint';
30
+ if (/\.dmn$/i.test(file))
31
+ return 'dmn';
32
+ if (/\.als$/i.test(file))
33
+ return 'alloy';
34
+ if (/\.openslo\.ya?ml$/i.test(file))
35
+ return 'openslo';
36
+ const extension = file.split('.').at(-1);
37
+ return extension && extension !== file ? extension.toLowerCase() : 'document';
38
+ }
39
+ /** The title of a spec: the `Feature:` line of a `.feature`, the `module` name of a `.qnt`, otherwise the file's base name. */
40
+ export function specTitleOf(file, text) {
41
+ if (/\.feature$/i.test(file))
42
+ return /^\s*Feature:\s*(.+)$/m.exec(text)?.[1]?.trim() || path.posix.basename(file);
43
+ if (/\.qnt$/i.test(file))
44
+ return /^\s*module\s+(\w+)/m.exec(text)?.[1] || path.posix.basename(file);
45
+ return path.posix.basename(file);
46
+ }
47
+ /**
48
+ * The spec files of a directory, named `specs/<path inside it>`: the one definition of what a spec is,
49
+ * shared by the outline a report is laid over and by the specs that are published. Hidden files and
50
+ * folders, `node_modules`, `dist`, files over 2 MiB and files of other types (pictures, build output) are
51
+ * left out. Sorted by path. Rejects if `directory` does not exist.
52
+ */
53
+ export async function collectSpecFiles(directory) {
54
+ const root = path.resolve(directory);
55
+ const found = [];
56
+ async function walk(dir) {
57
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
58
+ if (entry.name.startsWith('.') || SKIPPED.has(entry.name))
59
+ continue;
60
+ const full = path.join(dir, entry.name);
61
+ if (entry.isDirectory())
62
+ await walk(full);
63
+ else if (entry.isFile()) {
64
+ const relative = path.relative(root, full).split(path.sep).join('/');
65
+ if (isSpecFile(relative) && (await stat(full)).size <= MAX_BYTES)
66
+ found.push({ path: `specs/${relative}`, text: await readFile(full, 'utf8') });
67
+ }
68
+ }
69
+ }
70
+ await walk(root);
71
+ return found.sort((a, b) => (a.path < b.path ? -1 : 1));
72
+ }
73
+ async function git(cwd, args) {
74
+ try {
75
+ return (await exec('git', args, { cwd })).stdout.trim() || null;
76
+ }
77
+ catch {
78
+ return null;
79
+ }
80
+ }
81
+ /**
82
+ * The specs of a directory as the outline a report is laid over. Each spec is named
83
+ * `specs/<its path inside the directory>`: that is the `specPath` a check uses to say which spec it
84
+ * belongs to. `domain` groups specs in the viewer; it defaults to the project's folder name.
85
+ *
86
+ * `repoRoot` is where `git rev-parse HEAD` is asked for the outline's `sha` (default: `directory`); the `sha` is
87
+ * null outside git. `changedFiles` is always empty. The returned `readSpec(specPath)` answers the text read here,
88
+ * or null for a path that is not in the outline.
89
+ *
90
+ * @example
91
+ * const outline = await loadDirectoryOutline('./specs', { repoRoot: process.cwd() });
92
+ * outline.specs.map(spec => spec.path); // ['specs/join.feature', ...]
93
+ */
94
+ export async function loadDirectoryOutline(directory, options = {}) {
95
+ const root = path.resolve(directory);
96
+ const files = await collectSpecFiles(root);
97
+ const texts = new Map(files.map(file => [file.path, file.text]));
98
+ const domain = options.domain ?? (path.basename(options.repoRoot ?? root) || 'product');
99
+ const specs = files.map(file => ({ path: file.path, domain, kind: specKindOf(file.path), title: specTitleOf(file.path, file.text), fingerprint: textFingerprint(file.text) }));
100
+ const cwd = options.repoRoot ?? root;
101
+ const sha = await git(cwd, ['rev-parse', 'HEAD']);
102
+ return { specs, sha: sha && /^[0-9a-f]{40}$/.test(sha) ? sha : null, changedFiles: [], readSpec: async (specPath) => texts.get(specPath) ?? null };
103
+ }
@@ -0,0 +1,20 @@
1
+ import type { ObjectStore, PagedListing } from './object-store.js';
2
+ /**
3
+ * Creates an {@link ObjectStore} on a directory: one file per key, its content type in a sidecar file. The
4
+ * backend for a CI workspace, a downloaded artifact or a laptop. Writes are atomic, listing reads
5
+ * only the directories a prefix can reach, and two stores over copies of the same layers can be
6
+ * merged with `cp -R` because layers are immutable and addressed by content.
7
+ *
8
+ * The directory need not exist yet: `put` creates it, and `get`/`head`/`listKeys` on a missing one answer
9
+ * null/false/`[]`. A missing sidecar reads as `application/octet-stream`. `put` writes the content type before
10
+ * the bytes, so an object is never visible without its type. `listKeys` returns keys in code-unit order, skips
11
+ * temporary and sidecar files, and throws on a prefix that is absolute, contains a backslash or `..`. `getUri`
12
+ * is a `file://` URL. Also implements `listPage` (`after` is exclusive, `limit` defaults to 1000, `next` is the
13
+ * last returned key while more remain).
14
+ *
15
+ * @example
16
+ * const store = directoryStore('./atlas-reports');
17
+ * await store.put('layers/blobs/x', new TextEncoder().encode('hi'), 'text/plain');
18
+ * await store.listKeys('layers/'); // ['layers/blobs/x']
19
+ */
20
+ export declare function directoryStore(root: string): ObjectStore & PagedListing;
@@ -0,0 +1,166 @@
1
+ /**
2
+ * @module Directory-backed object store
3
+ *
4
+ * The {@link ObjectStore} implementation for a local folder: a CI workspace, a downloaded artifact, a mount or a
5
+ * laptop. Each key is a file under the root; its content type is kept in a sidecar `<key>.content-type`. Writes go
6
+ * to a temporary `.tmp-*` file and are renamed into place so a reader sees all of an object or none of it. Keys
7
+ * that could escape the root (`..`, absolute, backslash, empty segments) or collide with the sidecar or temporary
8
+ * names are rejected with an error. Node/Bun only.
9
+ *
10
+ * @see docs/report-contract.md (backends)
11
+ */
12
+ import { mkdir, readFile, readdir, rename, rm, stat, writeFile } from 'node:fs/promises';
13
+ import path from 'node:path';
14
+ import { pathToFileURL } from 'node:url';
15
+ import { ObjectStoreWithPrefix } from './store-prefix.js';
16
+ const TYPE_SUFFIX = '.content-type';
17
+ const TEMP_PREFIX = '.tmp-';
18
+ function rejectKey(key) {
19
+ const parts = key.split('/');
20
+ const last = parts.at(-1) ?? '';
21
+ if (key.length === 0 || key.startsWith('/') || key.includes('\\') || parts.some((part) => part.length === 0 || part === '.' || part === '..') || last.endsWith(TYPE_SUFFIX) || last.startsWith(TEMP_PREFIX)) {
22
+ throw new Error(`Invalid object store key: ${key}`);
23
+ }
24
+ }
25
+ function fileOf(root, key) {
26
+ rejectKey(key);
27
+ const rootPath = path.resolve(root);
28
+ const file = path.resolve(rootPath, key);
29
+ if (file !== rootPath && !file.startsWith(rootPath + path.sep))
30
+ throw new Error(`Invalid object store key: ${key}`);
31
+ return file;
32
+ }
33
+ function missing(error) {
34
+ const code = error.code;
35
+ return code === 'ENOENT' || code === 'ENOTDIR';
36
+ }
37
+ let counter = 0;
38
+ /** Write beside the target and rename into place: a reader sees the whole object or none of it. */
39
+ async function writeAtomically(file, bytes) {
40
+ const temporary = path.join(path.dirname(file), `${TEMP_PREFIX}${process.pid}-${Date.now().toString(36)}-${(counter += 1).toString(36)}`);
41
+ try {
42
+ await writeFile(temporary, bytes);
43
+ await rename(temporary, file);
44
+ }
45
+ catch (error) {
46
+ await rm(temporary, { force: true });
47
+ throw error;
48
+ }
49
+ }
50
+ /**
51
+ * Creates an {@link ObjectStore} on a directory: one file per key, its content type in a sidecar file. The
52
+ * backend for a CI workspace, a downloaded artifact or a laptop. Writes are atomic, listing reads
53
+ * only the directories a prefix can reach, and two stores over copies of the same layers can be
54
+ * merged with `cp -R` because layers are immutable and addressed by content.
55
+ *
56
+ * The directory need not exist yet: `put` creates it, and `get`/`head`/`listKeys` on a missing one answer
57
+ * null/false/`[]`. A missing sidecar reads as `application/octet-stream`. `put` writes the content type before
58
+ * the bytes, so an object is never visible without its type. `listKeys` returns keys in code-unit order, skips
59
+ * temporary and sidecar files, and throws on a prefix that is absolute, contains a backslash or `..`. `getUri`
60
+ * is a `file://` URL. Also implements `listPage` (`after` is exclusive, `limit` defaults to 1000, `next` is the
61
+ * last returned key while more remain).
62
+ *
63
+ * @example
64
+ * const store = directoryStore('./atlas-reports');
65
+ * await store.put('layers/blobs/x', new TextEncoder().encode('hi'), 'text/plain');
66
+ * await store.listKeys('layers/'); // ['layers/blobs/x']
67
+ */
68
+ export function directoryStore(root) {
69
+ const store = {
70
+ async get(key) {
71
+ const file = fileOf(root, key);
72
+ try {
73
+ // The type is beside the object: both are asked for at once.
74
+ const [content, contentType] = await Promise.all([readFile(file), readFile(file + TYPE_SUFFIX, 'utf8').catch(() => 'application/octet-stream')]);
75
+ const bytes = content;
76
+ return {
77
+ body: new ReadableStream({
78
+ start(controller) {
79
+ controller.enqueue(bytes);
80
+ controller.close();
81
+ }
82
+ }),
83
+ contentType: contentType.trim() || 'application/octet-stream',
84
+ size: bytes.byteLength
85
+ };
86
+ }
87
+ catch (error) {
88
+ if (missing(error))
89
+ return null;
90
+ throw error;
91
+ }
92
+ },
93
+ async put(key, bytes, contentType) {
94
+ const file = fileOf(root, key);
95
+ await mkdir(path.dirname(file), { recursive: true });
96
+ // The type first: an object is never visible without it.
97
+ await writeAtomically(file + TYPE_SUFFIX, contentType);
98
+ await writeAtomically(file, bytes);
99
+ },
100
+ async head(key) {
101
+ try {
102
+ return (await stat(fileOf(root, key))).isFile();
103
+ }
104
+ catch (error) {
105
+ if (missing(error))
106
+ return false;
107
+ throw error;
108
+ }
109
+ },
110
+ async delete(key) {
111
+ const file = fileOf(root, key);
112
+ await rm(file, { force: true });
113
+ await rm(file + TYPE_SUFFIX, { force: true });
114
+ },
115
+ async deleteMany(keys) {
116
+ for (const key of keys)
117
+ await store.delete(key);
118
+ },
119
+ async getUri(key) {
120
+ return (await store.head(key)) ? pathToFileURL(fileOf(root, key)).href : null;
121
+ },
122
+ async listKeys(prefix = '') {
123
+ const rootPath = path.resolve(root);
124
+ const found = [];
125
+ const walk = async (dir) => {
126
+ let entries;
127
+ try {
128
+ entries = await readdir(dir, { withFileTypes: true });
129
+ }
130
+ catch (error) {
131
+ if (missing(error))
132
+ return;
133
+ throw error;
134
+ }
135
+ await Promise.all(entries.map(async (entry) => {
136
+ if (entry.name.startsWith(TEMP_PREFIX) || entry.name.endsWith(TYPE_SUFFIX))
137
+ return;
138
+ const full = path.join(dir, entry.name);
139
+ if (entry.isDirectory())
140
+ return walk(full);
141
+ if (!entry.isFile())
142
+ return;
143
+ const key = path.relative(rootPath, full).split(path.sep).join('/');
144
+ if (key.startsWith(prefix))
145
+ found.push(key);
146
+ }));
147
+ };
148
+ // Only the directory a prefix names can hold its keys.
149
+ const slash = prefix.lastIndexOf('/');
150
+ if (prefix.startsWith('/') || prefix.includes('\\') || prefix.split('/').some((part) => part === '..'))
151
+ throw new Error(`Invalid object store prefix: ${prefix}`);
152
+ await walk(slash < 0 ? rootPath : path.resolve(rootPath, prefix.slice(0, slash)));
153
+ return found.sort();
154
+ },
155
+ async listPage(prefix, options = {}) {
156
+ const all = await store.listKeys(prefix);
157
+ const from = options.after === undefined ? 0 : all.findIndex(key => key > options.after);
158
+ const keys = from < 0 ? [] : all.slice(from, from + (options.limit ?? 1000));
159
+ return { keys, next: from >= 0 && from + keys.length < all.length ? keys.at(-1) ?? null : null };
160
+ },
161
+ withPrefix(prefix) {
162
+ return new ObjectStoreWithPrefix(store, prefix);
163
+ }
164
+ };
165
+ return store;
166
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * @module Store doctor and IAM policy
3
+ *
4
+ * `inspectStore` checks that a store works end to end (credentials, listing, what it holds, writing and reading back a
5
+ * scratch object) and says what to fix when it does not; `iamPolicy` prints the least-privilege AWS policy for publishers
6
+ * and readers. Used by `atlas report doctor` and `atlas report policy`, for folders, S3-compatible buckets and Atlas
7
+ * servers alike.
8
+ *
9
+ * @see docs/deploying.md
10
+ */
11
+ import type { CliBackend } from './cli-stores.js';
12
+ /** One thing `inspectStore` checked: its `name` (`store`, `list`, `contents`, `write`, …), whether it is `ok`, what was seen, and, when it is not, what to `fix`. */
13
+ export type Check = {
14
+ readonly ok: boolean;
15
+ readonly name: string;
16
+ readonly detail: string;
17
+ readonly fix?: string;
18
+ };
19
+ /** The least an account needs, as an AWS IAM policy. R2, MinIO and the rest have their own policy languages: grant the same actions. */
20
+ export declare function iamPolicy(location: {
21
+ bucket: string;
22
+ prefix: string;
23
+ }, role: 'publisher' | 'reader' | 'both'): object;
24
+ /** What the account being checked is for: `publisher` (writes layers), `reader` (reads them) or `both`. It decides which checks run. */
25
+ export type Role = 'publisher' | 'reader' | 'both';
26
+ /** Is this store usable for Atlas, and if not, what exactly is wrong? Each step says what to do. */
27
+ export declare function inspectStore(backend: CliBackend, options?: {
28
+ readonly role?: Role;
29
+ }): Promise<Check[]>;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * @module Store doctor and IAM policy
3
+ *
4
+ * `inspectStore` checks that a store works end to end (credentials, listing, what it holds, writing and reading back a
5
+ * scratch object) and says what to fix when it does not; `iamPolicy` prints the least-privilege AWS policy for publishers
6
+ * and readers. Used by `atlas report doctor` and `atlas report policy`, for folders, S3-compatible buckets and Atlas
7
+ * servers alike.
8
+ *
9
+ * @see docs/deploying.md
10
+ */
11
+ import { LAYER_INDEX } from './layer-store.js';
12
+ import { verifyObjectStore } from './object-store-conformance.js';
13
+ /** The least an account needs, as an AWS IAM policy. R2, MinIO and the rest have their own policy languages: grant the same actions. */
14
+ export function iamPolicy(location, role) {
15
+ const base = location.prefix ? `${location.prefix}/` : '';
16
+ const bucketArn = `arn:aws:s3:::${location.bucket}`;
17
+ const statements = [];
18
+ if (role !== 'reader') {
19
+ statements.push({ Sid: 'AtlasPublish', Effect: 'Allow', Action: ['s3:PutObject', 's3:GetObject'], Resource: `${bucketArn}/${base}layers/*` }, { Sid: 'AtlasDoctorCleanup', Effect: 'Allow', Action: 's3:DeleteObject', Resource: `${bucketArn}/${base}layers/doctor-*` });
20
+ }
21
+ if (role !== 'publisher') {
22
+ statements.push({ Sid: 'AtlasRead', Effect: 'Allow', Action: 's3:GetObject', Resource: `${bucketArn}/${base}layers/*` });
23
+ }
24
+ // Publishers need this too: without it S3 answers 403, not 404, when asked whether a layer exists yet.
25
+ statements.push({ Sid: 'AtlasList', Effect: 'Allow', Action: 's3:ListBucket', Resource: bucketArn, // IfExists: a listing always carries a prefix and is held to it; a HEAD or GET of one object carries none, and
26
+ // must still be answered "not found" rather than "forbidden".
27
+ ...(base ? { Condition: { StringLikeIfExists: { 's3:prefix': [base, `${base}*`] } } } : {}) });
28
+ return { Version: '2012-10-17', Statement: statements };
29
+ }
30
+ const when = (inverted) => {
31
+ const time = inverted ? Number.MAX_SAFE_INTEGER - Number(inverted) : NaN;
32
+ return Number.isFinite(time) ? new Date(time).toISOString() : 'unknown';
33
+ };
34
+ /** Is this store usable for Atlas, and if not, what exactly is wrong? Each step says what to do. */
35
+ export async function inspectStore(backend, options = {}) {
36
+ const role = options.role ?? 'both';
37
+ const checks = [];
38
+ const add = (check) => { checks.push(check); return check.ok; };
39
+ const store = backend.store;
40
+ const s3 = backend.s3;
41
+ add({ ok: true, name: 'store', detail: s3?.label ?? backend.label ?? backend.id });
42
+ if (s3 && role !== 'publisher') {
43
+ const ping = await s3.ping();
44
+ add(ping.ok
45
+ ? { ok: true, name: 'list', detail: `${ping.ms} ms${s3.credentialSource() ? `, credentials from ${s3.credentialSource()}` : ''}` }
46
+ : { ok: false, name: 'list', detail: ping.error ?? 'failed', fix: 'Check the bucket, region and endpoint in the store URL, and the credentials. `atlas report policy --store <url>` prints the permissions needed.' });
47
+ if (!ping.ok)
48
+ return checks;
49
+ }
50
+ if (role !== 'publisher') {
51
+ try {
52
+ const keys = await store.listKeys(LAYER_INDEX);
53
+ const [sets, runs] = await Promise.all([store.listKeys('layers/specs/index/'), store.listKeys('layers/runs/index/')]);
54
+ add({ ok: true, name: 'contents', detail: `${keys.length} layer${keys.length === 1 ? '' : 's'}${keys.length ? ` (newest ${when(/\/(\d{16})-/.exec(keys[0])?.[1])})` : ''}, ${sets.length} spec set${sets.length === 1 ? '' : 's'}, ${runs.length} run${runs.length === 1 ? '' : 's'}` });
55
+ }
56
+ catch (error) {
57
+ add({ ok: false, name: 'contents', detail: error instanceof Error ? error.message : String(error) });
58
+ return checks;
59
+ }
60
+ }
61
+ if (role === 'reader')
62
+ return checks;
63
+ const scratch = `layers/doctor-${Math.random().toString(36).slice(2, 10)}/`;
64
+ if (role === 'both') {
65
+ const verification = await verifyObjectStore(store, scratch);
66
+ add(verification.ok
67
+ ? { ok: true, name: 'write', detail: 'wrote, listed, read back and deleted a scratch object: the store meets the ObjectStore contract' }
68
+ : { ok: false, name: 'write', detail: verification.failures.join('; '), fix: 'Publishers need s3:PutObject and s3:GetObject on layers/*, and the doctor s3:DeleteObject on layers/doctor-*. To check a publisher-only or read-only account, pass --role publisher or --role reader.' });
69
+ return checks;
70
+ }
71
+ // A publisher cannot list: prove what it does, which is put, head and get.
72
+ const key = `${scratch}probe.txt`;
73
+ try {
74
+ await store.put(key, new TextEncoder().encode('atlas'), 'text/plain');
75
+ const found = await store.head(key);
76
+ await store.delete(key).catch(() => { });
77
+ add(found ? { ok: true, name: 'write', detail: 'wrote and found a scratch object' } : { ok: false, name: 'write', detail: 'an object was written but could not be found again', fix: 'The account needs s3:GetObject (HeadObject) on layers/*.' });
78
+ }
79
+ catch (error) {
80
+ add({ ok: false, name: 'write', detail: error instanceof Error ? error.message : String(error), fix: 'Publishers need s3:PutObject and s3:GetObject on layers/*, and s3:ListBucket on the bucket (S3 answers 403 instead of 404 for a missing object without it).' });
81
+ }
82
+ return checks;
83
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @module Report deltas
3
+ *
4
+ * The client half of "send only what changed": `applyEffectiveDelta` turns the report a viewer holds and a delta from the
5
+ * server (`GET /api/reports/evidence/effective?since=<version>`) into the current report. Pure and free of Node APIs, so a
6
+ * browser bundle uses it. The server half is `ReportIndex.delta`; a test holds the two together over random runs.
7
+ *
8
+ * @see docs/server.md#reading-only-what-changed
9
+ */
10
+ import type { LayerEvidence } from './evidence-types.js';
11
+ import type { EffectiveEvidence } from './report-index.js';
12
+ /**
13
+ * What a server answers when a viewer asks for the report `since` a version it holds and the server still
14
+ * remembers that version: only what changed. `layers` are whole layers (with the rows that win), replacing those
15
+ * of the same id; `removed` are layers that no longer win anything. Everything else, `backends` and `notices`
16
+ * and the count, is sent whole because it is small.
17
+ *
18
+ * It is an optimisation and nothing else: applying it gives exactly the report the server would have sent in
19
+ * full, and a server that does not know it sends the full report, which a viewer takes as it always did.
20
+ */
21
+ export type EffectiveDelta = Omit<EffectiveEvidence, 'layers'> & {
22
+ readonly delta: true;
23
+ /** The version this applies to. */
24
+ readonly base: string;
25
+ readonly layers: readonly LayerEvidence[];
26
+ readonly removed: readonly string[];
27
+ };
28
+ /** Is `value` a delta (`delta: true`, a `base` version, `layers` and `removed`) and not a whole report? Decides which of the two a server answered. */
29
+ export declare const isEffectiveDelta: (value: unknown) => value is EffectiveDelta;
30
+ /**
31
+ * The report a delta leads to, or null when it does not apply to `held` (a different version): ask for the
32
+ * whole report then.
33
+ */
34
+ export declare function applyEffectiveDelta(held: EffectiveEvidence, delta: EffectiveDelta): EffectiveEvidence | null;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @module Report deltas
3
+ *
4
+ * The client half of "send only what changed": `applyEffectiveDelta` turns the report a viewer holds and a delta from the
5
+ * server (`GET /api/reports/evidence/effective?since=<version>`) into the current report. Pure and free of Node APIs, so a
6
+ * browser bundle uses it. The server half is `ReportIndex.delta`; a test holds the two together over random runs.
7
+ *
8
+ * @see docs/server.md#reading-only-what-changed
9
+ */
10
+ /** Is `value` a delta (`delta: true`, a `base` version, `layers` and `removed`) and not a whole report? Decides which of the two a server answered. */
11
+ export const isEffectiveDelta = (value) => typeof value === 'object' && value !== null && value.delta === true && typeof value.base === 'string' && Array.isArray(value.layers) && Array.isArray(value.removed);
12
+ /** The report order: newest first, ties by id. */
13
+ const byRecency = (a, b) => b.layer.recordedAtMs - a.layer.recordedAtMs || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
14
+ /**
15
+ * The report a delta leads to, or null when it does not apply to `held` (a different version): ask for the
16
+ * whole report then.
17
+ */
18
+ export function applyEffectiveDelta(held, delta) {
19
+ if (held.version !== delta.base)
20
+ return null;
21
+ const gone = new Set(delta.removed);
22
+ const replaced = new Set(delta.layers.map(layer => layer.id));
23
+ const layers = [...held.layers.filter(layer => !gone.has(layer.id) && !replaced.has(layer.id)), ...delta.layers].sort(byRecency);
24
+ return { version: delta.version, layers, backends: delta.backends, notices: delta.notices, totalLayers: delta.totalLayers };
25
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * @module Evidence of one result
3
+ *
4
+ * Reads the evidence (attachments, steps, annotations, attempts, tags, labels, links, extensions) that a layer, a
5
+ * part or a check carries beyond its outcome: the short summary that rides in every report row, one result's full
6
+ * detail, and the bundle keys its files live under. It only reads; evidence never decides a status, a slot or a
7
+ * winner, so nothing here affects who wins.
8
+ *
9
+ * @see docs/extending.md
10
+ * @see docs/report-contract.md (evidence)
11
+ */
12
+ import type { Annotation, Attachment, Attempt, Check, Step } from '../../schema/src/layer.js';
13
+ import type { EvidenceSummary } from './evidence-types.js';
14
+ /** Anything that may carry evidence: a layer, a part or a check. */
15
+ export type EvidenceHolder = {
16
+ readonly tags?: readonly string[];
17
+ readonly labels?: Readonly<Record<string, string>>;
18
+ readonly links?: readonly {
19
+ readonly url: string;
20
+ readonly name?: string | undefined;
21
+ readonly rel?: string | undefined;
22
+ }[];
23
+ readonly attachments?: readonly Attachment[];
24
+ readonly annotations?: readonly Annotation[];
25
+ readonly steps?: readonly Step[];
26
+ readonly attempts?: readonly Attempt[];
27
+ readonly extensions?: Readonly<Record<string, unknown>>;
28
+ };
29
+ /**
30
+ * What a result carries beyond its outcome, as counts and short facts. It rides in every row of the
31
+ * report so a list can show "3 attachments, 12 steps" without opening anything; the evidence itself is
32
+ * read when someone asks for it. Undefined when there is none.
33
+ *
34
+ * Several holders (for example a check and its layer) are summed: `attachments` also counts those on steps and
35
+ * attempts, `steps` counts only each holder's own top-level steps, tags are de-duplicated, and for a repeated
36
+ * label the later holder wins. `roles` are the attachments' roles (or, without one, what their media type or file
37
+ * name implies), sorted and distinct.
38
+ */
39
+ export declare function summarizeEvidence(...holders: readonly EvidenceHolder[]): EvidenceSummary | undefined;
40
+ /** A role to show when a producer gave none, from the media type: `image`, `video`, `audio`, `snapshot` (text/html), `text` (text, JSON, XML) or `file`. Roles are an open vocabulary; a producer's own role always wins. */
41
+ export declare function roleOfType(contentType: string | undefined): string;
42
+ /** All of one result's evidence, read when someone opens it. */
43
+ export type CheckDetail = {
44
+ /** The id of the layer the check was read from. */
45
+ readonly layer: string;
46
+ /** The kind of the part that holds the check. */
47
+ readonly kind: string;
48
+ /** The check as recorded, with all of its evidence. */
49
+ readonly check: Check;
50
+ /** What the run as a whole carries: its logs, its links, its labels. */
51
+ readonly run: EvidenceHolder;
52
+ };
53
+ /** One result's evidence, from a layer: the first check with id `checkId` (in a part of kind `kind`, when given) plus the layer's own evidence. Null when the layer holds no such check. */
54
+ export declare function checkDetailOf(layer: {
55
+ readonly id: string;
56
+ readonly parts: readonly {
57
+ readonly kind: string;
58
+ readonly checks: readonly Check[];
59
+ }[];
60
+ } & EvidenceHolder, checkId: string, kind?: string): CheckDetail | null;
61
+ /** What a layer carries as a whole (its tags, labels, links, attachments, annotations and extensions; not its parts' checks). Absent fields stay absent. */
62
+ export declare function runEvidence(layer: EvidenceHolder): EvidenceHolder;
63
+ /** Every distinct bundle key (`bundleKey`) that the attachments of a holder, its steps and its attempts point to, so a layer's files can be copied. Attachments held by `uri` or inline `text` have none. */
64
+ export declare function bundleKeysOf(holder: EvidenceHolder): string[];