@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,337 @@
1
+ /**
2
+ * @module Incremental report index
3
+ *
4
+ * `ReportIndex` is the report, maintained as layers arrive. Each immutable layer is read once and folded into the winning
5
+ * result of every slot it fills, then dropped unless it still holds a winner, so memory follows the number of checks and not
6
+ * the age of the project. The winner rule is a total order, so folding is exact in any order and equals combining every layer
7
+ * from scratch (a test proves it over random partial runs). It also keeps what each layer sends (rows, JSON, a digest) until
8
+ * that layer changes, so taking in a result costs one layer, answers viewers a version hash, a cached JSON text and a delta,
9
+ * and tells listeners what arrived.
10
+ *
11
+ * @see docs/report-contract.md
12
+ * @see docs/server.md#performance
13
+ */
14
+ import { createHash } from 'node:crypto';
15
+ import { eachLimited } from './completion-order.js';
16
+ import { LAYER_INDEX, layerSummary, readLayer } from './layer-store.js';
17
+ import { layerEvidence, layerResultKeys, replaces } from './layer-merge.js';
18
+ import { readUtf8 } from './object-io.js';
19
+ import { isPaged } from './object-store.js';
20
+ const RETRY_FAILED_MS = 5 * 60_000;
21
+ /** Versions a viewer may still be holding, remembered so it can be sent only what changed. */
22
+ const REMEMBERED_VERSIONS = 256;
23
+ const REMEMBERED_REMOVALS = 4096;
24
+ /**
25
+ * The report, maintained as layers arrive. Each layer is read once and folded into the winning result
26
+ * of every slot it fills, then dropped unless it still holds a winner. Because the winner rule is a
27
+ * total order, folding is exact in any order, so this equals combining every layer from scratch, with
28
+ * memory proportional to the number of checks and not to how long the project has existed.
29
+ *
30
+ * It reads only immutable layers (`layers/index/…`), which is what makes "read once" correct.
31
+ */
32
+ export class ReportIndex {
33
+ #backends;
34
+ #concurrency;
35
+ #now;
36
+ #winners = new Map();
37
+ #held = new Map();
38
+ /** The smallest backend id a layer was found in, so attribution does not depend on read order. */
39
+ #attribution = new Map();
40
+ #folded = new Set();
41
+ #failed = new Map();
42
+ #available = new Map();
43
+ /** Counts every change to what the report shows. A layer remembers the count at which it last changed. */
44
+ #rev = 0;
45
+ /** The counts at which versions were seen, oldest first. */
46
+ #versions = new Map();
47
+ /** Layers that stopped holding any winner, and when. Older removals are forgotten; `#forgotten` says how far. */
48
+ #removed = [];
49
+ #forgotten = 0;
50
+ #refreshing = null;
51
+ #timer = null;
52
+ #refreshedAt = 0;
53
+ #cached = null;
54
+ #include;
55
+ #fullListEvery;
56
+ #listeners = new Set();
57
+ #added = [];
58
+ #announced = '';
59
+ #passes = 0;
60
+ /** Layers handed over by an uploader, read one at a time in the order they came. */
61
+ #accepting = Promise.resolve();
62
+ constructor(backends, options = {}) {
63
+ this.#backends = backends;
64
+ this.#include = options.include ?? (() => true);
65
+ this.#fullListEvery = Math.max(1, options.fullListEvery ?? 30);
66
+ this.#concurrency = options.concurrency ?? 64;
67
+ this.#now = options.now ?? Date.now;
68
+ }
69
+ /** Read what is new in every backend and fold it in. Resolves true when the report changed. Calls overlap safely. */
70
+ refresh() {
71
+ this.#refreshing ??= this.#refresh().finally(() => { this.#refreshing = null; });
72
+ return this.#refreshing;
73
+ }
74
+ /**
75
+ * A refresh that surely starts after this call: one already under way may have looked before whatever
76
+ * made the caller ask. Used when something was just written.
77
+ */
78
+ async refreshNow() {
79
+ const running = this.#refreshing;
80
+ const earlier = running ? await running.catch(() => false) : false;
81
+ return (await this.refresh()) || earlier;
82
+ }
83
+ /**
84
+ * Take in a layer an uploader has just written, from its catalog entry, without listing anything. Entries
85
+ * are read one after another; one already known is ignored. The regular refresh still reconciles the rest.
86
+ */
87
+ accept(backendId, indexKey, known) {
88
+ const backend = this.#backends.find(item => item.id === backendId);
89
+ if (!backend)
90
+ return Promise.resolve();
91
+ this.#accepting = this.#accepting.then(async () => {
92
+ const mark = `${backend.id}|${indexKey}`;
93
+ if (this.#folded.has(mark))
94
+ return;
95
+ await this.#load(backend, indexKey, known);
96
+ this.#emit();
97
+ }).catch(() => { });
98
+ return this.#accepting;
99
+ }
100
+ /** Be told when the report changes. Returns how to stop. */
101
+ onChange(listener) {
102
+ this.#listeners.add(listener);
103
+ return () => { this.#listeners.delete(listener); };
104
+ }
105
+ #emit() {
106
+ if (!this.#listeners.size && !this.#added.length)
107
+ return;
108
+ const { version, totalLayers } = this.snapshot();
109
+ // Say it once per distinct report, and whenever layers arrived (they are news even if nothing they hold wins).
110
+ if (version === this.#announced && !this.#added.length)
111
+ return;
112
+ this.#announced = version;
113
+ const added = this.#added.sort((a, b) => b.recordedAtMs - a.recordedAtMs || (a.id < b.id ? -1 : 1));
114
+ this.#added = [];
115
+ for (const listener of this.#listeners) {
116
+ try {
117
+ listener({ version, added, totalLayers });
118
+ }
119
+ catch { /* a listener that fails does not stop the others */ }
120
+ }
121
+ }
122
+ /** Keep refreshing in the background. The first call returns once the first pass has finished. */
123
+ async start(intervalMs) {
124
+ await this.refresh();
125
+ this.#timer = setInterval(() => { void this.refresh().catch(() => { }); }, intervalMs);
126
+ this.#timer.unref?.();
127
+ }
128
+ /** Stops the background refresh started by `start`. Safe to call twice. */
129
+ stop() {
130
+ if (this.#timer)
131
+ clearInterval(this.#timer);
132
+ this.#timer = null;
133
+ }
134
+ /** True once a first pass over the backends has finished, so `snapshot` is a report and not an empty guess. */
135
+ get ready() {
136
+ return this.#refreshedAt > 0;
137
+ }
138
+ async #refresh() {
139
+ const before = this.#rev;
140
+ const fresh = [];
141
+ // The whole catalog on the first pass and now and then; otherwise only its newest end, where new layers sort.
142
+ const full = this.#passes++ % this.#fullListEvery === 0;
143
+ await Promise.all(this.#backends.map(async (backend) => {
144
+ try {
145
+ const consider = (key) => { if (!this.#folded.has(`${backend.id}|${key}`) && !this.#recentlyFailed(`${backend.id}|${key}`))
146
+ fresh.push({ backend, key }); };
147
+ if (!full && isPaged(backend.store)) {
148
+ // Newest first: pages of keys we already know mean nothing newer is behind them.
149
+ for (let after;;) {
150
+ const page = await backend.store.listPage(LAYER_INDEX, { ...(after === undefined ? {} : { after }), limit: 1000 });
151
+ let unknown = 0;
152
+ for (const key of page.keys) {
153
+ if (!this.#folded.has(`${backend.id}|${key}`))
154
+ unknown += 1;
155
+ consider(key);
156
+ }
157
+ if (page.next === null || unknown < page.keys.length)
158
+ break;
159
+ after = page.next;
160
+ }
161
+ }
162
+ else {
163
+ for (const key of await backend.store.listKeys(LAYER_INDEX))
164
+ consider(key);
165
+ }
166
+ this.#available.set(backend.id, true);
167
+ this.#failed.delete(`${backend.id}|`);
168
+ }
169
+ catch (error) {
170
+ this.#available.set(backend.id, false);
171
+ this.#failed.set(`${backend.id}|`, { at: this.#now(), message: `${backend.label ?? backend.id} is unavailable: ${error instanceof Error ? error.message : String(error)}` });
172
+ }
173
+ }));
174
+ // Oldest first or newest first gives the same report; newest first shows recent work soonest.
175
+ fresh.sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : a.backend.id < b.backend.id ? -1 : 1));
176
+ await eachLimited(fresh, this.#concurrency, ({ backend, key }) => this.#load(backend, key));
177
+ this.#refreshedAt = this.#now();
178
+ this.#emit();
179
+ return this.#rev !== before;
180
+ }
181
+ async #load(backend, key, known) {
182
+ const mark = `${backend.id}|${key}`;
183
+ try {
184
+ let layer;
185
+ let id;
186
+ if (known && key.endsWith(`-${known.id}.json`)) {
187
+ // The caller has just written and read this layer: its entry names it, so there is nothing to look up.
188
+ id = known.id;
189
+ layer = known;
190
+ }
191
+ else {
192
+ const raw = await readUtf8(backend.store, key);
193
+ id = raw ? JSON.parse(raw).id : undefined;
194
+ layer = id ? await readLayer(backend.store, id) : null;
195
+ }
196
+ if (!id || !layer)
197
+ throw new Error('an indexed layer is missing');
198
+ this.#fold(layerEvidence(layerSummary(layer), backend.id), backend.id);
199
+ this.#folded.add(mark);
200
+ this.#failed.delete(mark);
201
+ this.#refreshedAt ||= this.#now();
202
+ }
203
+ catch (error) {
204
+ this.#failed.set(mark, { at: this.#now(), message: `${backend.label ?? backend.id}: a layer could not be read, so its outcome is unknown (${error instanceof Error ? error.message : String(error)})` });
205
+ }
206
+ }
207
+ #recentlyFailed(mark) {
208
+ const failure = this.#failed.get(mark);
209
+ return failure !== undefined && this.#now() - failure.at < RETRY_FAILED_MS;
210
+ }
211
+ #fold(evidence, backend) {
212
+ const id = evidence.id;
213
+ const known = this.#attribution.get(id);
214
+ if (known !== undefined) {
215
+ // The same immutable layer in another backend: one layer, attributed to the backend that sorts first.
216
+ if (backend < known) {
217
+ this.#attribution.set(id, backend);
218
+ const held = this.#held.get(id);
219
+ if (held)
220
+ this.#touch(held);
221
+ else
222
+ this.#rev += 1;
223
+ }
224
+ return;
225
+ }
226
+ this.#attribution.set(id, backend);
227
+ if (!this.#include(evidence.layer))
228
+ return;
229
+ this.#added.push({ ...evidence.layer, backend });
230
+ const wins = new Set();
231
+ const keyOf = layerResultKeys(evidence.layer);
232
+ for (const row of evidence.rows) {
233
+ const key = keyOf(row);
234
+ const result = { key, slot: '', layer: evidence.layer, row, parameters: evidence.layer.parameters, warning: null };
235
+ const prior = this.#winners.get(key);
236
+ if (prior && !replaces(result, prior))
237
+ continue;
238
+ if (prior)
239
+ this.#release(prior.layer.id, key);
240
+ this.#winners.set(key, result);
241
+ wins.add(key);
242
+ }
243
+ if (wins.size) {
244
+ this.#held.set(id, { evidence, wins, rev: ++this.#rev, entry: null, json: null, digest: null });
245
+ }
246
+ }
247
+ /** What this layer sends has changed. */
248
+ #touch(held) {
249
+ held.rev = ++this.#rev;
250
+ held.entry = held.json = held.digest = null;
251
+ }
252
+ #release(layerId, key) {
253
+ const held = this.#held.get(layerId);
254
+ if (!held)
255
+ return;
256
+ held.wins.delete(key);
257
+ if (held.wins.size) {
258
+ this.#touch(held);
259
+ return;
260
+ }
261
+ this.#held.delete(layerId);
262
+ this.#removed.push({ id: layerId, rev: ++this.#rev });
263
+ if (this.#removed.length > REMEMBERED_REMOVALS)
264
+ this.#forgotten = this.#removed.splice(0, this.#removed.length - REMEMBERED_REMOVALS).at(-1).rev;
265
+ }
266
+ #entryOf(held) {
267
+ if (!held.entry) {
268
+ const id = held.evidence.id;
269
+ const rows = [...held.wins].map(key => this.#winners.get(key).row);
270
+ held.entry = { id, layer: { ...held.evidence.layer, backend: this.#attribution.get(id) ?? held.evidence.layer.backend }, rows, fingerprints: held.evidence.fingerprints };
271
+ }
272
+ return held.entry;
273
+ }
274
+ /** The current report, ready to send. Built once per change, and only the layers that changed are rebuilt. */
275
+ snapshot() {
276
+ return this.#current().value;
277
+ }
278
+ #current() {
279
+ const backends = this.#backends.map(backend => ({ id: backend.id, label: backend.label ?? backend.id, live: false, available: this.#available.get(backend.id) ?? false })).sort((a, b) => (a.id < b.id ? -1 : 1));
280
+ const notices = [...new Set([...this.#failed.values()].map(failure => failure.message))].sort();
281
+ const key = [this.#rev, this.#folded.size, backends.map(item => (item.available ? 1 : 0)).join(''), notices.join('\n')].join('|');
282
+ if (this.#cached?.key === key)
283
+ return this.#cached;
284
+ const held = [...this.#held.values()];
285
+ held.sort((a, b) => b.evidence.layer.recordedAtMs - a.evidence.layer.recordedAtMs || (a.evidence.id < b.evidence.id ? -1 : a.evidence.id > b.evidence.id ? 1 : 0));
286
+ const layers = held.map(item => this.#entryOf(item));
287
+ // A layer's rows are fixed by its id and which of its slots it holds, so a digest of those says when the report differs.
288
+ const hash = createHash('sha256');
289
+ for (const item of held) {
290
+ item.digest ??= `${item.evidence.id}|${this.#attribution.get(item.evidence.id) ?? ''}|${[...item.wins].join('\u0001')}`;
291
+ hash.update(item.digest).update('\n');
292
+ }
293
+ hash.update(JSON.stringify([backends, notices, this.#attribution.size]));
294
+ const version = hash.digest('hex').slice(0, 20);
295
+ if (!this.#versions.has(version)) {
296
+ this.#versions.set(version, this.#rev);
297
+ if (this.#versions.size > REMEMBERED_VERSIONS)
298
+ this.#versions.delete(this.#versions.keys().next().value);
299
+ }
300
+ this.#cached = { key, value: { version, layers, backends, notices, totalLayers: this.#attribution.size }, json: null, held };
301
+ return this.#cached;
302
+ }
303
+ /**
304
+ * The report as JSON text: exactly what `JSON.stringify({ ...snapshot(), active: [] })` writes, put together from
305
+ * the text of each layer, which is written once. `active` is what a store without live runs always sends: none.
306
+ */
307
+ json() {
308
+ const current = this.#current();
309
+ if (current.json === null) {
310
+ const { value, held } = current;
311
+ current.json = `{"version":${JSON.stringify(value.version)},"layers":[${held.map(item => (item.json ??= JSON.stringify(this.#entryOf(item)))).join(',')}],"backends":${JSON.stringify(value.backends)},"notices":${JSON.stringify(value.notices)},"totalLayers":${value.totalLayers},"active":[]}`;
312
+ }
313
+ return { version: current.value.version, text: current.json };
314
+ }
315
+ /**
316
+ * What differs between the report a viewer holds (named by its version) and the current one: the layers that
317
+ * changed in full, and the ids of those that no longer hold a winner. Null when that version is not remembered,
318
+ * and the viewer should ask for the whole report. See `applyEffectiveDelta`.
319
+ */
320
+ delta(since) {
321
+ const current = this.#current();
322
+ const base = this.#versions.get(since);
323
+ if (base === undefined || base < this.#forgotten)
324
+ return null;
325
+ const changed = current.held.filter(item => item.rev > base);
326
+ const gone = [...new Set(this.#removed.filter(item => item.rev > base && !this.#held.has(item.id)).map(item => item.id))];
327
+ const { value } = current;
328
+ return {
329
+ version: value.version,
330
+ text: `{"version":${JSON.stringify(value.version)},"base":${JSON.stringify(since)},"delta":true,"layers":[${changed.map(item => (item.json ??= JSON.stringify(this.#entryOf(item)))).join(',')}],"removed":${JSON.stringify(gone)},"backends":${JSON.stringify(value.backends)},"notices":${JSON.stringify(value.notices)},"totalLayers":${value.totalLayers},"active":[]}`
331
+ };
332
+ }
333
+ /** For `/readyz` and logs. */
334
+ stats() {
335
+ return { backends: this.#backends.length, layers: this.#attribution.size, slots: this.#winners.size, holding: this.#held.size, failed: this.#failed.size, refreshedAt: this.#refreshedAt };
336
+ }
337
+ }
@@ -1,3 +1,10 @@
1
+ /**
2
+ * @module Test loading state
3
+ *
4
+ * The state of loading one recorded test into the viewer, and the interface a report gives the viewer to load tests on
5
+ * demand. Types only.
6
+ */
7
+ /** Where loading a test is: `ready`, `loading` (with how many frame files have arrived and how many there are, when known), `missing`, or `error` with a message to show. */
1
8
  export type TestLoad = {
2
9
  readonly kind: 'ready';
3
10
  } | {
@@ -16,7 +23,10 @@ export type TestLoad = {
16
23
  * (a standalone HTML file).
17
24
  */
18
25
  export type ReportTestLoader = {
26
+ /** Starts loading a test's timeline if it has not started (or failed before). Returns at once. */
19
27
  readonly load: (testId: string) => void;
28
+ /** Starts fetching one frame file of a test if it is not held or already being fetched. */
20
29
  readonly ensureBlob?: (testId: string, file: string) => void;
30
+ /** The current load state of a test; reactive where the host is. */
21
31
  readonly state: (testId: string) => TestLoad;
22
32
  };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @module Test loading state
3
+ *
4
+ * The state of loading one recorded test into the viewer, and the interface a report gives the viewer to load tests on
5
+ * demand. Types only.
6
+ */
@@ -1,10 +1,36 @@
1
1
  import type { ObjectStore } from './object-store.js';
2
2
  import type { EvidenceStatus, ReportPart } from './explorer-types.js';
3
+ /** The status of a whole part: `failed` if any check failed, else `running` if any is running, else `not-run` if it has no checks or any is `not-run`, else `passed`. */
3
4
  export declare function partStatus(part: ReportPart): EvidenceStatus;
5
+ /**
6
+ * Throws unless `part` is well formed: a 40-hex `sha`, an `id` usable as a key segment, a non-empty `kind` and
7
+ * `title`, a non-negative safe-integer `recordedAtMs`, string `parameters` values, and checks with a unique non-empty
8
+ * `id`, non-empty `title`, a string `detail`, one of the four statuses and finite-number measurements with a name and
9
+ * unit. The error is `Invalid report part`, `Invalid report parameters`, `Invalid evidence check` or `Invalid evidence
10
+ * measurement`; it does not say which field.
11
+ */
4
12
  export declare function validatePart(part: ReportPart): void;
5
- /** Each producer owns a separate key, independent of the spec-run attempt. */
13
+ /**
14
+ * Stores a part and publishes it as a layer. Validates first (`validatePart`, which throws and writes nothing).
15
+ *
16
+ * Each producer owns a separate key, `commits/<sha>/parts/<id>/<recordedAtMs>-<sha256 of the JSON>.json`, independent
17
+ * of the spec-run attempt, so republishing identical content rewrites the same key. It then records the commit as an
18
+ * explorer version and publishes a layer (producer `part.id`, environment from the `environment` parameter, the
19
+ * commit's branch and run URL when a commit record exists, the commit's spec inventory) with no captured files.
20
+ */
6
21
  export declare function publishPart(store: ObjectStore, part: ReportPart): Promise<void>;
7
- /** Latest result per producer, sorted stably; corrupt evidence fails visibly. */
22
+ /**
23
+ * Reads the latest part of every producer for a commit (see `latestParts`), sorted by kind then id.
24
+ *
25
+ * Throws `Invalid commit` for a sha that is not 40 hex, a validation error (see `validatePart`) or a `SyntaxError` for
26
+ * a part that is corrupt, `Report part disappeared` when a listed part vanishes, and `Report part belongs to another
27
+ * commit` when a part's `sha` or `id` does not match its key. Corrupt evidence fails visibly; it is never skipped.
28
+ */
8
29
  export declare function readParts(store: ObjectStore, sha: string): Promise<readonly ReportPart[]>;
9
- /** Ties are conservative and independent of write/list order. */
30
+ /**
31
+ * Keeps one part per producer id and sorts by kind then id. The newer `recordedAtMs` wins; at the same time the more
32
+ * severe status wins (`failed`, then `running`, then `not-run`, then `passed`); at the same status the greater JSON
33
+ * text wins, so the answer is independent of write and list order. This is not the layer winner rule: here a newer
34
+ * unfinished part replaces an older completed one.
35
+ */
10
36
  export declare function latestParts(parts: readonly ReportPart[]): readonly ReportPart[];
@@ -1,3 +1,12 @@
1
+ /**
2
+ * @module Report parts of a commit
3
+ *
4
+ * Reads, writes and validates "parts": one producer's checks for one commit, stored under
5
+ * `commits/<sha>/parts/<producer>/` in the older commit-keyed layout. Publishing a part also publishes it as an
6
+ * immutable layer, so it composes with everything else; reading parts picks the latest per producer for the explorer.
7
+ * A part that is corrupt or belongs to another commit fails visibly instead of being skipped.
8
+ */
9
+ import { normalizeEnvironment } from './layer-axes.js';
1
10
  import { publishLayer } from './layer-store.js';
2
11
  import { readCommit } from './read.js';
3
12
  import { readInventory } from './spec-inventory.js';
@@ -6,6 +15,7 @@ import { inCompletionOrder } from './completion-order.js';
6
15
  import { recordExplorerVersion } from './explorer-versions.js';
7
16
  import { isCommitSha, isKeySegment } from './keys.js';
8
17
  const STATUSES = new Set(['passed', 'failed', 'running', 'not-run']);
18
+ /** The status of a whole part: `failed` if any check failed, else `running` if any is running, else `not-run` if it has no checks or any is `not-run`, else `passed`. */
9
19
  export function partStatus(part) {
10
20
  const statuses = part.checks.map((check) => check.status);
11
21
  if (statuses.includes('failed'))
@@ -16,6 +26,13 @@ export function partStatus(part) {
16
26
  return 'not-run';
17
27
  return 'passed';
18
28
  }
29
+ /**
30
+ * Throws unless `part` is well formed: a 40-hex `sha`, an `id` usable as a key segment, a non-empty `kind` and
31
+ * `title`, a non-negative safe-integer `recordedAtMs`, string `parameters` values, and checks with a unique non-empty
32
+ * `id`, non-empty `title`, a string `detail`, one of the four statuses and finite-number measurements with a name and
33
+ * unit. The error is `Invalid report part`, `Invalid report parameters`, `Invalid evidence check` or `Invalid evidence
34
+ * measurement`; it does not say which field.
35
+ */
19
36
  export function validatePart(part) {
20
37
  if (!isCommitSha(part.sha) ||
21
38
  !isKeySegment(part.id) ||
@@ -57,7 +74,14 @@ function partsPrefix(sha) {
57
74
  throw new Error('Invalid commit');
58
75
  return `commits/${sha}/parts/`;
59
76
  }
60
- /** Each producer owns a separate key, independent of the spec-run attempt. */
77
+ /**
78
+ * Stores a part and publishes it as a layer. Validates first (`validatePart`, which throws and writes nothing).
79
+ *
80
+ * Each producer owns a separate key, `commits/<sha>/parts/<id>/<recordedAtMs>-<sha256 of the JSON>.json`, independent
81
+ * of the spec-run attempt, so republishing identical content rewrites the same key. It then records the commit as an
82
+ * explorer version and publishes a layer (producer `part.id`, environment from the `environment` parameter, the
83
+ * commit's branch and run URL when a commit record exists, the commit's spec inventory) with no captured files.
84
+ */
61
85
  export async function publishPart(store, part) {
62
86
  validatePart(part);
63
87
  const json = JSON.stringify(part);
@@ -68,12 +92,20 @@ export async function publishPart(store, part) {
68
92
  await writeUtf8(store, `${partsPrefix(part.sha)}${part.id}/${part.recordedAtMs}-${hash}.json`, json, 'application/json');
69
93
  await recordExplorerVersion(store, part.sha);
70
94
  const commit = await readCommit(store, part.sha);
95
+ const environment = normalizeEnvironment(part.parameters?.environment);
71
96
  await publishLayer(store, {
72
97
  ...(part.freshness ? { freshness: part.freshness } : {}), producer: part.id, recordedAtMs: part.recordedAtMs, sha: part.sha, branch: commit?.branch ?? null, runUrl: commit?.runUrl ?? null,
98
+ ...(environment ? { environment } : {}),
73
99
  parameters: part.parameters ?? {}, specs: (await readInventory(store, part.sha))?.entries ?? [], parts: [part], source: null, recording: null,
74
100
  }, (async function* () { })());
75
101
  }
76
- /** Latest result per producer, sorted stably; corrupt evidence fails visibly. */
102
+ /**
103
+ * Reads the latest part of every producer for a commit (see `latestParts`), sorted by kind then id.
104
+ *
105
+ * Throws `Invalid commit` for a sha that is not 40 hex, a validation error (see `validatePart`) or a `SyntaxError` for
106
+ * a part that is corrupt, `Report part disappeared` when a listed part vanishes, and `Report part belongs to another
107
+ * commit` when a part's `sha` or `id` does not match its key. Corrupt evidence fails visibly; it is never skipped.
108
+ */
77
109
  export async function readParts(store, sha) {
78
110
  const keys = await store.listKeys(partsPrefix(sha));
79
111
  const parts = [];
@@ -91,7 +123,12 @@ export async function readParts(store, sha) {
91
123
  parts.push(part);
92
124
  return latestParts(parts);
93
125
  }
94
- /** Ties are conservative and independent of write/list order. */
126
+ /**
127
+ * Keeps one part per producer id and sorts by kind then id. The newer `recordedAtMs` wins; at the same time the more
128
+ * severe status wins (`failed`, then `running`, then `not-run`, then `passed`); at the same status the greater JSON
129
+ * text wins, so the answer is independent of write and list order. This is not the layer winner rule: here a newer
130
+ * unfinished part replaces an older completed one.
131
+ */
95
132
  export function latestParts(parts) {
96
133
  const latest = new Map();
97
134
  const rank = { passed: 0, 'not-run': 1, running: 2, failed: 3 };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @module The report gate
3
+ *
4
+ * Reduces a combined report to a pass or fail with reasons (`reportStatus`) and renders that result as text or
5
+ * markdown. The JSON of `ReportStatus` is a stable contract (`schemaVersion` 1); the text and markdown are for people
6
+ * and are not. The gate fails closed: no checks, a failed or still-running check, an unreadable layer or backend
7
+ * (a notice), a missing required environment, or (when asked) a spec with no result each make it fail.
8
+ *
9
+ * @see docs/report-contract.md#statuses-and-the-gate
10
+ */
11
+ import { type ReportStatus } from '../../schema/src/layer.js';
12
+ import { type CombinedReport } from './compose.js';
13
+ /** Extra conditions for the gate beyond "all checks pass". */
14
+ export type StatusOptions = {
15
+ /** Environments that must have at least one check, such as `ios,android`. A missing one fails the gate. */
16
+ readonly requireEnvironments?: readonly string[];
17
+ /** Fail when any spec of the outline has no result at all. */
18
+ readonly requireAllSpecs?: boolean;
19
+ };
20
+ /**
21
+ * The gate. A report passes only when it has checks, none failed, none is still running, every
22
+ * layer and backend could be read, and every required environment is present. Unknown never passes.
23
+ *
24
+ * Returns a `ReportStatus` (schemaVersion 1): `ok` is true exactly when `reasons` is empty. Reasons name, in order: no
25
+ * checks (or none completed), failed checks, running checks, specs without results (only with `requireAllSpecs`),
26
+ * each required environment with no checks (the name is normalised, so `iOS` means `ios`), then every notice of the
27
+ * report (an unreadable layer or backend). `failing` lists each failed or running winner, in a fixed order that does
28
+ * not depend on read order; diagnostic `performance` checks are left out unless nothing else was recorded.
29
+ *
30
+ * @example
31
+ * const status = reportStatus(await combineBackends(backends), { requireEnvironments: ['ios'] });
32
+ * if (!status.ok) throw new Error(status.reasons.join('\n'));
33
+ *
34
+ * @see docs/report-contract.md#atlas-report-status
35
+ */
36
+ export declare function reportStatus(report: CombinedReport, options?: StatusOptions): ReportStatus;
37
+ /**
38
+ * Renders a status as plain text for a terminal: a `PASS`/`FAIL` line with totals, one line per environment, each
39
+ * failing or running check with where it ran and the first line of its detail, notices, reasons and specs with no
40
+ * results. Ends with a newline. For people: the layout is not a contract; parse `--format json` instead.
41
+ */
42
+ export declare function formatStatusText(status: ReportStatus): string;
43
+ /**
44
+ * Renders a status as GitHub-flavoured markdown for a pull request comment or `$GITHUB_STEP_SUMMARY`: a heading with
45
+ * totals, a table per environment, a table of failing checks (`|` and newlines in cells are escaped) and the reasons.
46
+ * Ends with a newline. For people: the layout is not a contract.
47
+ */
48
+ export declare function formatStatusMarkdown(status: ReportStatus): string;