@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.
- package/CHANGELOG.md +100 -0
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/dist/__fixtures__/review.d.ts +47 -0
- package/dist/__fixtures__/review.js +145 -0
- package/dist/__fixtures__/review.js.map +1 -0
- package/dist/bindings.d.ts +134 -0
- package/dist/bindings.js +88 -0
- package/dist/bindings.js.map +1 -0
- package/dist/changelog.d.ts +132 -0
- package/dist/changelog.js +129 -0
- package/dist/changelog.js.map +1 -0
- package/dist/history-queries.d.ts +82 -0
- package/dist/history-queries.js +200 -0
- package/dist/history-queries.js.map +1 -0
- package/dist/history-rows.d.ts +42 -0
- package/dist/history-rows.js +148 -0
- package/dist/history-rows.js.map +1 -0
- package/dist/history.d.ts +40 -0
- package/dist/history.js +186 -0
- package/dist/history.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/migration-steps.d.ts +32 -0
- package/dist/migration-steps.js +303 -0
- package/dist/migration-steps.js.map +1 -0
- package/dist/migrations.d.ts +58 -0
- package/dist/migrations.js +283 -0
- package/dist/migrations.js.map +1 -0
- package/dist/next.d.ts +94 -0
- package/dist/next.js +44 -0
- package/dist/next.js.map +1 -0
- package/dist/node/bin.d.ts +120 -0
- package/dist/node/bin.js +337 -0
- package/dist/node/bin.js.map +1 -0
- package/dist/node/bucket.d.ts +58 -0
- package/dist/node/bucket.js +142 -0
- package/dist/node/bucket.js.map +1 -0
- package/dist/node/database.d.ts +33 -0
- package/dist/node/database.js +157 -0
- package/dist/node/database.js.map +1 -0
- package/dist/node/index.d.ts +43 -0
- package/dist/node/index.js +43 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/serve.d.ts +81 -0
- package/dist/node/serve.js +193 -0
- package/dist/node/serve.js.map +1 -0
- package/dist/node/ui-assets.d.ts +47 -0
- package/dist/node/ui-assets.js +60 -0
- package/dist/node/ui-assets.js.map +1 -0
- package/dist/review-ingest.d.ts +24 -0
- package/dist/review-ingest.js +184 -0
- package/dist/review-ingest.js.map +1 -0
- package/dist/review-read.d.ts +87 -0
- package/dist/review-read.js +361 -0
- package/dist/review-read.js.map +1 -0
- package/dist/review-rows.d.ts +30 -0
- package/dist/review-rows.js +67 -0
- package/dist/review-rows.js.map +1 -0
- package/dist/review-types.d.ts +414 -0
- package/dist/review-types.js +2 -0
- package/dist/review-types.js.map +1 -0
- package/dist/review-write.d.ts +35 -0
- package/dist/review-write.js +86 -0
- package/dist/review-write.js.map +1 -0
- package/dist/review.d.ts +53 -0
- package/dist/review.fixtures.d.ts +47 -0
- package/dist/review.fixtures.js +145 -0
- package/dist/review.fixtures.js.map +1 -0
- package/dist/review.js +245 -0
- package/dist/review.js.map +1 -0
- package/dist/schema.d.ts +36 -0
- package/dist/schema.js +39 -0
- package/dist/schema.js.map +1 -0
- package/dist/store.d.ts +58 -0
- package/dist/store.js +278 -0
- package/dist/store.js.map +1 -0
- package/dist/testing.d.ts +38 -0
- package/dist/testing.js +92 -0
- package/dist/testing.js.map +1 -0
- package/dist/ui/__probe.d.ts +3 -0
- package/dist/ui/__probe.js +5 -0
- package/dist/ui/__probe.js.map +1 -0
- package/dist/ui/attribution.d.ts +46 -0
- package/dist/ui/attribution.js +79 -0
- package/dist/ui/attribution.js.map +1 -0
- package/dist/ui/because.d.ts +51 -0
- package/dist/ui/because.js +52 -0
- package/dist/ui/because.js.map +1 -0
- package/dist/ui/browser-entry.d.ts +2 -0
- package/dist/ui/browser-entry.js +34 -0
- package/dist/ui/browser-entry.js.map +1 -0
- package/dist/ui/builds.d.ts +43 -0
- package/dist/ui/builds.js +79 -0
- package/dist/ui/builds.js.map +1 -0
- package/dist/ui/carried.d.ts +73 -0
- package/dist/ui/carried.js +63 -0
- package/dist/ui/carried.js.map +1 -0
- package/dist/ui/cause.d.ts +73 -0
- package/dist/ui/cause.js +133 -0
- package/dist/ui/cause.js.map +1 -0
- package/dist/ui/change-story.d.ts +153 -0
- package/dist/ui/change-story.js +253 -0
- package/dist/ui/change-story.js.map +1 -0
- package/dist/ui/change.d.ts +51 -0
- package/dist/ui/change.js +125 -0
- package/dist/ui/change.js.map +1 -0
- package/dist/ui/client.d.ts +74 -0
- package/dist/ui/client.js +88 -0
- package/dist/ui/client.js.map +1 -0
- package/dist/ui/consumers.d.ts +80 -0
- package/dist/ui/consumers.js +125 -0
- package/dist/ui/consumers.js.map +1 -0
- package/dist/ui/control.d.ts +63 -0
- package/dist/ui/control.js +39 -0
- package/dist/ui/control.js.map +1 -0
- package/dist/ui/crossing.d.ts +55 -0
- package/dist/ui/crossing.js +74 -0
- package/dist/ui/crossing.js.map +1 -0
- package/dist/ui/declarations.d.ts +36 -0
- package/dist/ui/declarations.js +73 -0
- package/dist/ui/declarations.js.map +1 -0
- package/dist/ui/distance.d.ts +154 -0
- package/dist/ui/distance.js +180 -0
- package/dist/ui/distance.js.map +1 -0
- package/dist/ui/divergence.d.ts +40 -0
- package/dist/ui/divergence.js +176 -0
- package/dist/ui/divergence.js.map +1 -0
- package/dist/ui/docket.d.ts +39 -0
- package/dist/ui/docket.js +172 -0
- package/dist/ui/docket.js.map +1 -0
- package/dist/ui/findings.d.ts +42 -0
- package/dist/ui/findings.js +110 -0
- package/dist/ui/findings.js.map +1 -0
- package/dist/ui/foreseen.d.ts +55 -0
- package/dist/ui/foreseen.js +91 -0
- package/dist/ui/foreseen.js.map +1 -0
- package/dist/ui/glance.d.ts +101 -0
- package/dist/ui/glance.js +101 -0
- package/dist/ui/glance.js.map +1 -0
- package/dist/ui/grouping.d.ts +158 -0
- package/dist/ui/grouping.js +187 -0
- package/dist/ui/grouping.js.map +1 -0
- package/dist/ui/handed.d.ts +60 -0
- package/dist/ui/handed.js +122 -0
- package/dist/ui/handed.js.map +1 -0
- package/dist/ui/history.d.ts +88 -0
- package/dist/ui/history.js +170 -0
- package/dist/ui/history.js.map +1 -0
- package/dist/ui/holding.d.ts +84 -0
- package/dist/ui/holding.js +90 -0
- package/dist/ui/holding.js.map +1 -0
- package/dist/ui/impact.d.ts +31 -0
- package/dist/ui/impact.js +132 -0
- package/dist/ui/impact.js.map +1 -0
- package/dist/ui/index.d.ts +21 -0
- package/dist/ui/index.js +21 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/lead.d.ts +35 -0
- package/dist/ui/lead.js +34 -0
- package/dist/ui/lead.js.map +1 -0
- package/dist/ui/look.d.ts +109 -0
- package/dist/ui/look.js +248 -0
- package/dist/ui/look.js.map +1 -0
- package/dist/ui/mark.d.ts +11 -0
- package/dist/ui/mark.js +13 -0
- package/dist/ui/mark.js.map +1 -0
- package/dist/ui/moved.d.ts +121 -0
- package/dist/ui/moved.js +143 -0
- package/dist/ui/moved.js.map +1 -0
- package/dist/ui/navigation.d.ts +26 -0
- package/dist/ui/navigation.js +53 -0
- package/dist/ui/navigation.js.map +1 -0
- package/dist/ui/order.d.ts +113 -0
- package/dist/ui/order.js +226 -0
- package/dist/ui/order.js.map +1 -0
- package/dist/ui/origins.d.ts +42 -0
- package/dist/ui/origins.js +149 -0
- package/dist/ui/origins.js.map +1 -0
- package/dist/ui/outcome.d.ts +93 -0
- package/dist/ui/outcome.js +162 -0
- package/dist/ui/outcome.js.map +1 -0
- package/dist/ui/parted.d.ts +68 -0
- package/dist/ui/parted.js +101 -0
- package/dist/ui/parted.js.map +1 -0
- package/dist/ui/rail.d.ts +31 -0
- package/dist/ui/rail.js +21 -0
- package/dist/ui/rail.js.map +1 -0
- package/dist/ui/reach.d.ts +66 -0
- package/dist/ui/reach.js +185 -0
- package/dist/ui/reach.js.map +1 -0
- package/dist/ui/regions.d.ts +82 -0
- package/dist/ui/regions.js +67 -0
- package/dist/ui/regions.js.map +1 -0
- package/dist/ui/review.bundle.js +69 -0
- package/dist/ui/review.d.ts +97 -0
- package/dist/ui/review.js +39 -0
- package/dist/ui/review.js.map +1 -0
- package/dist/ui/root.d.ts +72 -0
- package/dist/ui/root.js +102 -0
- package/dist/ui/root.js.map +1 -0
- package/dist/ui/route.d.ts +95 -0
- package/dist/ui/route.js +114 -0
- package/dist/ui/route.js.map +1 -0
- package/dist/ui/run.d.ts +26 -0
- package/dist/ui/run.js +12 -0
- package/dist/ui/run.js.map +1 -0
- package/dist/ui/sense.d.ts +187 -0
- package/dist/ui/sense.js +222 -0
- package/dist/ui/sense.js.map +1 -0
- package/dist/ui/settled.d.ts +43 -0
- package/dist/ui/settled.js +59 -0
- package/dist/ui/settled.js.map +1 -0
- package/dist/ui/shell.d.ts +76 -0
- package/dist/ui/shell.js +65 -0
- package/dist/ui/shell.js.map +1 -0
- package/dist/ui/shift.d.ts +52 -0
- package/dist/ui/shift.js +98 -0
- package/dist/ui/shift.js.map +1 -0
- package/dist/ui/styles-docket.d.ts +18 -0
- package/dist/ui/styles-docket.js +281 -0
- package/dist/ui/styles-docket.js.map +1 -0
- package/dist/ui/styles-stage.d.ts +18 -0
- package/dist/ui/styles-stage.js +92 -0
- package/dist/ui/styles-stage.js.map +1 -0
- package/dist/ui/styles.d.ts +12 -0
- package/dist/ui/styles.js +482 -0
- package/dist/ui/styles.js.map +1 -0
- package/dist/ui/subject.d.ts +36 -0
- package/dist/ui/subject.js +138 -0
- package/dist/ui/subject.js.map +1 -0
- package/dist/ui/text.d.ts +129 -0
- package/dist/ui/text.js +164 -0
- package/dist/ui/text.js.map +1 -0
- package/dist/ui/variations.d.ts +37 -0
- package/dist/ui/variations.js +60 -0
- package/dist/ui/variations.js.map +1 -0
- package/dist/ui/viewer.d.ts +67 -0
- package/dist/ui/viewer.js +246 -0
- package/dist/ui/viewer.js.map +1 -0
- package/dist/worker-auth.d.ts +48 -0
- package/dist/worker-auth.js +74 -0
- package/dist/worker-auth.js.map +1 -0
- package/dist/worker-entry.d.ts +58 -0
- package/dist/worker-entry.js +36 -0
- package/dist/worker-entry.js.map +1 -0
- package/dist/worker-http.d.ts +39 -0
- package/dist/worker-http.js +111 -0
- package/dist/worker-http.js.map +1 -0
- package/dist/worker-input.d.ts +81 -0
- package/dist/worker-input.js +296 -0
- package/dist/worker-input.js.map +1 -0
- package/dist/worker.d.ts +91 -0
- package/dist/worker.js +287 -0
- package/dist/worker.js.map +1 -0
- package/mark.svg +30 -0
- package/migrations/0001_initial.sql +193 -0
- package/migrations/0002_changelog.sql +30 -0
- package/migrations/0003_accessibility.sql +8 -0
- package/migrations/0004_variations.sql +19 -0
- package/migrations/0005_reach.sql +26 -0
- package/migrations/0006_baseline-size.sql +7 -0
- package/migrations/0007_declarations.sql +7 -0
- package/migrations/0008_green-because.sql +7 -0
- package/migrations/0009_moved-bands.sql +6 -0
- package/migrations/0010_composition.sql +15 -0
- package/migrations/0011_movements.sql +21 -0
- package/migrations/0012_control-size.sql +6 -0
- package/package.json +110 -0
- 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
|