@variance-authority/tribunal 0.1.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 (271) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/LICENSE +21 -0
  3. package/README.md +506 -0
  4. package/dist/__fixtures__/review.d.ts +47 -0
  5. package/dist/__fixtures__/review.js +145 -0
  6. package/dist/__fixtures__/review.js.map +1 -0
  7. package/dist/bindings.d.ts +134 -0
  8. package/dist/bindings.js +88 -0
  9. package/dist/bindings.js.map +1 -0
  10. package/dist/changelog.d.ts +132 -0
  11. package/dist/changelog.js +129 -0
  12. package/dist/changelog.js.map +1 -0
  13. package/dist/history-queries.d.ts +82 -0
  14. package/dist/history-queries.js +200 -0
  15. package/dist/history-queries.js.map +1 -0
  16. package/dist/history-rows.d.ts +42 -0
  17. package/dist/history-rows.js +148 -0
  18. package/dist/history-rows.js.map +1 -0
  19. package/dist/history.d.ts +40 -0
  20. package/dist/history.js +186 -0
  21. package/dist/history.js.map +1 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/migration-steps.d.ts +32 -0
  26. package/dist/migration-steps.js +303 -0
  27. package/dist/migration-steps.js.map +1 -0
  28. package/dist/migrations.d.ts +58 -0
  29. package/dist/migrations.js +283 -0
  30. package/dist/migrations.js.map +1 -0
  31. package/dist/next.d.ts +94 -0
  32. package/dist/next.js +44 -0
  33. package/dist/next.js.map +1 -0
  34. package/dist/node/bin.d.ts +120 -0
  35. package/dist/node/bin.js +337 -0
  36. package/dist/node/bin.js.map +1 -0
  37. package/dist/node/bucket.d.ts +58 -0
  38. package/dist/node/bucket.js +142 -0
  39. package/dist/node/bucket.js.map +1 -0
  40. package/dist/node/database.d.ts +33 -0
  41. package/dist/node/database.js +157 -0
  42. package/dist/node/database.js.map +1 -0
  43. package/dist/node/index.d.ts +43 -0
  44. package/dist/node/index.js +43 -0
  45. package/dist/node/index.js.map +1 -0
  46. package/dist/node/serve.d.ts +81 -0
  47. package/dist/node/serve.js +193 -0
  48. package/dist/node/serve.js.map +1 -0
  49. package/dist/node/ui-assets.d.ts +47 -0
  50. package/dist/node/ui-assets.js +60 -0
  51. package/dist/node/ui-assets.js.map +1 -0
  52. package/dist/review-ingest.d.ts +24 -0
  53. package/dist/review-ingest.js +184 -0
  54. package/dist/review-ingest.js.map +1 -0
  55. package/dist/review-read.d.ts +87 -0
  56. package/dist/review-read.js +361 -0
  57. package/dist/review-read.js.map +1 -0
  58. package/dist/review-rows.d.ts +30 -0
  59. package/dist/review-rows.js +67 -0
  60. package/dist/review-rows.js.map +1 -0
  61. package/dist/review-types.d.ts +414 -0
  62. package/dist/review-types.js +2 -0
  63. package/dist/review-types.js.map +1 -0
  64. package/dist/review-write.d.ts +35 -0
  65. package/dist/review-write.js +86 -0
  66. package/dist/review-write.js.map +1 -0
  67. package/dist/review.d.ts +53 -0
  68. package/dist/review.fixtures.d.ts +47 -0
  69. package/dist/review.fixtures.js +145 -0
  70. package/dist/review.fixtures.js.map +1 -0
  71. package/dist/review.js +245 -0
  72. package/dist/review.js.map +1 -0
  73. package/dist/schema.d.ts +36 -0
  74. package/dist/schema.js +39 -0
  75. package/dist/schema.js.map +1 -0
  76. package/dist/store.d.ts +58 -0
  77. package/dist/store.js +278 -0
  78. package/dist/store.js.map +1 -0
  79. package/dist/testing.d.ts +38 -0
  80. package/dist/testing.js +92 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/ui/__probe.d.ts +3 -0
  83. package/dist/ui/__probe.js +5 -0
  84. package/dist/ui/__probe.js.map +1 -0
  85. package/dist/ui/attribution.d.ts +46 -0
  86. package/dist/ui/attribution.js +79 -0
  87. package/dist/ui/attribution.js.map +1 -0
  88. package/dist/ui/because.d.ts +51 -0
  89. package/dist/ui/because.js +52 -0
  90. package/dist/ui/because.js.map +1 -0
  91. package/dist/ui/browser-entry.d.ts +2 -0
  92. package/dist/ui/browser-entry.js +34 -0
  93. package/dist/ui/browser-entry.js.map +1 -0
  94. package/dist/ui/builds.d.ts +43 -0
  95. package/dist/ui/builds.js +79 -0
  96. package/dist/ui/builds.js.map +1 -0
  97. package/dist/ui/carried.d.ts +73 -0
  98. package/dist/ui/carried.js +63 -0
  99. package/dist/ui/carried.js.map +1 -0
  100. package/dist/ui/cause.d.ts +73 -0
  101. package/dist/ui/cause.js +133 -0
  102. package/dist/ui/cause.js.map +1 -0
  103. package/dist/ui/change-story.d.ts +153 -0
  104. package/dist/ui/change-story.js +253 -0
  105. package/dist/ui/change-story.js.map +1 -0
  106. package/dist/ui/change.d.ts +51 -0
  107. package/dist/ui/change.js +125 -0
  108. package/dist/ui/change.js.map +1 -0
  109. package/dist/ui/client.d.ts +74 -0
  110. package/dist/ui/client.js +88 -0
  111. package/dist/ui/client.js.map +1 -0
  112. package/dist/ui/consumers.d.ts +80 -0
  113. package/dist/ui/consumers.js +125 -0
  114. package/dist/ui/consumers.js.map +1 -0
  115. package/dist/ui/control.d.ts +63 -0
  116. package/dist/ui/control.js +39 -0
  117. package/dist/ui/control.js.map +1 -0
  118. package/dist/ui/crossing.d.ts +55 -0
  119. package/dist/ui/crossing.js +74 -0
  120. package/dist/ui/crossing.js.map +1 -0
  121. package/dist/ui/declarations.d.ts +36 -0
  122. package/dist/ui/declarations.js +73 -0
  123. package/dist/ui/declarations.js.map +1 -0
  124. package/dist/ui/distance.d.ts +154 -0
  125. package/dist/ui/distance.js +180 -0
  126. package/dist/ui/distance.js.map +1 -0
  127. package/dist/ui/divergence.d.ts +40 -0
  128. package/dist/ui/divergence.js +176 -0
  129. package/dist/ui/divergence.js.map +1 -0
  130. package/dist/ui/docket.d.ts +39 -0
  131. package/dist/ui/docket.js +172 -0
  132. package/dist/ui/docket.js.map +1 -0
  133. package/dist/ui/findings.d.ts +42 -0
  134. package/dist/ui/findings.js +110 -0
  135. package/dist/ui/findings.js.map +1 -0
  136. package/dist/ui/foreseen.d.ts +55 -0
  137. package/dist/ui/foreseen.js +91 -0
  138. package/dist/ui/foreseen.js.map +1 -0
  139. package/dist/ui/glance.d.ts +101 -0
  140. package/dist/ui/glance.js +101 -0
  141. package/dist/ui/glance.js.map +1 -0
  142. package/dist/ui/grouping.d.ts +158 -0
  143. package/dist/ui/grouping.js +187 -0
  144. package/dist/ui/grouping.js.map +1 -0
  145. package/dist/ui/handed.d.ts +60 -0
  146. package/dist/ui/handed.js +122 -0
  147. package/dist/ui/handed.js.map +1 -0
  148. package/dist/ui/history.d.ts +88 -0
  149. package/dist/ui/history.js +170 -0
  150. package/dist/ui/history.js.map +1 -0
  151. package/dist/ui/holding.d.ts +84 -0
  152. package/dist/ui/holding.js +90 -0
  153. package/dist/ui/holding.js.map +1 -0
  154. package/dist/ui/impact.d.ts +31 -0
  155. package/dist/ui/impact.js +132 -0
  156. package/dist/ui/impact.js.map +1 -0
  157. package/dist/ui/index.d.ts +21 -0
  158. package/dist/ui/index.js +21 -0
  159. package/dist/ui/index.js.map +1 -0
  160. package/dist/ui/lead.d.ts +35 -0
  161. package/dist/ui/lead.js +34 -0
  162. package/dist/ui/lead.js.map +1 -0
  163. package/dist/ui/look.d.ts +109 -0
  164. package/dist/ui/look.js +248 -0
  165. package/dist/ui/look.js.map +1 -0
  166. package/dist/ui/mark.d.ts +11 -0
  167. package/dist/ui/mark.js +13 -0
  168. package/dist/ui/mark.js.map +1 -0
  169. package/dist/ui/moved.d.ts +121 -0
  170. package/dist/ui/moved.js +143 -0
  171. package/dist/ui/moved.js.map +1 -0
  172. package/dist/ui/navigation.d.ts +26 -0
  173. package/dist/ui/navigation.js +53 -0
  174. package/dist/ui/navigation.js.map +1 -0
  175. package/dist/ui/order.d.ts +113 -0
  176. package/dist/ui/order.js +226 -0
  177. package/dist/ui/order.js.map +1 -0
  178. package/dist/ui/origins.d.ts +42 -0
  179. package/dist/ui/origins.js +149 -0
  180. package/dist/ui/origins.js.map +1 -0
  181. package/dist/ui/outcome.d.ts +93 -0
  182. package/dist/ui/outcome.js +162 -0
  183. package/dist/ui/outcome.js.map +1 -0
  184. package/dist/ui/parted.d.ts +68 -0
  185. package/dist/ui/parted.js +101 -0
  186. package/dist/ui/parted.js.map +1 -0
  187. package/dist/ui/rail.d.ts +31 -0
  188. package/dist/ui/rail.js +21 -0
  189. package/dist/ui/rail.js.map +1 -0
  190. package/dist/ui/reach.d.ts +66 -0
  191. package/dist/ui/reach.js +185 -0
  192. package/dist/ui/reach.js.map +1 -0
  193. package/dist/ui/regions.d.ts +82 -0
  194. package/dist/ui/regions.js +67 -0
  195. package/dist/ui/regions.js.map +1 -0
  196. package/dist/ui/review.bundle.js +69 -0
  197. package/dist/ui/review.d.ts +97 -0
  198. package/dist/ui/review.js +39 -0
  199. package/dist/ui/review.js.map +1 -0
  200. package/dist/ui/root.d.ts +72 -0
  201. package/dist/ui/root.js +102 -0
  202. package/dist/ui/root.js.map +1 -0
  203. package/dist/ui/route.d.ts +95 -0
  204. package/dist/ui/route.js +114 -0
  205. package/dist/ui/route.js.map +1 -0
  206. package/dist/ui/run.d.ts +26 -0
  207. package/dist/ui/run.js +12 -0
  208. package/dist/ui/run.js.map +1 -0
  209. package/dist/ui/sense.d.ts +187 -0
  210. package/dist/ui/sense.js +222 -0
  211. package/dist/ui/sense.js.map +1 -0
  212. package/dist/ui/settled.d.ts +43 -0
  213. package/dist/ui/settled.js +59 -0
  214. package/dist/ui/settled.js.map +1 -0
  215. package/dist/ui/shell.d.ts +76 -0
  216. package/dist/ui/shell.js +65 -0
  217. package/dist/ui/shell.js.map +1 -0
  218. package/dist/ui/shift.d.ts +52 -0
  219. package/dist/ui/shift.js +98 -0
  220. package/dist/ui/shift.js.map +1 -0
  221. package/dist/ui/styles-docket.d.ts +18 -0
  222. package/dist/ui/styles-docket.js +281 -0
  223. package/dist/ui/styles-docket.js.map +1 -0
  224. package/dist/ui/styles-stage.d.ts +18 -0
  225. package/dist/ui/styles-stage.js +92 -0
  226. package/dist/ui/styles-stage.js.map +1 -0
  227. package/dist/ui/styles.d.ts +12 -0
  228. package/dist/ui/styles.js +482 -0
  229. package/dist/ui/styles.js.map +1 -0
  230. package/dist/ui/subject.d.ts +36 -0
  231. package/dist/ui/subject.js +138 -0
  232. package/dist/ui/subject.js.map +1 -0
  233. package/dist/ui/text.d.ts +129 -0
  234. package/dist/ui/text.js +164 -0
  235. package/dist/ui/text.js.map +1 -0
  236. package/dist/ui/variations.d.ts +37 -0
  237. package/dist/ui/variations.js +60 -0
  238. package/dist/ui/variations.js.map +1 -0
  239. package/dist/ui/viewer.d.ts +67 -0
  240. package/dist/ui/viewer.js +246 -0
  241. package/dist/ui/viewer.js.map +1 -0
  242. package/dist/worker-auth.d.ts +48 -0
  243. package/dist/worker-auth.js +74 -0
  244. package/dist/worker-auth.js.map +1 -0
  245. package/dist/worker-entry.d.ts +58 -0
  246. package/dist/worker-entry.js +36 -0
  247. package/dist/worker-entry.js.map +1 -0
  248. package/dist/worker-http.d.ts +39 -0
  249. package/dist/worker-http.js +111 -0
  250. package/dist/worker-http.js.map +1 -0
  251. package/dist/worker-input.d.ts +81 -0
  252. package/dist/worker-input.js +296 -0
  253. package/dist/worker-input.js.map +1 -0
  254. package/dist/worker.d.ts +91 -0
  255. package/dist/worker.js +287 -0
  256. package/dist/worker.js.map +1 -0
  257. package/mark.svg +30 -0
  258. package/migrations/0001_initial.sql +193 -0
  259. package/migrations/0002_changelog.sql +30 -0
  260. package/migrations/0003_accessibility.sql +8 -0
  261. package/migrations/0004_variations.sql +19 -0
  262. package/migrations/0005_reach.sql +26 -0
  263. package/migrations/0006_baseline-size.sql +7 -0
  264. package/migrations/0007_declarations.sql +7 -0
  265. package/migrations/0008_green-because.sql +7 -0
  266. package/migrations/0009_moved-bands.sql +6 -0
  267. package/migrations/0010_composition.sql +15 -0
  268. package/migrations/0011_movements.sql +21 -0
  269. package/migrations/0012_control-size.sql +6 -0
  270. package/package.json +110 -0
  271. package/wrangler.jsonc +45 -0
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The two files a browser needs, and neither of them is React.
3
+ *
4
+ * The review surface is a React component tree, and a `node:http` server cannot
5
+ * hand a browser a component tree. Something has to bundle it. That something is
6
+ * **not this package** — [`tools/page-agents.mjs`](../../../../tools/page-agents.mjs)
7
+ * records the rule and the reason: a bundler is not a thing a consumer of a
8
+ * visual-regression tool should install, and `tools/shape.check.ts` refuses build
9
+ * tools in any package's dependencies. So the bundling happens at this
10
+ * repository's build time and the package ships the result, exactly as the four
11
+ * page agents do.
12
+ *
13
+ * What is left here is the page that loads it: a document, the stylesheet inlined,
14
+ * and the configuration the entry reads. The script src is **absolute**, and that
15
+ * is not a style choice — this same document is served at every address in
16
+ * [`ui/route.ts`](../ui/route.ts), and a relative `ui/review.js` resolves against
17
+ * the one it was fetched from. On `/builds/7/changes/Button` it asks for
18
+ * `/builds/7/changes/ui/review.js`, gets the router's 404, and renders a blank
19
+ * page with an error in a console nobody opened. The configuration goes in a
20
+ * `application/json` script rather than into the bundle, because the endpoint and
21
+ * the reviewer are properties of a *deployment* and the bundle is built once for
22
+ * all of them.
23
+ *
24
+ * **No token is ever written into this page.** The review token promotes
25
+ * baselines; a token in a page is a token in everybody's devtools. The browser
26
+ * calls the same origin with no credential at all and the host attaches the
27
+ * capability its own `authorize` granted — the rule is stated once in
28
+ * [`next.ts`](../next.ts) and this file is downstream of it, not a second opinion.
29
+ */
30
+ export interface ReviewPageOptions {
31
+ /** Where the API is mounted, as the browser will address it. `''` is same-origin. */
32
+ readonly endpoint: string;
33
+ /** Recorded on every decision. There are no accounts here; this is a name. */
34
+ readonly reviewer?: string;
35
+ }
36
+ export declare function reviewPage(options: ReviewPageOptions): string;
37
+ /**
38
+ * The bundle, or a script that says why there isn't one.
39
+ *
40
+ * A missing bundle means `tools/tribunal-ui.mjs` did not run — a build-ordering
41
+ * problem in this repository, or an install from a tree that was never built. The
42
+ * alternative to saying so is a blank page and a 404 in a console nobody opened,
43
+ * so the failure is delivered as the script it replaces and lands in the document
44
+ * where a reviewer will see it.
45
+ */
46
+ export declare function uiAsset(): Promise<string>;
47
+ //# sourceMappingURL=ui-assets.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { REVIEW_STYLES } from '../ui/styles.js';
3
+ export function reviewPage(options) {
4
+ const configuration = JSON.stringify({
5
+ endpoint: options.endpoint,
6
+ reviewer: options.reviewer ?? 'reviewer',
7
+ });
8
+ return `<!doctype html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8">
12
+ <meta name="viewport" content="width=device-width, initial-scale=1">
13
+ <meta name="theme-color" content="#181b1d">
14
+ <title>Variance Authority — review</title>
15
+ <style>/* This document's own reset, which is why it is here and not in
16
+ REVIEW_STYLES: an operator injects that sheet into a page this package did not
17
+ write, and a component stylesheet that reaches out and restyles the document
18
+ body is a rude guest. This document is ours. Its shell is a hundred dynamic
19
+ viewport units tall, so a default body margin overflows it by exactly twice
20
+ the margin — a scrollbar on the one page whose whole claim is that it does
21
+ not scroll. */
22
+ body { margin: 0; }
23
+ ${REVIEW_STYLES}</style>
24
+ </head>
25
+ <body>
26
+ <div id="variance-review"></div>
27
+ <script type="application/json" id="variance-config">${safely(configuration)}</script>
28
+ <script type="module" src="/ui/review.js"></script>
29
+ </body>
30
+ </html>
31
+ `;
32
+ }
33
+ /**
34
+ * The bundle, or a script that says why there isn't one.
35
+ *
36
+ * A missing bundle means `tools/tribunal-ui.mjs` did not run — a build-ordering
37
+ * problem in this repository, or an install from a tree that was never built. The
38
+ * alternative to saying so is a blank page and a 404 in a console nobody opened,
39
+ * so the failure is delivered as the script it replaces and lands in the document
40
+ * where a reviewer will see it.
41
+ */
42
+ export async function uiAsset() {
43
+ const from = new URL('../ui/review.bundle.js', import.meta.url);
44
+ const bundle = await readFile(from, 'utf8').catch(() => null);
45
+ if (bundle !== null)
46
+ return bundle;
47
+ return `document.getElementById('variance-review').textContent = ${JSON.stringify('The review surface was never bundled. `dist/ui/review.bundle.js` is generated by ' +
48
+ '`tools/tribunal-ui.mjs` at build time and this install does not have it. Run `yarn build` ' +
49
+ 'in the repository, or install a published @variance-authority/tribunal rather than a ' +
50
+ 'source checkout. The JSON API on this service is unaffected.')};`;
51
+ }
52
+ /**
53
+ * `</script>` inside a JSON string ends the script element, whatever the JSON
54
+ * thinks. One replacement, because the value is `JSON.stringify` output and the
55
+ * only sequence that can close the element is this one.
56
+ */
57
+ function safely(json) {
58
+ return json.replace(/<\//g, '<\\/');
59
+ }
60
+ //# sourceMappingURL=ui-assets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ui-assets.js","sourceRoot":"","sources":["../../src/node/ui-assets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAuChD,MAAM,UAAU,UAAU,CAAC,OAA0B;IACnD,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC;QACnC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,UAAU;KACzC,CAAC,CAAC;IAEH,OAAO;;;;;;;;;;;;;;;EAeP,aAAa;;;;uDAIwC,MAAM,CAAC,aAAa,CAAC;;;;CAI3E,CAAC;AACF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO;IAC3B,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,wBAAwB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAChE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;IAC9D,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAEnC,OAAO,4DAA4D,IAAI,CAAC,SAAS,CAC/E,mFAAmF;QACjF,4FAA4F;QAC5F,uFAAuF;QACvF,8DAA8D,CACjE,GAAG,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,IAAY;IAC1B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AACtC,CAAC","sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { REVIEW_STYLES } from '../ui/styles.js';\n\n/**\n * The two files a browser needs, and neither of them is React.\n *\n * The review surface is a React component tree, and a `node:http` server cannot\n * hand a browser a component tree. Something has to bundle it. That something is\n * **not this package** — [`tools/page-agents.mjs`](../../../../tools/page-agents.mjs)\n * records the rule and the reason: a bundler is not a thing a consumer of a\n * visual-regression tool should install, and `tools/shape.check.ts` refuses build\n * tools in any package's dependencies. So the bundling happens at this\n * repository's build time and the package ships the result, exactly as the four\n * page agents do.\n *\n * What is left here is the page that loads it: a document, the stylesheet inlined,\n * and the configuration the entry reads. The script src is **absolute**, and that\n * is not a style choice — this same document is served at every address in\n * [`ui/route.ts`](../ui/route.ts), and a relative `ui/review.js` resolves against\n * the one it was fetched from. On `/builds/7/changes/Button` it asks for\n * `/builds/7/changes/ui/review.js`, gets the router's 404, and renders a blank\n * page with an error in a console nobody opened. The configuration goes in a\n * `application/json` script rather than into the bundle, because the endpoint and\n * the reviewer are properties of a *deployment* and the bundle is built once for\n * all of them.\n *\n * **No token is ever written into this page.** The review token promotes\n * baselines; a token in a page is a token in everybody's devtools. The browser\n * calls the same origin with no credential at all and the host attaches the\n * capability its own `authorize` granted — the rule is stated once in\n * [`next.ts`](../next.ts) and this file is downstream of it, not a second opinion.\n */\n\nexport interface ReviewPageOptions {\n /** Where the API is mounted, as the browser will address it. `''` is same-origin. */\n readonly endpoint: string;\n /** Recorded on every decision. There are no accounts here; this is a name. */\n readonly reviewer?: string;\n}\n\nexport function reviewPage(options: ReviewPageOptions): string {\n const configuration = JSON.stringify({\n endpoint: options.endpoint,\n reviewer: options.reviewer ?? 'reviewer',\n });\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"theme-color\" content=\"#181b1d\">\n<title>Variance Authority — review</title>\n<style>/* This document's own reset, which is why it is here and not in\n REVIEW_STYLES: an operator injects that sheet into a page this package did not\n write, and a component stylesheet that reaches out and restyles the document\n body is a rude guest. This document is ours. Its shell is a hundred dynamic\n viewport units tall, so a default body margin overflows it by exactly twice\n the margin — a scrollbar on the one page whose whole claim is that it does\n not scroll. */\nbody { margin: 0; }\n${REVIEW_STYLES}</style>\n</head>\n<body>\n<div id=\"variance-review\"></div>\n<script type=\"application/json\" id=\"variance-config\">${safely(configuration)}</script>\n<script type=\"module\" src=\"/ui/review.js\"></script>\n</body>\n</html>\n`;\n}\n\n/**\n * The bundle, or a script that says why there isn't one.\n *\n * A missing bundle means `tools/tribunal-ui.mjs` did not run — a build-ordering\n * problem in this repository, or an install from a tree that was never built. The\n * alternative to saying so is a blank page and a 404 in a console nobody opened,\n * so the failure is delivered as the script it replaces and lands in the document\n * where a reviewer will see it.\n */\nexport async function uiAsset(): Promise<string> {\n const from = new URL('../ui/review.bundle.js', import.meta.url);\n const bundle = await readFile(from, 'utf8').catch(() => null);\n if (bundle !== null) return bundle;\n\n return `document.getElementById('variance-review').textContent = ${JSON.stringify(\n 'The review surface was never bundled. `dist/ui/review.bundle.js` is generated by ' +\n '`tools/tribunal-ui.mjs` at build time and this install does not have it. Run `yarn build` ' +\n 'in the repository, or install a published @variance-authority/tribunal rather than a ' +\n 'source checkout. The JSON API on this service is unaffected.',\n )};`;\n}\n\n/**\n * `</script>` inside a JSON string ends the script element, whatever the JSON\n * thinks. One replacement, because the value is `JSON.stringify` output and the\n * only sequence that can close the element is this one.\n */\nfunction safely(json: string): string {\n return json.replace(/<\\//g, '<\\\\/');\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type { D1Like, R2Like } from './bindings.js';
2
+ import type { BuildIngest } from './review-types.js';
3
+ /**
4
+ * One run report, written into rows.
5
+ *
6
+ * Apart from [`review.ts`](./review.ts) because it is the only operation there
7
+ * that reads a whole `RunReport` — every other one reads a row back out. Five
8
+ * tables move together, and the order they move in is the argument: objects
9
+ * first, rows second, every statement in one `batch`.
10
+ *
11
+ * What this does *not* do is normalise. A report says `undefined` in a dozen
12
+ * places where it could have said `[]` or `false`, and each of those pairs is a
13
+ * distinction somebody downstream reads: a coverage list that was never stated
14
+ * is not an empty one, findings nothing collected are not an absence of defects,
15
+ * and a variation with no parent was not compared and found to differ. Each is
16
+ * stored as `null` in a column that permits it, so reading it back can still
17
+ * tell the two apart.
18
+ */
19
+ export declare function ingestBuild({ db, bucket, project, }: {
20
+ readonly db: D1Like;
21
+ readonly bucket: R2Like;
22
+ readonly project: string;
23
+ }, build: BuildIngest): Promise<void>;
24
+ //# sourceMappingURL=review-ingest.d.ts.map
@@ -0,0 +1,184 @@
1
+ import { identityDigest } from '@variance-authority/core';
2
+ import { ReviewError, instant } from './review-rows.js';
3
+ import { store } from './review-write.js';
4
+ /**
5
+ * One run report, written into rows.
6
+ *
7
+ * Apart from [`review.ts`](./review.ts) because it is the only operation there
8
+ * that reads a whole `RunReport` — every other one reads a row back out. Five
9
+ * tables move together, and the order they move in is the argument: objects
10
+ * first, rows second, every statement in one `batch`.
11
+ *
12
+ * What this does *not* do is normalise. A report says `undefined` in a dozen
13
+ * places where it could have said `[]` or `false`, and each of those pairs is a
14
+ * distinction somebody downstream reads: a coverage list that was never stated
15
+ * is not an empty one, findings nothing collected are not an absence of defects,
16
+ * and a variation with no parent was not compared and found to differ. Each is
17
+ * stored as `null` in a column that permits it, so reading it back can still
18
+ * tell the two apart.
19
+ */
20
+ export async function ingestBuild({ db, bucket, project, }, build) {
21
+ const { report } = build;
22
+ if (report.runVersion !== 1) {
23
+ // The same refusal `readRunReport` makes, for the same reason: an agent
24
+ // edits code on these answers, and a partly-understood report produces
25
+ // confident sentences about fields that were never there.
26
+ throw new ReviewError(`build "${build.build}" carries a report at runVersion ${String(report.runVersion)}; ` +
27
+ 'this deployment understands 1. Refusing it rather than storing a shape whose ' +
28
+ 'fields it would then misread');
29
+ }
30
+ const identity = report.identity;
31
+ const at = report.at;
32
+ const images = build.images ?? {};
33
+ // Objects first, rows second — the same order and the same argument as the
34
+ // baseline store's `put`. An object nothing points at is invisible and is
35
+ // swept; a row pointing at nothing is a build that throws whenever anybody
36
+ // opens it.
37
+ const written = [];
38
+ for (const observation of report.observations) {
39
+ written.push({
40
+ subject: observation.subject,
41
+ keys: await store(bucket, project, build.build, observation.subject, images[observation.subject]),
42
+ });
43
+ }
44
+ // The build's own rows, cleared before they are rewritten.
45
+ //
46
+ // Every table below is written with `INSERT OR REPLACE`, which replaces a row
47
+ // whose key comes back and leaves one that does not. A second ingest of the
48
+ // same build is not a rarity — it is what happens every time a run is
49
+ // re-pushed after the tool that produced it was fixed — and under replace
50
+ // alone the reading that was wrong survives beside the reading that replaced
51
+ // it, for every subject, component or pair the new report no longer names. A
52
+ // reviewer then reads an attribution from a version of the analysis that no
53
+ // longer exists, with nothing on the page to say so.
54
+ //
55
+ // Same `batch` as the inserts, so there is no instant where the build is on
56
+ // the page with its rows gone. `builds` is one row per build and is not
57
+ // cleared: `INSERT OR REPLACE` on a single known key is already exact.
58
+ // `decisions` is nobody's to clear here — a person put their name on those,
59
+ // and re-pushing a run is not them taking it back.
60
+ const statements = [
61
+ ...[
62
+ 'build_subjects',
63
+ 'build_not_observed',
64
+ 'build_variations',
65
+ 'build_composition',
66
+ 'build_movements',
67
+ 'build_reach_subjects',
68
+ 'build_reach',
69
+ ].map((table) => db.prepare(`DELETE FROM ${table} WHERE project = ? AND build = ?`).bind(project, build.build)),
70
+ db
71
+ .prepare(`INSERT OR REPLACE INTO builds
72
+ (project, build, "commit", branch, intent, at, at_ms, identity, identity_digest,
73
+ retention, run_version, says_not_observed, ignores, sensitivities)
74
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
75
+ .bind(project, build.build, build.commit, build.branch ?? null, report.intent ?? null, at, instant(at, `build "${build.build}"`), JSON.stringify(identity), identityDigest(identity), report.retention, report.runVersion, report.notObserved === undefined ? 0 : 1,
76
+ // Stored whole and stored as absent when absent. A ledger reshaped on the
77
+ // way in would be a second vocabulary for one fact, and a `'[]'` written
78
+ // where the report said nothing would turn a build nobody audited into
79
+ // one that was audited and found clean.
80
+ report.ignores === undefined ? null : JSON.stringify(report.ignores), report.sensitivities === undefined ? null : JSON.stringify(report.sensitivities)),
81
+ ];
82
+ for (const [index, observation] of report.observations.entries()) {
83
+ const keys = written[index]?.keys ?? {};
84
+ const after = images[observation.subject]?.after;
85
+ const before = images[observation.subject]?.before;
86
+ statements.push(db
87
+ .prepare(`INSERT OR REPLACE INTO build_subjects
88
+ (project, build, subject, verdict, because, changed_pixels, regions, truncated,
89
+ missing_fonts, findings, signals, ignored, relaxed, moved, before_key, after_key,
90
+ diff_key, candidate_document_digest, candidate_width, candidate_height,
91
+ candidate_missing_fonts, candidate_accessibility,
92
+ baseline_width, baseline_height)
93
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
94
+ .bind(project, build.build, observation.subject, observation.verdict, observation.because, observation.changedPixels, JSON.stringify(observation.regions), observation.truncated === undefined ? null : JSON.stringify(observation.truncated), observation.missingFonts === undefined
95
+ ? null
96
+ : JSON.stringify(observation.missingFonts),
97
+ // `null` and `'[]'` are different claims and are stored as
98
+ // different values: nothing inspected this render, versus this
99
+ // render was inspected and was clean.
100
+ observation.findings === undefined ? null : JSON.stringify(observation.findings), observation.signals === undefined ? null : JSON.stringify(observation.signals),
101
+ // What decided this subject, when a declaration did. Stored rather
102
+ // than left to the run-level ledger: the ledger says what a rule took
103
+ // across the whole run, and the question a settled list asks is which
104
+ // rule took *this one*. Dropping it made the service report the run as
105
+ // silent about something the run had written down.
106
+ observation.ignored === undefined ? null : JSON.stringify(observation.ignored), observation.relaxed === undefined ? null : JSON.stringify(observation.relaxed),
107
+ // The semantic tier's own list of who moved, kept beside the raster
108
+ // tier's regions rather than folded into them. `null` is a baseline
109
+ // with no hashes; `'[]'` is both sides compared and nothing moved.
110
+ observation.moved === undefined ? null : JSON.stringify(observation.moved), keys.before ?? null, keys.after ?? null, keys.diff ?? null, after?.documentDigest ?? null, after?.width ?? null, after?.height ?? null, after === undefined ? null : JSON.stringify(after.missingFonts), after?.accessibility === undefined ? null : JSON.stringify(after.accessibility),
111
+ // Null when the push could not read the baseline's header. Not the
112
+ // candidate's numbers: a guess here is a width change made invisible.
113
+ before?.width ?? null, before?.height ?? null));
114
+ }
115
+ for (const variation of report.variations ?? []) {
116
+ statements.push(db
117
+ .prepare(`INSERT OR REPLACE INTO build_variations
118
+ (project, build, subject, parent, identical, bands, unobserved, components,
119
+ digest, how, because)
120
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
121
+ .bind(project, build.build, variation.subject, variation.parent ?? null,
122
+ // Absent stays absent. `false` would say the pair was compared and
123
+ // differs; what happened is that the parent this run was told about
124
+ // is not in this run, which is a broken link and reads as one.
125
+ variation.identical === undefined ? null : variation.identical ? 1 : 0, variation.bands === undefined ? null : JSON.stringify(variation.bands), variation.unobserved === undefined ? null : JSON.stringify(variation.unobserved), variation.components === undefined ? null : JSON.stringify(variation.components), variation.digest ?? null, variation.how ?? null, variation.because));
126
+ }
127
+ // The build row is written whenever the run carried a diff, including when
128
+ // the walk refused to attribute it: a refusal with its reason is a fact a
129
+ // reviewer reads, and dropping the row would make it indistinguishable
130
+ // from a run that was never given a ref.
131
+ const reach = report.reach;
132
+ if (reach !== undefined) {
133
+ statements.push(db
134
+ .prepare(`INSERT OR REPLACE INTO build_reach
135
+ (project, build, against_ref, changed, components, whole, unscanned, opaque)
136
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
137
+ .bind(project, build.build, reach.against, JSON.stringify(reach.changed), JSON.stringify(reach.components), reach.whole ?? null, reach.unscanned === undefined ? null : JSON.stringify(reach.unscanned), reach.opaque === undefined ? null : JSON.stringify(reach.opaque)));
138
+ for (const [subject, entry] of Object.entries(reach.subjects ?? {})) {
139
+ statements.push(db
140
+ .prepare(`INSERT OR REPLACE INTO build_reach_subjects
141
+ (project, build, subject, reached, through, trail, because)
142
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
143
+ .bind(project, build.build, subject, entry.reached ? 1 : 0, entry.through.length === 0 ? null : JSON.stringify(entry.through), entry.trail === undefined ? null : JSON.stringify(entry.trail), entry.because));
144
+ }
145
+ }
146
+ // The census, which is the only record in a report that answers *what draws
147
+ // this*. `reach` answers the mirror question and cannot answer this one: it
148
+ // walks importers, so a component a changed file renders is off its graph in
149
+ // the one direction it never goes.
150
+ //
151
+ // Narrowed to four columns of the report's own `ComponentRecord`. The rest —
152
+ // instance counts, variant counts, the token list — is a census a reader opens
153
+ // the report for; what a review page needs is the edges.
154
+ for (const entry of report.composition?.components ?? []) {
155
+ statements.push(db
156
+ .prepare(`INSERT OR REPLACE INTO build_composition
157
+ (project, build, component, subjects, within, created_by, renders)
158
+ VALUES (?, ?, ?, ?, ?, ?, ?)`)
159
+ .bind(project, build.build, entry.component, JSON.stringify(entry.subjects), JSON.stringify(entry.within), JSON.stringify(entry.createdBy), JSON.stringify(entry.renders)));
160
+ }
161
+ // The attribution, which is the part of the census a reviewer actually reads.
162
+ // The rows above say `Card` draws `CardFooter`; these say the run climbed that
163
+ // edge, found `ProductCard` in the diff, and concluded `CardFooter` moved on
164
+ // what it was handed. Dropping them left the page to re-derive a worse answer
165
+ // from the graph alone — without the props digests, the control group or the
166
+ // source index the run had used — and to print *nothing declares this* about
167
+ // components the run had already attributed to an edited parent.
168
+ for (const entry of report.composition?.movements ?? []) {
169
+ statements.push(db
170
+ .prepare(`INSERT OR REPLACE INTO build_movements
171
+ (project, build, subject, component, cause, because, bands, held,
172
+ compared, file, tokens, upstream, through, standing)
173
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`)
174
+ .bind(project, build.build, entry.subject, entry.component, entry.cause, entry.because, JSON.stringify(entry.bands), JSON.stringify(entry.held), entry.compared ?? null, entry.file ?? null, entry.tokens === undefined ? null : JSON.stringify(entry.tokens), entry.upstream ?? null, entry.through === undefined ? null : JSON.stringify(entry.through), entry.standing ?? null));
175
+ }
176
+ for (const entry of report.notObserved ?? []) {
177
+ statements.push(db
178
+ .prepare(`INSERT OR REPLACE INTO build_not_observed (project, build, subject, kind, because)
179
+ VALUES (?, ?, ?, ?, ?)`)
180
+ .bind(project, build.build, entry.subject, entry.kind, entry.because));
181
+ }
182
+ await db.batch(statements);
183
+ }
184
+ //# sourceMappingURL=review-ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"review-ingest.js","sourceRoot":"","sources":["../src/review-ingest.ts"],"names":[],"mappings":"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","sourcesContent":["import { identityDigest } from '@variance-authority/core';\nimport type { D1Like, R2Like } from './bindings.js';\nimport { ReviewError, instant } from './review-rows.js';\nimport type { BuildIngest } from './review-types.js';\nimport { store, type StoredKeys } from './review-write.js';\n\n/**\n * One run report, written into rows.\n *\n * Apart from [`review.ts`](./review.ts) because it is the only operation there\n * that reads a whole `RunReport` — every other one reads a row back out. Five\n * tables move together, and the order they move in is the argument: objects\n * first, rows second, every statement in one `batch`.\n *\n * What this does *not* do is normalise. A report says `undefined` in a dozen\n * places where it could have said `[]` or `false`, and each of those pairs is a\n * distinction somebody downstream reads: a coverage list that was never stated\n * is not an empty one, findings nothing collected are not an absence of defects,\n * and a variation with no parent was not compared and found to differ. Each is\n * stored as `null` in a column that permits it, so reading it back can still\n * tell the two apart.\n */\nexport async function ingestBuild(\n {\n db,\n bucket,\n project,\n }: { readonly db: D1Like; readonly bucket: R2Like; readonly project: string },\n build: BuildIngest,\n): Promise<void> {\n const { report } = build;\n if (report.runVersion !== 1) {\n // The same refusal `readRunReport` makes, for the same reason: an agent\n // edits code on these answers, and a partly-understood report produces\n // confident sentences about fields that were never there.\n throw new ReviewError(\n `build \"${build.build}\" carries a report at runVersion ${String(report.runVersion)}; ` +\n 'this deployment understands 1. Refusing it rather than storing a shape whose ' +\n 'fields it would then misread',\n );\n }\n\n const identity = report.identity;\n const at = report.at;\n const images = build.images ?? {};\n\n // Objects first, rows second — the same order and the same argument as the\n // baseline store's `put`. An object nothing points at is invisible and is\n // swept; a row pointing at nothing is a build that throws whenever anybody\n // opens it.\n const written: { readonly subject: string; readonly keys: StoredKeys }[] = [];\n for (const observation of report.observations) {\n written.push({\n subject: observation.subject,\n keys: await store(bucket, project, build.build, observation.subject, images[observation.subject]),\n });\n }\n\n // The build's own rows, cleared before they are rewritten.\n //\n // Every table below is written with `INSERT OR REPLACE`, which replaces a row\n // whose key comes back and leaves one that does not. A second ingest of the\n // same build is not a rarity — it is what happens every time a run is\n // re-pushed after the tool that produced it was fixed — and under replace\n // alone the reading that was wrong survives beside the reading that replaced\n // it, for every subject, component or pair the new report no longer names. A\n // reviewer then reads an attribution from a version of the analysis that no\n // longer exists, with nothing on the page to say so.\n //\n // Same `batch` as the inserts, so there is no instant where the build is on\n // the page with its rows gone. `builds` is one row per build and is not\n // cleared: `INSERT OR REPLACE` on a single known key is already exact.\n // `decisions` is nobody's to clear here — a person put their name on those,\n // and re-pushing a run is not them taking it back.\n const statements = [\n ...[\n 'build_subjects',\n 'build_not_observed',\n 'build_variations',\n 'build_composition',\n 'build_movements',\n 'build_reach_subjects',\n 'build_reach',\n ].map((table) =>\n db.prepare(`DELETE FROM ${table} WHERE project = ? AND build = ?`).bind(project, build.build),\n ),\n db\n .prepare(\n `INSERT OR REPLACE INTO builds\n (project, build, \"commit\", branch, intent, at, at_ms, identity, identity_digest,\n retention, run_version, says_not_observed, ignores, sensitivities)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n build.commit,\n build.branch ?? null,\n report.intent ?? null,\n at,\n instant(at, `build \"${build.build}\"`),\n JSON.stringify(identity),\n identityDigest(identity),\n report.retention,\n report.runVersion,\n report.notObserved === undefined ? 0 : 1,\n // Stored whole and stored as absent when absent. A ledger reshaped on the\n // way in would be a second vocabulary for one fact, and a `'[]'` written\n // where the report said nothing would turn a build nobody audited into\n // one that was audited and found clean.\n report.ignores === undefined ? null : JSON.stringify(report.ignores),\n report.sensitivities === undefined ? null : JSON.stringify(report.sensitivities),\n ),\n ];\n\n for (const [index, observation] of report.observations.entries()) {\n const keys = written[index]?.keys ?? {};\n const after = images[observation.subject]?.after;\n const before = images[observation.subject]?.before;\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_subjects\n (project, build, subject, verdict, because, changed_pixels, regions, truncated,\n missing_fonts, findings, signals, ignored, relaxed, moved, before_key, after_key,\n diff_key, candidate_document_digest, candidate_width, candidate_height,\n candidate_missing_fonts, candidate_accessibility,\n baseline_width, baseline_height)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n observation.subject,\n observation.verdict,\n observation.because,\n observation.changedPixels,\n JSON.stringify(observation.regions),\n observation.truncated === undefined ? null : JSON.stringify(observation.truncated),\n observation.missingFonts === undefined\n ? null\n : JSON.stringify(observation.missingFonts),\n // `null` and `'[]'` are different claims and are stored as\n // different values: nothing inspected this render, versus this\n // render was inspected and was clean.\n observation.findings === undefined ? null : JSON.stringify(observation.findings),\n observation.signals === undefined ? null : JSON.stringify(observation.signals),\n // What decided this subject, when a declaration did. Stored rather\n // than left to the run-level ledger: the ledger says what a rule took\n // across the whole run, and the question a settled list asks is which\n // rule took *this one*. Dropping it made the service report the run as\n // silent about something the run had written down.\n observation.ignored === undefined ? null : JSON.stringify(observation.ignored),\n observation.relaxed === undefined ? null : JSON.stringify(observation.relaxed),\n // The semantic tier's own list of who moved, kept beside the raster\n // tier's regions rather than folded into them. `null` is a baseline\n // with no hashes; `'[]'` is both sides compared and nothing moved.\n observation.moved === undefined ? null : JSON.stringify(observation.moved),\n keys.before ?? null,\n keys.after ?? null,\n keys.diff ?? null,\n after?.documentDigest ?? null,\n after?.width ?? null,\n after?.height ?? null,\n after === undefined ? null : JSON.stringify(after.missingFonts),\n after?.accessibility === undefined ? null : JSON.stringify(after.accessibility),\n // Null when the push could not read the baseline's header. Not the\n // candidate's numbers: a guess here is a width change made invisible.\n before?.width ?? null,\n before?.height ?? null,\n ),\n );\n }\n\n for (const variation of report.variations ?? []) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_variations\n (project, build, subject, parent, identical, bands, unobserved, components,\n digest, how, because)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n variation.subject,\n variation.parent ?? null,\n // Absent stays absent. `false` would say the pair was compared and\n // differs; what happened is that the parent this run was told about\n // is not in this run, which is a broken link and reads as one.\n variation.identical === undefined ? null : variation.identical ? 1 : 0,\n variation.bands === undefined ? null : JSON.stringify(variation.bands),\n variation.unobserved === undefined ? null : JSON.stringify(variation.unobserved),\n variation.components === undefined ? null : JSON.stringify(variation.components),\n variation.digest ?? null,\n variation.how ?? null,\n variation.because,\n ),\n );\n }\n\n // The build row is written whenever the run carried a diff, including when\n // the walk refused to attribute it: a refusal with its reason is a fact a\n // reviewer reads, and dropping the row would make it indistinguishable\n // from a run that was never given a ref.\n const reach = report.reach;\n if (reach !== undefined) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_reach\n (project, build, against_ref, changed, components, whole, unscanned, opaque)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n reach.against,\n JSON.stringify(reach.changed),\n JSON.stringify(reach.components),\n reach.whole ?? null,\n reach.unscanned === undefined ? null : JSON.stringify(reach.unscanned),\n reach.opaque === undefined ? null : JSON.stringify(reach.opaque),\n ),\n );\n\n for (const [subject, entry] of Object.entries(reach.subjects ?? {})) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_reach_subjects\n (project, build, subject, reached, through, trail, because)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n subject,\n entry.reached ? 1 : 0,\n entry.through.length === 0 ? null : JSON.stringify(entry.through),\n entry.trail === undefined ? null : JSON.stringify(entry.trail),\n entry.because,\n ),\n );\n }\n }\n\n // The census, which is the only record in a report that answers *what draws\n // this*. `reach` answers the mirror question and cannot answer this one: it\n // walks importers, so a component a changed file renders is off its graph in\n // the one direction it never goes.\n //\n // Narrowed to four columns of the report's own `ComponentRecord`. The rest —\n // instance counts, variant counts, the token list — is a census a reader opens\n // the report for; what a review page needs is the edges.\n for (const entry of report.composition?.components ?? []) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_composition\n (project, build, component, subjects, within, created_by, renders)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n entry.component,\n JSON.stringify(entry.subjects),\n JSON.stringify(entry.within),\n JSON.stringify(entry.createdBy),\n JSON.stringify(entry.renders),\n ),\n );\n }\n\n // The attribution, which is the part of the census a reviewer actually reads.\n // The rows above say `Card` draws `CardFooter`; these say the run climbed that\n // edge, found `ProductCard` in the diff, and concluded `CardFooter` moved on\n // what it was handed. Dropping them left the page to re-derive a worse answer\n // from the graph alone — without the props digests, the control group or the\n // source index the run had used — and to print *nothing declares this* about\n // components the run had already attributed to an edited parent.\n for (const entry of report.composition?.movements ?? []) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_movements\n (project, build, subject, component, cause, because, bands, held,\n compared, file, tokens, upstream, through, standing)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n )\n .bind(\n project,\n build.build,\n entry.subject,\n entry.component,\n entry.cause,\n entry.because,\n JSON.stringify(entry.bands),\n JSON.stringify(entry.held),\n entry.compared ?? null,\n entry.file ?? null,\n entry.tokens === undefined ? null : JSON.stringify(entry.tokens),\n entry.upstream ?? null,\n entry.through === undefined ? null : JSON.stringify(entry.through),\n entry.standing ?? null,\n ),\n );\n }\n\n for (const entry of report.notObserved ?? []) {\n statements.push(\n db\n .prepare(\n `INSERT OR REPLACE INTO build_not_observed (project, build, subject, kind, because)\n VALUES (?, ?, ?, ?, ?)`,\n )\n .bind(project, build.build, entry.subject, entry.kind, entry.because),\n );\n }\n\n await db.batch(statements);\n}\n"]}
@@ -0,0 +1,87 @@
1
+ import type { NotObserved, VariationRecord } from '@variance-authority/report';
2
+ import type { D1Like } from './bindings.js';
3
+ import { type Row } from './review-rows.js';
4
+ import type { BuildSummary, Cause, Declarations, DecisionRecord, MovementView, Placement, ReachView, SubjectView } from './review-types.js';
5
+ /**
6
+ * A build, read back out of D1 and turned into what a reviewer is shown.
7
+ *
8
+ * Apart from [`review.ts`](./review.ts) because nothing here writes anything:
9
+ * given rows, these produce the summary, the current decision per subject, the
10
+ * docket and the per-subject view, and every one of them is safe to call twice.
11
+ *
12
+ * Two of the project's arguments are implemented here and nowhere else — that the
13
+ * *current* decision is the highest sequence rather than the newest timestamp,
14
+ * and that the docket is ranked by cause pixels rather than by area — and both
15
+ * are written on the function that carries them, because that is what a later
16
+ * edit will be looking at when it is about to undo one.
17
+ */
18
+ export declare function summarize(db: D1Like, project: string, row: Row): Promise<BuildSummary>;
19
+ /**
20
+ * The current decision per subject, from an append-only table.
21
+ *
22
+ * `MAX(seq)` rather than `MAX(at_ms)`: two decisions can share a millisecond, and
23
+ * a tie broken arbitrarily would show a reviewer their earlier answer as the
24
+ * current one. The sequence is the order the rows were written and cannot tie.
25
+ */
26
+ export declare function latestDecisions(db: D1Like, project: string, build: string): Promise<ReadonlyMap<string, DecisionRecord>>;
27
+ /**
28
+ * The docket: one entry per cause component, with collateral counted.
29
+ *
30
+ * Ranked by cause pixels rather than by total area, because area measures
31
+ * *displacement* — a container that only reflowed outranks the component that was
32
+ * edited, measured at 6× on one edit. Collateral is summed into the entry rather
33
+ * than listed, so one token change across 300 subjects is one review item with a
34
+ * count.
35
+ */
36
+ export declare function docket(subjects: readonly SubjectView[]): readonly Cause[];
37
+ export declare function toSubjectView(row: Row, decision: DecisionRecord | null): SubjectView;
38
+ /**
39
+ * A stored variation, read back as the record the report wrote.
40
+ *
41
+ * Every optional field stays optional. `identical` is the one worth naming: the
42
+ * column is nullable and `NULL` is read as absent rather than as `false`, because
43
+ * a pair nothing compared and a pair compared and found to differ are different
44
+ * claims, and only the second is something a reviewer can act on.
45
+ *
46
+ * `how` is narrowed rather than cast. A row holding a word this deployment does
47
+ * not know is read as *unstated* — the sentence in `because` still says what
48
+ * happened, and asserting `declared` over an unrecognised value would put "a
49
+ * person said so" on an inference.
50
+ */
51
+ export declare function toVariation(row: Row): VariationRecord;
52
+ /**
53
+ * The reach rows, read back as the section the page draws.
54
+ *
55
+ * The one rule worth stating: `whole` present means `subjects` stays *absent*,
56
+ * even though the query returned an empty list either way. A refusal rendered as
57
+ * an empty attribution reads as "this commit reaches none of your subjects",
58
+ * which is the sentence somebody merges on — and it is the opposite of what the
59
+ * row says.
60
+ */
61
+ export declare function toReach(row: Row, subjects: readonly Row[]): ReachView;
62
+ /**
63
+ * One census row, read back as an edge a sentence can be built from.
64
+ *
65
+ * No shape checking beyond the parse, for the reason `toReach` gives: the
66
+ * columns were written from a typed value by `ingest`.
67
+ */
68
+ export declare function toPlacement(row: Row): Placement;
69
+ /**
70
+ * One attributed movement, read back as the run wrote it.
71
+ *
72
+ * `cause` is narrowed by comparison against the five rungs rather than cast. It
73
+ * is the only column here a later schema could widen, and a row carrying a rung
74
+ * this build does not know is a row it must refuse rather than print.
75
+ */
76
+ export declare function toMovement(row: Row): MovementView;
77
+ export declare function toNotObserved(row: Row): NotObserved;
78
+ /**
79
+ * The declaration ledgers, read back whole or read back as absent.
80
+ *
81
+ * No shape checking beyond the parse, and that is the same trade `toReach` makes:
82
+ * the column was written from a typed value by `ingest`, and a reader that
83
+ * re-validated it would be a second opinion about a shape this repository owns.
84
+ * What it does refuse is turning a missing column into an empty ledger.
85
+ */
86
+ export declare function toDeclarations(row: Row): Declarations;
87
+ //# sourceMappingURL=review-read.d.ts.map