@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,296 @@
|
|
|
1
|
+
import { BANDS as FREQUENCY_BANDS, profileById, } from '@variance-authority/core';
|
|
2
|
+
import { BANDS, MAX_CURRENT_SUBJECTS, } from '@variance-authority/history';
|
|
3
|
+
import { identityFrom } from '@variance-authority/raster';
|
|
4
|
+
import { BadRequest, array, asRecordBody, describe, flag, instant, optional, record, string, } from './worker-http.js';
|
|
5
|
+
/**
|
|
6
|
+
* Every request body and query string this deployment accepts, checked before any
|
|
7
|
+
* of it reaches a store.
|
|
8
|
+
*
|
|
9
|
+
* One file because these share a deadline rather than a subject. The baseline
|
|
10
|
+
* routes, the history routes and the build ingest all write to something that
|
|
11
|
+
* does not forget, and each function below is the last moment its route can still
|
|
12
|
+
* refuse — the history record is append-only, a promoted baseline is what the
|
|
13
|
+
* next run compares against, and a build row outlives the request that made it.
|
|
14
|
+
* What each one checks differs, and the argument for it sits on the function.
|
|
15
|
+
*
|
|
16
|
+
* Reading a *response* is a different job with a different consequence and is not
|
|
17
|
+
* here: `identityFrom` and `rasterFrom` come from `@variance-authority/raster`
|
|
18
|
+
* because a misread response becomes a verdict, while everything below becomes a
|
|
19
|
+
* 4xx nobody mistakes for an answer.
|
|
20
|
+
*/
|
|
21
|
+
export function asKey(value) {
|
|
22
|
+
const source = record(value, '`key`');
|
|
23
|
+
const label = source['label'];
|
|
24
|
+
if (label !== undefined && typeof label !== 'string') {
|
|
25
|
+
throw new BadRequest('`key.label` must be a string when present');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
subject: string(source, 'subject', '`key`'),
|
|
29
|
+
...(typeof label === 'string' ? { label } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function asIdentity(value) {
|
|
33
|
+
const identity = identityFrom(value);
|
|
34
|
+
if (identity === null) {
|
|
35
|
+
// Never defaulted, and never partially rebuilt. An identity missing a field
|
|
36
|
+
// is a different machine from the one that wrote the baseline, and inventing
|
|
37
|
+
// the field would make a wrong-machine comparison look comparable.
|
|
38
|
+
throw new BadRequest('`identity` is not a renderer identity. Every field is required, because the identity is ' +
|
|
39
|
+
'the partition that decides whether two images may be compared at all');
|
|
40
|
+
}
|
|
41
|
+
return identity;
|
|
42
|
+
}
|
|
43
|
+
export function asDecision(value) {
|
|
44
|
+
if (value !== 'approved' && value !== 'rejected') {
|
|
45
|
+
throw new BadRequest(`\`decision\` must be "approved" or "rejected"; received ${describe(value)}`);
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A build, checked before any of it is stored.
|
|
51
|
+
*
|
|
52
|
+
* The report itself is handed to `review.ingest` as-is rather than rebuilt field
|
|
53
|
+
* by field, and that is a deliberate asymmetry with the baseline routes. A
|
|
54
|
+
* misread *response* becomes a verdict; a misread *request* becomes a 4xx nobody
|
|
55
|
+
* mistakes for an answer. What is checked here is what the store would otherwise
|
|
56
|
+
* crash on or silently mis-record — the version, and the shape of the wrapper.
|
|
57
|
+
*/
|
|
58
|
+
export async function asBuildIngest(request) {
|
|
59
|
+
const body = await asRecordBody(request);
|
|
60
|
+
const report = record(body['report'], '`report`');
|
|
61
|
+
if (report['runVersion'] !== 1) {
|
|
62
|
+
throw new BadRequest(`\`report.runVersion\` must be 1; received ${describe(report['runVersion'])}. A report from ` +
|
|
63
|
+
'a writer this deployment does not understand would be partly stored and wholly believed');
|
|
64
|
+
}
|
|
65
|
+
if (!Array.isArray(report['observations'])) {
|
|
66
|
+
throw new BadRequest('`report.observations` must be an array');
|
|
67
|
+
}
|
|
68
|
+
const branch = body['branch'];
|
|
69
|
+
const images = body['images'];
|
|
70
|
+
return {
|
|
71
|
+
build: string(body, 'build', 'the build'),
|
|
72
|
+
commit: string(body, 'commit', 'the build'),
|
|
73
|
+
report: report,
|
|
74
|
+
...(typeof branch === 'string' && branch !== '' ? { branch } : {}),
|
|
75
|
+
...(images === undefined || images === null
|
|
76
|
+
? {}
|
|
77
|
+
: { images: record(images, '`images`') }),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The window, validated before it can quietly select nothing.
|
|
82
|
+
*
|
|
83
|
+
* An unparseable `since` would reach the query as `NaN`, bind as NULL, match no
|
|
84
|
+
* row, and answer a churn of zero over zero runs — indistinguishable from a
|
|
85
|
+
* component that has never changed. A limit of zero is refused for the same
|
|
86
|
+
* reason: it asks for an answer computed over nothing, dressed as an answer.
|
|
87
|
+
*/
|
|
88
|
+
export function windowOf(url) {
|
|
89
|
+
const since = optional(url, 'since');
|
|
90
|
+
const until = optional(url, 'until');
|
|
91
|
+
const limit = optional(url, 'limit');
|
|
92
|
+
if (since !== undefined && Number.isNaN(Date.parse(since))) {
|
|
93
|
+
throw new BadRequest(`\`since\` must be an ISO-8601 instant; received "${since}"`);
|
|
94
|
+
}
|
|
95
|
+
if (until !== undefined && Number.isNaN(Date.parse(until))) {
|
|
96
|
+
throw new BadRequest(`\`until\` must be an ISO-8601 instant; received "${until}"`);
|
|
97
|
+
}
|
|
98
|
+
let parsedLimit;
|
|
99
|
+
if (limit !== undefined) {
|
|
100
|
+
parsedLimit = Number(limit);
|
|
101
|
+
if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {
|
|
102
|
+
throw new BadRequest(`\`limit\` must be a whole number of at least 1; received "${limit}". A limit of 0 asks ` +
|
|
103
|
+
'for a drift answer computed over no rows, which reads as stability');
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
...(since !== undefined ? { since } : {}),
|
|
108
|
+
...(until !== undefined ? { until } : {}),
|
|
109
|
+
...(parsedLimit !== undefined ? { limit: parsedLimit } : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* A write, validated completely before any of it is appended.
|
|
114
|
+
*
|
|
115
|
+
* The history store is append-only, so this is the last moment anything can be
|
|
116
|
+
* refused. A row with an unknown band or an unparseable instant that gets in
|
|
117
|
+
* stays in, and every later query over that window either throws or silently
|
|
118
|
+
* misorders — so the strictness here is not politeness about input, it is the
|
|
119
|
+
* only place the invariant can still be enforced.
|
|
120
|
+
*/
|
|
121
|
+
export async function asRecordRequest(request) {
|
|
122
|
+
const body = await asRecordBody(request);
|
|
123
|
+
return {
|
|
124
|
+
run: asRunRecord(body['run']),
|
|
125
|
+
observations: array(body['observations'], '`observations`').map((row, index) => asObservation(row, `observations[${index}]`)),
|
|
126
|
+
tokens: array(body['tokens'], '`tokens`').map((row, index) => asTokenValue(row, `tokens[${index}]`)),
|
|
127
|
+
// Absent is an empty list here and nowhere else in this file, because the
|
|
128
|
+
// claim it makes is carried by a different field: `run.swept` says whether
|
|
129
|
+
// anything examined every subject, so "no instabilities" from a caller that
|
|
130
|
+
// never looked cannot be read as "nothing read differently".
|
|
131
|
+
instabilities: array(body['instabilities'] ?? [], '`instabilities`').map((row, index) => asInstability(row, `instabilities[${index}]`)),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* An acceptance, and who made it when the surface that recorded it knows.
|
|
136
|
+
*
|
|
137
|
+
* The route this parses is how churn learns that anything shipped. Every
|
|
138
|
+
* observation is written unapproved — a run has not been reviewed at the moment
|
|
139
|
+
* it writes — so a deployment that could not take approvals answers *this
|
|
140
|
+
* component has never changed* about a component that changed forty times.
|
|
141
|
+
*/
|
|
142
|
+
export async function asApprovals(request) {
|
|
143
|
+
const body = await asRecordBody(request);
|
|
144
|
+
return array(body['approvals'], '`approvals`').map((row, index) => {
|
|
145
|
+
const what = `approvals[${index}]`;
|
|
146
|
+
const source = record(row, what);
|
|
147
|
+
const by = source['by'];
|
|
148
|
+
if (by !== undefined && by !== null && (typeof by !== 'string' || by === '')) {
|
|
149
|
+
throw new BadRequest(`${what}.by must be a non-empty string when present; received ${describe(by)}`);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
project: string(source, 'project', what),
|
|
153
|
+
subject: string(source, 'subject', what),
|
|
154
|
+
run: string(source, 'run', what),
|
|
155
|
+
at: instant(source, 'at', what),
|
|
156
|
+
...(typeof by === 'string' ? { by } : {}),
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The subject list a run asks about before it writes.
|
|
162
|
+
*
|
|
163
|
+
* The only validation here that refuses a request for being *large* rather than
|
|
164
|
+
* malformed, and the reason is that the answer cannot be trimmed: a `previous`
|
|
165
|
+
* set with a hole in it is a change that did not happen, appended to a store that
|
|
166
|
+
* does not forget. So a caller naming more than one request may carry is told the
|
|
167
|
+
* cap rather than answered with the first {@link MAX_CURRENT_SUBJECTS} and left
|
|
168
|
+
* looking successful.
|
|
169
|
+
*/
|
|
170
|
+
export async function asCurrentRequest(request) {
|
|
171
|
+
const body = await asRecordBody(request);
|
|
172
|
+
const subjects = array(body['subjects'], '`subjects`').map((value, index) => {
|
|
173
|
+
if (typeof value !== 'string' || value === '') {
|
|
174
|
+
throw new BadRequest(`subjects[${index}] must be a non-empty string; received ${describe(value)}`);
|
|
175
|
+
}
|
|
176
|
+
return value;
|
|
177
|
+
});
|
|
178
|
+
const distinct = new Set(subjects).size;
|
|
179
|
+
if (distinct > MAX_CURRENT_SUBJECTS) {
|
|
180
|
+
throw new BadRequest(`this request names ${distinct} subjects and one \`current\` request may name ` +
|
|
181
|
+
`${MAX_CURRENT_SUBJECTS}. The answer is not trimmed to fit — a missing previous row is ` +
|
|
182
|
+
'indistinguishable from a hash that was never recorded, so the run would append a change ' +
|
|
183
|
+
'that did not happen — so the request is refused and the caller splits its list');
|
|
184
|
+
}
|
|
185
|
+
return subjects;
|
|
186
|
+
}
|
|
187
|
+
export function asBand(value, what) {
|
|
188
|
+
if (typeof value !== 'string' || !BANDS.includes(value)) {
|
|
189
|
+
throw new BadRequest(`${what} must be one of ${BANDS.join(', ')}; received ${describe(value)}`);
|
|
190
|
+
}
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
/** Checked against `core`'s own table, so a tier added there needs no edit here. */
|
|
194
|
+
function asProfile(value, what) {
|
|
195
|
+
const known = typeof value === 'string' ? profileById(value) : undefined;
|
|
196
|
+
if (known === undefined) {
|
|
197
|
+
throw new BadRequest(`${what}.profile is not a known observation profile: ${describe(value)}`);
|
|
198
|
+
}
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
function asRunRecord(value) {
|
|
202
|
+
const what = '`run`';
|
|
203
|
+
const source = record(value, what);
|
|
204
|
+
const swept = source['swept'];
|
|
205
|
+
if (swept !== undefined && swept !== null && typeof swept !== 'boolean') {
|
|
206
|
+
throw new BadRequest(`${what}.swept must be a boolean when present; received ${describe(swept)}`);
|
|
207
|
+
}
|
|
208
|
+
return {
|
|
209
|
+
project: string(source, 'project', what),
|
|
210
|
+
run: string(source, 'run', what),
|
|
211
|
+
commit: string(source, 'commit', what),
|
|
212
|
+
profile: asProfile(source['profile'], what),
|
|
213
|
+
at: instant(source, 'at', what),
|
|
214
|
+
// Absent stays absent through the whole path: a run that never said what it
|
|
215
|
+
// examined must not be stored as one that examined nothing, because that
|
|
216
|
+
// number becomes the denominator of a flake rate.
|
|
217
|
+
...(typeof swept === 'boolean' ? { swept } : {}),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* One occurrence of a subject failing to read the same way twice.
|
|
222
|
+
*
|
|
223
|
+
* `component` and `band` are optional and their absence is meaningful: a
|
|
224
|
+
* collector that supplied documents without snapshots proved the instability and
|
|
225
|
+
* gave nobody the means to name it. An empty string is refused rather than
|
|
226
|
+
* stored, because it would come back as a component named "".
|
|
227
|
+
*/
|
|
228
|
+
function asInstability(value, what) {
|
|
229
|
+
const source = record(value, what);
|
|
230
|
+
const component = source['component'];
|
|
231
|
+
const band = source['band'];
|
|
232
|
+
const absorbedBy = source['absorbedBy'];
|
|
233
|
+
for (const [key, held] of [
|
|
234
|
+
['component', component],
|
|
235
|
+
['absorbedBy', absorbedBy],
|
|
236
|
+
]) {
|
|
237
|
+
if (held !== undefined && held !== null && (typeof held !== 'string' || held === '')) {
|
|
238
|
+
throw new BadRequest(`${what}.${key} must be a non-empty string when present; received ${describe(held)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
project: string(source, 'project', what),
|
|
243
|
+
subject: string(source, 'subject', what),
|
|
244
|
+
...(typeof component === 'string' ? { component } : {}),
|
|
245
|
+
...(band === undefined || band === null ? {} : { band: asFrequencyBand(band, `${what}.band`) }),
|
|
246
|
+
profile: asProfile(source['profile'], what),
|
|
247
|
+
commit: string(source, 'commit', what),
|
|
248
|
+
run: string(source, 'run', what),
|
|
249
|
+
at: instant(source, 'at', what),
|
|
250
|
+
...(typeof absorbedBy === 'string' ? { absorbedBy } : {}),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* A frequency band, checked against `core`'s list rather than this package's.
|
|
255
|
+
*
|
|
256
|
+
* The two `Band` types classify different things — one names which part of a
|
|
257
|
+
* component was hashed, the other how often that kind of thing changes — and an
|
|
258
|
+
* instability is reported in the second, because that is what a fix is aimed at.
|
|
259
|
+
*/
|
|
260
|
+
function asFrequencyBand(value, what) {
|
|
261
|
+
if (typeof value !== 'string' || !FREQUENCY_BANDS.includes(value)) {
|
|
262
|
+
throw new BadRequest(`${what} must be one of ${FREQUENCY_BANDS.join(', ')}; received ${describe(value)}`);
|
|
263
|
+
}
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
function asObservation(value, what) {
|
|
267
|
+
const source = record(value, what);
|
|
268
|
+
const file = source['file'];
|
|
269
|
+
if (file !== undefined && file !== null && typeof file !== 'string') {
|
|
270
|
+
throw new BadRequest(`${what}.file must be a string when present; received ${describe(file)}`);
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
project: string(source, 'project', what),
|
|
274
|
+
subject: string(source, 'subject', what),
|
|
275
|
+
component: string(source, 'component', what),
|
|
276
|
+
band: asBand(source['band'], `${what}.band`),
|
|
277
|
+
hash: string(source, 'hash', what),
|
|
278
|
+
profile: asProfile(source['profile'], what),
|
|
279
|
+
commit: string(source, 'commit', what),
|
|
280
|
+
run: string(source, 'run', what),
|
|
281
|
+
at: instant(source, 'at', what),
|
|
282
|
+
accepted: flag(source, 'accepted', what),
|
|
283
|
+
...(typeof file === 'string' && file !== '' ? { file } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function asTokenValue(value, what) {
|
|
287
|
+
const source = record(value, what);
|
|
288
|
+
return {
|
|
289
|
+
project: string(source, 'project', what),
|
|
290
|
+
token: string(source, 'token', what),
|
|
291
|
+
value: string(source, 'value', what),
|
|
292
|
+
commit: string(source, 'commit', what),
|
|
293
|
+
at: instant(source, 'at', what),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=worker-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-input.js","sourceRoot":"","sources":["../src/worker-input.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BANDS as FREQUENCY_BANDS,\n profileById,\n type Digest,\n type ProfileId,\n} from '@variance-authority/core';\nimport {\n BANDS,\n MAX_CURRENT_SUBJECTS,\n type Approval,\n type Band,\n type FrequencyBand,\n type Instability,\n type Observation,\n type RunRecord,\n type TokenValue,\n type Window,\n} from '@variance-authority/history';\nimport { identityFrom } from '@variance-authority/raster';\nimport type { RunReport } from '@variance-authority/report';\nimport type { BuildIngest, Decision, SubjectImages } from './review-types.js';\nimport {\n BadRequest,\n array,\n asRecordBody,\n describe,\n flag,\n instant,\n optional,\n record,\n string,\n} from './worker-http.js';\n\n/**\n * Every request body and query string this deployment accepts, checked before any\n * of it reaches a store.\n *\n * One file because these share a deadline rather than a subject. The baseline\n * routes, the history routes and the build ingest all write to something that\n * does not forget, and each function below is the last moment its route can still\n * refuse — the history record is append-only, a promoted baseline is what the\n * next run compares against, and a build row outlives the request that made it.\n * What each one checks differs, and the argument for it sits on the function.\n *\n * Reading a *response* is a different job with a different consequence and is not\n * here: `identityFrom` and `rasterFrom` come from `@variance-authority/raster`\n * because a misread response becomes a verdict, while everything below becomes a\n * 4xx nobody mistakes for an answer.\n */\n\nexport function asKey(value: unknown): { readonly subject: string; readonly label?: string } {\n const source = record(value, '`key`');\n const label = source['label'];\n if (label !== undefined && typeof label !== 'string') {\n throw new BadRequest('`key.label` must be a string when present');\n }\n return {\n subject: string(source, 'subject', '`key`'),\n ...(typeof label === 'string' ? { label } : {}),\n };\n}\n\nexport function asIdentity(value: unknown): ReturnType<typeof identityFrom> & object {\n const identity = identityFrom(value);\n if (identity === null) {\n // Never defaulted, and never partially rebuilt. An identity missing a field\n // is a different machine from the one that wrote the baseline, and inventing\n // the field would make a wrong-machine comparison look comparable.\n throw new BadRequest(\n '`identity` is not a renderer identity. Every field is required, because the identity is ' +\n 'the partition that decides whether two images may be compared at all',\n );\n }\n return identity;\n}\n\nexport function asDecision(value: unknown): Decision {\n if (value !== 'approved' && value !== 'rejected') {\n throw new BadRequest(`\\`decision\\` must be \"approved\" or \"rejected\"; received ${describe(value)}`);\n }\n return value;\n}\n\n/**\n * A build, checked before any of it is stored.\n *\n * The report itself is handed to `review.ingest` as-is rather than rebuilt field\n * by field, and that is a deliberate asymmetry with the baseline routes. A\n * misread *response* becomes a verdict; a misread *request* becomes a 4xx nobody\n * mistakes for an answer. What is checked here is what the store would otherwise\n * crash on or silently mis-record — the version, and the shape of the wrapper.\n */\nexport async function asBuildIngest(request: Request): Promise<BuildIngest> {\n const body = await asRecordBody(request);\n const report = record(body['report'], '`report`');\n\n if (report['runVersion'] !== 1) {\n throw new BadRequest(\n `\\`report.runVersion\\` must be 1; received ${describe(report['runVersion'])}. A report from ` +\n 'a writer this deployment does not understand would be partly stored and wholly believed',\n );\n }\n if (!Array.isArray(report['observations'])) {\n throw new BadRequest('`report.observations` must be an array');\n }\n\n const branch = body['branch'];\n const images = body['images'];\n\n return {\n build: string(body, 'build', 'the build'),\n commit: string(body, 'commit', 'the build'),\n report: report as unknown as RunReport,\n ...(typeof branch === 'string' && branch !== '' ? { branch } : {}),\n ...(images === undefined || images === null\n ? {}\n : { images: record(images, '`images`') as Readonly<Record<string, SubjectImages>> }),\n };\n}\n\n/**\n * The window, validated before it can quietly select nothing.\n *\n * An unparseable `since` would reach the query as `NaN`, bind as NULL, match no\n * row, and answer a churn of zero over zero runs — indistinguishable from a\n * component that has never changed. A limit of zero is refused for the same\n * reason: it asks for an answer computed over nothing, dressed as an answer.\n */\nexport function windowOf(url: URL): Window {\n const since = optional(url, 'since');\n const until = optional(url, 'until');\n const limit = optional(url, 'limit');\n\n if (since !== undefined && Number.isNaN(Date.parse(since))) {\n throw new BadRequest(`\\`since\\` must be an ISO-8601 instant; received \"${since}\"`);\n }\n if (until !== undefined && Number.isNaN(Date.parse(until))) {\n throw new BadRequest(`\\`until\\` must be an ISO-8601 instant; received \"${until}\"`);\n }\n\n let parsedLimit: number | undefined;\n if (limit !== undefined) {\n parsedLimit = Number(limit);\n if (!Number.isInteger(parsedLimit) || parsedLimit < 1) {\n throw new BadRequest(\n `\\`limit\\` must be a whole number of at least 1; received \"${limit}\". A limit of 0 asks ` +\n 'for a drift answer computed over no rows, which reads as stability',\n );\n }\n }\n\n return {\n ...(since !== undefined ? { since } : {}),\n ...(until !== undefined ? { until } : {}),\n ...(parsedLimit !== undefined ? { limit: parsedLimit } : {}),\n };\n}\n\n/**\n * A write, validated completely before any of it is appended.\n *\n * The history store is append-only, so this is the last moment anything can be\n * refused. A row with an unknown band or an unparseable instant that gets in\n * stays in, and every later query over that window either throws or silently\n * misorders — so the strictness here is not politeness about input, it is the\n * only place the invariant can still be enforced.\n */\nexport async function asRecordRequest(request: Request): Promise<{\n readonly run: RunRecord;\n readonly observations: readonly Observation[];\n readonly tokens: readonly TokenValue[];\n readonly instabilities: readonly Instability[];\n}> {\n const body = await asRecordBody(request);\n\n return {\n run: asRunRecord(body['run']),\n observations: array(body['observations'], '`observations`').map((row, index) =>\n asObservation(row, `observations[${index}]`),\n ),\n tokens: array(body['tokens'], '`tokens`').map((row, index) =>\n asTokenValue(row, `tokens[${index}]`),\n ),\n // Absent is an empty list here and nowhere else in this file, because the\n // claim it makes is carried by a different field: `run.swept` says whether\n // anything examined every subject, so \"no instabilities\" from a caller that\n // never looked cannot be read as \"nothing read differently\".\n instabilities: array(body['instabilities'] ?? [], '`instabilities`').map((row, index) =>\n asInstability(row, `instabilities[${index}]`),\n ),\n };\n}\n\n/**\n * An acceptance, and who made it when the surface that recorded it knows.\n *\n * The route this parses is how churn learns that anything shipped. Every\n * observation is written unapproved — a run has not been reviewed at the moment\n * it writes — so a deployment that could not take approvals answers *this\n * component has never changed* about a component that changed forty times.\n */\nexport async function asApprovals(request: Request): Promise<readonly Approval[]> {\n const body = await asRecordBody(request);\n\n return array(body['approvals'], '`approvals`').map((row, index) => {\n const what = `approvals[${index}]`;\n const source = record(row, what);\n const by = source['by'];\n\n if (by !== undefined && by !== null && (typeof by !== 'string' || by === '')) {\n throw new BadRequest(\n `${what}.by must be a non-empty string when present; received ${describe(by)}`,\n );\n }\n\n return {\n project: string(source, 'project', what),\n subject: string(source, 'subject', what),\n run: string(source, 'run', what),\n at: instant(source, 'at', what),\n ...(typeof by === 'string' ? { by } : {}),\n };\n });\n}\n\n/**\n * The subject list a run asks about before it writes.\n *\n * The only validation here that refuses a request for being *large* rather than\n * malformed, and the reason is that the answer cannot be trimmed: a `previous`\n * set with a hole in it is a change that did not happen, appended to a store that\n * does not forget. So a caller naming more than one request may carry is told the\n * cap rather than answered with the first {@link MAX_CURRENT_SUBJECTS} and left\n * looking successful.\n */\nexport async function asCurrentRequest(request: Request): Promise<readonly string[]> {\n const body = await asRecordBody(request);\n\n const subjects = array(body['subjects'], '`subjects`').map((value, index) => {\n if (typeof value !== 'string' || value === '') {\n throw new BadRequest(\n `subjects[${index}] must be a non-empty string; received ${describe(value)}`,\n );\n }\n return value;\n });\n\n const distinct = new Set(subjects).size;\n if (distinct > MAX_CURRENT_SUBJECTS) {\n throw new BadRequest(\n `this request names ${distinct} subjects and one \\`current\\` request may name ` +\n `${MAX_CURRENT_SUBJECTS}. The answer is not trimmed to fit — a missing previous row is ` +\n 'indistinguishable from a hash that was never recorded, so the run would append a change ' +\n 'that did not happen — so the request is refused and the caller splits its list',\n );\n }\n\n return subjects;\n}\n\nexport function asBand(value: unknown, what: string): Band {\n if (typeof value !== 'string' || !BANDS.includes(value as Band)) {\n throw new BadRequest(`${what} must be one of ${BANDS.join(', ')}; received ${describe(value)}`);\n }\n return value as Band;\n}\n\n/** Checked against `core`'s own table, so a tier added there needs no edit here. */\nfunction asProfile(value: unknown, what: string): ProfileId {\n const known: unknown = typeof value === 'string' ? profileById(value as ProfileId) : undefined;\n if (known === undefined) {\n throw new BadRequest(`${what}.profile is not a known observation profile: ${describe(value)}`);\n }\n return value as ProfileId;\n}\n\nfunction asRunRecord(value: unknown): RunRecord {\n const what = '`run`';\n const source = record(value, what);\n const swept = source['swept'];\n\n if (swept !== undefined && swept !== null && typeof swept !== 'boolean') {\n throw new BadRequest(`${what}.swept must be a boolean when present; received ${describe(swept)}`);\n }\n\n return {\n project: string(source, 'project', what),\n run: string(source, 'run', what),\n commit: string(source, 'commit', what),\n profile: asProfile(source['profile'], what),\n at: instant(source, 'at', what),\n // Absent stays absent through the whole path: a run that never said what it\n // examined must not be stored as one that examined nothing, because that\n // number becomes the denominator of a flake rate.\n ...(typeof swept === 'boolean' ? { swept } : {}),\n };\n}\n\n/**\n * One occurrence of a subject failing to read the same way twice.\n *\n * `component` and `band` are optional and their absence is meaningful: a\n * collector that supplied documents without snapshots proved the instability and\n * gave nobody the means to name it. An empty string is refused rather than\n * stored, because it would come back as a component named \"\".\n */\nfunction asInstability(value: unknown, what: string): Instability {\n const source = record(value, what);\n const component = source['component'];\n const band = source['band'];\n const absorbedBy = source['absorbedBy'];\n\n for (const [key, held] of [\n ['component', component],\n ['absorbedBy', absorbedBy],\n ] as const) {\n if (held !== undefined && held !== null && (typeof held !== 'string' || held === '')) {\n throw new BadRequest(\n `${what}.${key} must be a non-empty string when present; received ${describe(held)}`,\n );\n }\n }\n\n return {\n project: string(source, 'project', what),\n subject: string(source, 'subject', what),\n ...(typeof component === 'string' ? { component } : {}),\n ...(band === undefined || band === null ? {} : { band: asFrequencyBand(band, `${what}.band`) }),\n profile: asProfile(source['profile'], what),\n commit: string(source, 'commit', what),\n run: string(source, 'run', what),\n at: instant(source, 'at', what),\n ...(typeof absorbedBy === 'string' ? { absorbedBy } : {}),\n };\n}\n\n/**\n * A frequency band, checked against `core`'s list rather than this package's.\n *\n * The two `Band` types classify different things — one names which part of a\n * component was hashed, the other how often that kind of thing changes — and an\n * instability is reported in the second, because that is what a fix is aimed at.\n */\nfunction asFrequencyBand(value: unknown, what: string): FrequencyBand {\n if (typeof value !== 'string' || !FREQUENCY_BANDS.includes(value as FrequencyBand)) {\n throw new BadRequest(\n `${what} must be one of ${FREQUENCY_BANDS.join(', ')}; received ${describe(value)}`,\n );\n }\n return value as FrequencyBand;\n}\n\nfunction asObservation(value: unknown, what: string): Observation {\n const source = record(value, what);\n const file = source['file'];\n\n if (file !== undefined && file !== null && typeof file !== 'string') {\n throw new BadRequest(`${what}.file must be a string when present; received ${describe(file)}`);\n }\n\n return {\n project: string(source, 'project', what),\n subject: string(source, 'subject', what),\n component: string(source, 'component', what),\n band: asBand(source['band'], `${what}.band`),\n hash: string(source, 'hash', what) as Digest,\n profile: asProfile(source['profile'], what),\n commit: string(source, 'commit', what),\n run: string(source, 'run', what),\n at: instant(source, 'at', what),\n accepted: flag(source, 'accepted', what),\n ...(typeof file === 'string' && file !== '' ? { file } : {}),\n };\n}\n\nfunction asTokenValue(value: unknown, what: string): TokenValue {\n const source = record(value, what);\n return {\n project: string(source, 'project', what),\n token: string(source, 'token', what),\n value: string(source, 'value', what),\n commit: string(source, 'commit', what),\n at: instant(source, 'at', what),\n };\n}\n"]}
|
package/dist/worker.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { RasterStoreError } from '@variance-authority/raster';
|
|
2
|
+
import type { TribunalBindings } from './bindings.js';
|
|
3
|
+
/**
|
|
4
|
+
* One `fetch` handler, three surfaces, and no outbound call.
|
|
5
|
+
*
|
|
6
|
+
* The operator writes the Worker entry and hands this their bindings; everything
|
|
7
|
+
* below is the composition. Two of the three surfaces are protocols defined
|
|
8
|
+
* elsewhere and re-served here, so a CLI configured with
|
|
9
|
+
* `baselines.kind: "remote"` and a client built from
|
|
10
|
+
* `@variance-authority/history/client` both reach this deployment unchanged.
|
|
11
|
+
*
|
|
12
|
+
* ## The Worker never calls anything
|
|
13
|
+
*
|
|
14
|
+
* No status check, no pull-request comment, no webhook, no telemetry. The
|
|
15
|
+
* pipeline reports to the server and the server reports to nobody. That is the
|
|
16
|
+
* project's standing constraint, and it is also what keeps this deployment free
|
|
17
|
+
* of a credential for somebody else's system: there is nothing here to steal that
|
|
18
|
+
* is useful anywhere but here.
|
|
19
|
+
*
|
|
20
|
+
* ## Two tokens, and what each one is for
|
|
21
|
+
*
|
|
22
|
+
* `ingest` is written into CI configuration and can write builds, baselines and
|
|
23
|
+
* history. `review` belongs to people and can read the review surface and decide.
|
|
24
|
+
* They are separate because deciding *promotes a baseline*, and one secret doing
|
|
25
|
+
* both means anything that can read a CI log can approve a regression.
|
|
26
|
+
*
|
|
27
|
+
* They may not be equal, and construction refuses it. A deployment that set both
|
|
28
|
+
* to the same value would satisfy every check in this file while having exactly
|
|
29
|
+
* one secret, and nothing in a request would show it.
|
|
30
|
+
*
|
|
31
|
+
* ## Authentication happens before routing
|
|
32
|
+
*
|
|
33
|
+
* A caller holding neither token gets one sentence, whatever it asked for,
|
|
34
|
+
* including for paths that do not exist. The alternative — 404 for unknown paths,
|
|
35
|
+
* 401 for known ones — hands an unauthorised caller a map of the API, and on a
|
|
36
|
+
* subject-scoped path it answers "that subject exists" to somebody holding
|
|
37
|
+
* nothing.
|
|
38
|
+
*
|
|
39
|
+
* The *capability* check happens after routing, and deliberately: it can only be
|
|
40
|
+
* reached by someone already holding a valid token, so telling them which of the
|
|
41
|
+
* two a route wants reveals nothing they could not learn by trying.
|
|
42
|
+
*
|
|
43
|
+
* ## What is next door
|
|
44
|
+
*
|
|
45
|
+
* The token comparison and the capability rule are in
|
|
46
|
+
* [`worker-auth.ts`](./worker-auth.ts); the refusals and the readers every route
|
|
47
|
+
* validates in are in [`worker-http.ts`](./worker-http.ts) and
|
|
48
|
+
* [`worker-input.ts`](./worker-input.ts). What is left here is the composition and
|
|
49
|
+
* the routing table, which is the part an operator has to read.
|
|
50
|
+
*/
|
|
51
|
+
export interface TribunalOptions extends TribunalBindings {
|
|
52
|
+
/** Scopes every row and object. One deployment, several repositories. */
|
|
53
|
+
readonly project: string;
|
|
54
|
+
/** Written into CI. Writes builds, baselines and history. At least 16 characters. */
|
|
55
|
+
readonly ingestToken: string;
|
|
56
|
+
/** Held by people. Reads the review surface and decides. At least 16 characters. */
|
|
57
|
+
readonly reviewToken: string;
|
|
58
|
+
/**
|
|
59
|
+
* Days of builds to keep when `POST /review/sweep` is called.
|
|
60
|
+
*
|
|
61
|
+
* Applied on request rather than on a timer, because a Worker has no timer and
|
|
62
|
+
* this package will not invent a cron the operator did not ask for. Wire it to a
|
|
63
|
+
* scheduled trigger, or call it from a CI job, or never.
|
|
64
|
+
*/
|
|
65
|
+
readonly retentionDays?: number;
|
|
66
|
+
readonly now?: () => Date;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The whole service, as one `fetch` handler.
|
|
70
|
+
*
|
|
71
|
+
* Deliberately nothing else. A handler is what a Worker exports, what a Next
|
|
72
|
+
* route handler is, and what `node:http` can be adapted to in a dozen lines — so
|
|
73
|
+
* the three deployments differ in their wiring and not in their router. It is
|
|
74
|
+
* also the reason a `Tribunal` cannot be asked for its own tokens: a value with
|
|
75
|
+
* one method has nothing to leak when it is logged.
|
|
76
|
+
*/
|
|
77
|
+
export interface Tribunal {
|
|
78
|
+
fetch(request: Request): Promise<Response>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Build the service over a database, a bucket and two tokens.
|
|
82
|
+
*
|
|
83
|
+
* Refuses a token under 16 characters and refuses two identical ones, here
|
|
84
|
+
* rather than on the first request: a deployment whose ingest token also
|
|
85
|
+
* promotes baselines is a misconfiguration that would otherwise be discovered by
|
|
86
|
+
* the build log that used it.
|
|
87
|
+
*/
|
|
88
|
+
export declare function createTribunal(options: TribunalOptions): Tribunal;
|
|
89
|
+
/** Re-exported so a Worker entry can recognise a store failure without a second import. */
|
|
90
|
+
export { RasterStoreError };
|
|
91
|
+
//# sourceMappingURL=worker.d.ts.map
|