@variance-authority/tribunal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/CHANGELOG.md +100 -0
  2. package/LICENSE +21 -0
  3. package/README.md +506 -0
  4. package/dist/__fixtures__/review.d.ts +47 -0
  5. package/dist/__fixtures__/review.js +145 -0
  6. package/dist/__fixtures__/review.js.map +1 -0
  7. package/dist/bindings.d.ts +134 -0
  8. package/dist/bindings.js +88 -0
  9. package/dist/bindings.js.map +1 -0
  10. package/dist/changelog.d.ts +132 -0
  11. package/dist/changelog.js +129 -0
  12. package/dist/changelog.js.map +1 -0
  13. package/dist/history-queries.d.ts +82 -0
  14. package/dist/history-queries.js +200 -0
  15. package/dist/history-queries.js.map +1 -0
  16. package/dist/history-rows.d.ts +42 -0
  17. package/dist/history-rows.js +148 -0
  18. package/dist/history-rows.js.map +1 -0
  19. package/dist/history.d.ts +40 -0
  20. package/dist/history.js +186 -0
  21. package/dist/history.js.map +1 -0
  22. package/dist/index.d.ts +13 -0
  23. package/dist/index.js +12 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/migration-steps.d.ts +32 -0
  26. package/dist/migration-steps.js +303 -0
  27. package/dist/migration-steps.js.map +1 -0
  28. package/dist/migrations.d.ts +58 -0
  29. package/dist/migrations.js +283 -0
  30. package/dist/migrations.js.map +1 -0
  31. package/dist/next.d.ts +94 -0
  32. package/dist/next.js +44 -0
  33. package/dist/next.js.map +1 -0
  34. package/dist/node/bin.d.ts +120 -0
  35. package/dist/node/bin.js +337 -0
  36. package/dist/node/bin.js.map +1 -0
  37. package/dist/node/bucket.d.ts +58 -0
  38. package/dist/node/bucket.js +142 -0
  39. package/dist/node/bucket.js.map +1 -0
  40. package/dist/node/database.d.ts +33 -0
  41. package/dist/node/database.js +157 -0
  42. package/dist/node/database.js.map +1 -0
  43. package/dist/node/index.d.ts +43 -0
  44. package/dist/node/index.js +43 -0
  45. package/dist/node/index.js.map +1 -0
  46. package/dist/node/serve.d.ts +81 -0
  47. package/dist/node/serve.js +193 -0
  48. package/dist/node/serve.js.map +1 -0
  49. package/dist/node/ui-assets.d.ts +47 -0
  50. package/dist/node/ui-assets.js +60 -0
  51. package/dist/node/ui-assets.js.map +1 -0
  52. package/dist/review-ingest.d.ts +24 -0
  53. package/dist/review-ingest.js +184 -0
  54. package/dist/review-ingest.js.map +1 -0
  55. package/dist/review-read.d.ts +87 -0
  56. package/dist/review-read.js +361 -0
  57. package/dist/review-read.js.map +1 -0
  58. package/dist/review-rows.d.ts +30 -0
  59. package/dist/review-rows.js +67 -0
  60. package/dist/review-rows.js.map +1 -0
  61. package/dist/review-types.d.ts +414 -0
  62. package/dist/review-types.js +2 -0
  63. package/dist/review-types.js.map +1 -0
  64. package/dist/review-write.d.ts +35 -0
  65. package/dist/review-write.js +86 -0
  66. package/dist/review-write.js.map +1 -0
  67. package/dist/review.d.ts +53 -0
  68. package/dist/review.fixtures.d.ts +47 -0
  69. package/dist/review.fixtures.js +145 -0
  70. package/dist/review.fixtures.js.map +1 -0
  71. package/dist/review.js +245 -0
  72. package/dist/review.js.map +1 -0
  73. package/dist/schema.d.ts +36 -0
  74. package/dist/schema.js +39 -0
  75. package/dist/schema.js.map +1 -0
  76. package/dist/store.d.ts +58 -0
  77. package/dist/store.js +278 -0
  78. package/dist/store.js.map +1 -0
  79. package/dist/testing.d.ts +38 -0
  80. package/dist/testing.js +92 -0
  81. package/dist/testing.js.map +1 -0
  82. package/dist/ui/__probe.d.ts +3 -0
  83. package/dist/ui/__probe.js +5 -0
  84. package/dist/ui/__probe.js.map +1 -0
  85. package/dist/ui/attribution.d.ts +46 -0
  86. package/dist/ui/attribution.js +79 -0
  87. package/dist/ui/attribution.js.map +1 -0
  88. package/dist/ui/because.d.ts +51 -0
  89. package/dist/ui/because.js +52 -0
  90. package/dist/ui/because.js.map +1 -0
  91. package/dist/ui/browser-entry.d.ts +2 -0
  92. package/dist/ui/browser-entry.js +34 -0
  93. package/dist/ui/browser-entry.js.map +1 -0
  94. package/dist/ui/builds.d.ts +43 -0
  95. package/dist/ui/builds.js +79 -0
  96. package/dist/ui/builds.js.map +1 -0
  97. package/dist/ui/carried.d.ts +73 -0
  98. package/dist/ui/carried.js +63 -0
  99. package/dist/ui/carried.js.map +1 -0
  100. package/dist/ui/cause.d.ts +73 -0
  101. package/dist/ui/cause.js +133 -0
  102. package/dist/ui/cause.js.map +1 -0
  103. package/dist/ui/change-story.d.ts +153 -0
  104. package/dist/ui/change-story.js +253 -0
  105. package/dist/ui/change-story.js.map +1 -0
  106. package/dist/ui/change.d.ts +51 -0
  107. package/dist/ui/change.js +125 -0
  108. package/dist/ui/change.js.map +1 -0
  109. package/dist/ui/client.d.ts +74 -0
  110. package/dist/ui/client.js +88 -0
  111. package/dist/ui/client.js.map +1 -0
  112. package/dist/ui/consumers.d.ts +80 -0
  113. package/dist/ui/consumers.js +125 -0
  114. package/dist/ui/consumers.js.map +1 -0
  115. package/dist/ui/control.d.ts +63 -0
  116. package/dist/ui/control.js +39 -0
  117. package/dist/ui/control.js.map +1 -0
  118. package/dist/ui/crossing.d.ts +55 -0
  119. package/dist/ui/crossing.js +74 -0
  120. package/dist/ui/crossing.js.map +1 -0
  121. package/dist/ui/declarations.d.ts +36 -0
  122. package/dist/ui/declarations.js +73 -0
  123. package/dist/ui/declarations.js.map +1 -0
  124. package/dist/ui/distance.d.ts +154 -0
  125. package/dist/ui/distance.js +180 -0
  126. package/dist/ui/distance.js.map +1 -0
  127. package/dist/ui/divergence.d.ts +40 -0
  128. package/dist/ui/divergence.js +176 -0
  129. package/dist/ui/divergence.js.map +1 -0
  130. package/dist/ui/docket.d.ts +39 -0
  131. package/dist/ui/docket.js +172 -0
  132. package/dist/ui/docket.js.map +1 -0
  133. package/dist/ui/findings.d.ts +42 -0
  134. package/dist/ui/findings.js +110 -0
  135. package/dist/ui/findings.js.map +1 -0
  136. package/dist/ui/foreseen.d.ts +55 -0
  137. package/dist/ui/foreseen.js +91 -0
  138. package/dist/ui/foreseen.js.map +1 -0
  139. package/dist/ui/glance.d.ts +101 -0
  140. package/dist/ui/glance.js +101 -0
  141. package/dist/ui/glance.js.map +1 -0
  142. package/dist/ui/grouping.d.ts +158 -0
  143. package/dist/ui/grouping.js +187 -0
  144. package/dist/ui/grouping.js.map +1 -0
  145. package/dist/ui/handed.d.ts +60 -0
  146. package/dist/ui/handed.js +122 -0
  147. package/dist/ui/handed.js.map +1 -0
  148. package/dist/ui/history.d.ts +88 -0
  149. package/dist/ui/history.js +170 -0
  150. package/dist/ui/history.js.map +1 -0
  151. package/dist/ui/holding.d.ts +84 -0
  152. package/dist/ui/holding.js +90 -0
  153. package/dist/ui/holding.js.map +1 -0
  154. package/dist/ui/impact.d.ts +31 -0
  155. package/dist/ui/impact.js +132 -0
  156. package/dist/ui/impact.js.map +1 -0
  157. package/dist/ui/index.d.ts +21 -0
  158. package/dist/ui/index.js +21 -0
  159. package/dist/ui/index.js.map +1 -0
  160. package/dist/ui/lead.d.ts +35 -0
  161. package/dist/ui/lead.js +34 -0
  162. package/dist/ui/lead.js.map +1 -0
  163. package/dist/ui/look.d.ts +109 -0
  164. package/dist/ui/look.js +248 -0
  165. package/dist/ui/look.js.map +1 -0
  166. package/dist/ui/mark.d.ts +11 -0
  167. package/dist/ui/mark.js +13 -0
  168. package/dist/ui/mark.js.map +1 -0
  169. package/dist/ui/moved.d.ts +121 -0
  170. package/dist/ui/moved.js +143 -0
  171. package/dist/ui/moved.js.map +1 -0
  172. package/dist/ui/navigation.d.ts +26 -0
  173. package/dist/ui/navigation.js +53 -0
  174. package/dist/ui/navigation.js.map +1 -0
  175. package/dist/ui/order.d.ts +113 -0
  176. package/dist/ui/order.js +226 -0
  177. package/dist/ui/order.js.map +1 -0
  178. package/dist/ui/origins.d.ts +42 -0
  179. package/dist/ui/origins.js +149 -0
  180. package/dist/ui/origins.js.map +1 -0
  181. package/dist/ui/outcome.d.ts +93 -0
  182. package/dist/ui/outcome.js +162 -0
  183. package/dist/ui/outcome.js.map +1 -0
  184. package/dist/ui/parted.d.ts +68 -0
  185. package/dist/ui/parted.js +101 -0
  186. package/dist/ui/parted.js.map +1 -0
  187. package/dist/ui/rail.d.ts +31 -0
  188. package/dist/ui/rail.js +21 -0
  189. package/dist/ui/rail.js.map +1 -0
  190. package/dist/ui/reach.d.ts +66 -0
  191. package/dist/ui/reach.js +185 -0
  192. package/dist/ui/reach.js.map +1 -0
  193. package/dist/ui/regions.d.ts +82 -0
  194. package/dist/ui/regions.js +67 -0
  195. package/dist/ui/regions.js.map +1 -0
  196. package/dist/ui/review.bundle.js +69 -0
  197. package/dist/ui/review.d.ts +97 -0
  198. package/dist/ui/review.js +39 -0
  199. package/dist/ui/review.js.map +1 -0
  200. package/dist/ui/root.d.ts +72 -0
  201. package/dist/ui/root.js +102 -0
  202. package/dist/ui/root.js.map +1 -0
  203. package/dist/ui/route.d.ts +95 -0
  204. package/dist/ui/route.js +114 -0
  205. package/dist/ui/route.js.map +1 -0
  206. package/dist/ui/run.d.ts +26 -0
  207. package/dist/ui/run.js +12 -0
  208. package/dist/ui/run.js.map +1 -0
  209. package/dist/ui/sense.d.ts +187 -0
  210. package/dist/ui/sense.js +222 -0
  211. package/dist/ui/sense.js.map +1 -0
  212. package/dist/ui/settled.d.ts +43 -0
  213. package/dist/ui/settled.js +59 -0
  214. package/dist/ui/settled.js.map +1 -0
  215. package/dist/ui/shell.d.ts +76 -0
  216. package/dist/ui/shell.js +65 -0
  217. package/dist/ui/shell.js.map +1 -0
  218. package/dist/ui/shift.d.ts +52 -0
  219. package/dist/ui/shift.js +98 -0
  220. package/dist/ui/shift.js.map +1 -0
  221. package/dist/ui/styles-docket.d.ts +18 -0
  222. package/dist/ui/styles-docket.js +281 -0
  223. package/dist/ui/styles-docket.js.map +1 -0
  224. package/dist/ui/styles-stage.d.ts +18 -0
  225. package/dist/ui/styles-stage.js +92 -0
  226. package/dist/ui/styles-stage.js.map +1 -0
  227. package/dist/ui/styles.d.ts +12 -0
  228. package/dist/ui/styles.js +482 -0
  229. package/dist/ui/styles.js.map +1 -0
  230. package/dist/ui/subject.d.ts +36 -0
  231. package/dist/ui/subject.js +138 -0
  232. package/dist/ui/subject.js.map +1 -0
  233. package/dist/ui/text.d.ts +129 -0
  234. package/dist/ui/text.js +164 -0
  235. package/dist/ui/text.js.map +1 -0
  236. package/dist/ui/variations.d.ts +37 -0
  237. package/dist/ui/variations.js +60 -0
  238. package/dist/ui/variations.js.map +1 -0
  239. package/dist/ui/viewer.d.ts +67 -0
  240. package/dist/ui/viewer.js +246 -0
  241. package/dist/ui/viewer.js.map +1 -0
  242. package/dist/worker-auth.d.ts +48 -0
  243. package/dist/worker-auth.js +74 -0
  244. package/dist/worker-auth.js.map +1 -0
  245. package/dist/worker-entry.d.ts +58 -0
  246. package/dist/worker-entry.js +36 -0
  247. package/dist/worker-entry.js.map +1 -0
  248. package/dist/worker-http.d.ts +39 -0
  249. package/dist/worker-http.js +111 -0
  250. package/dist/worker-http.js.map +1 -0
  251. package/dist/worker-input.d.ts +81 -0
  252. package/dist/worker-input.js +296 -0
  253. package/dist/worker-input.js.map +1 -0
  254. package/dist/worker.d.ts +91 -0
  255. package/dist/worker.js +287 -0
  256. package/dist/worker.js.map +1 -0
  257. package/mark.svg +30 -0
  258. package/migrations/0001_initial.sql +193 -0
  259. package/migrations/0002_changelog.sql +30 -0
  260. package/migrations/0003_accessibility.sql +8 -0
  261. package/migrations/0004_variations.sql +19 -0
  262. package/migrations/0005_reach.sql +26 -0
  263. package/migrations/0006_baseline-size.sql +7 -0
  264. package/migrations/0007_declarations.sql +7 -0
  265. package/migrations/0008_green-because.sql +7 -0
  266. package/migrations/0009_moved-bands.sql +6 -0
  267. package/migrations/0010_composition.sql +15 -0
  268. package/migrations/0011_movements.sql +21 -0
  269. package/migrations/0012_control-size.sql +6 -0
  270. package/package.json +110 -0
  271. package/wrangler.jsonc +45 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-auth.js","sourceRoot":"","sources":["../src/worker-auth.ts"],"names":[],"mappings":"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","sourcesContent":["import { Forbidden } from './worker-http.js';\n\n/**\n * Which of the two secrets a request presented, whether the deployment has two of\n * them at all, and whether the one presented may do what was asked.\n *\n * Apart from the router because none of it is about routes. Read together it is\n * the whole authentication story — the check that runs before anything is parsed,\n * the comparison that must not leak, the length below which a shared secret is a\n * public one, and the capability rule that runs *after* a route is chosen. The\n * argument for why there are two tokens rather than one lives in\n * [`worker.ts`](./worker.ts), where an operator configuring a deployment meets it\n * first.\n */\n\n/** Which of the two secrets a request presented, or nothing at all. */\nexport type Granted = 'ingest' | 'review';\n\n/** The shortest token this will start with. A short shared secret is a public one. */\nconst MIN_TOKEN = 16;\n\n/**\n * The one sentence a caller holding neither token ever gets.\n *\n * Identical for a missing token, a wrong token, and a path that does not exist.\n * Any variation between those three is an oracle.\n */\nexport const UNAUTHENTICATED = 'a valid bearer token is required';\n\n/**\n * The two secrets, and nothing else this file has any business reading.\n *\n * Structural rather than `TribunalOptions` on purpose: authentication cannot be\n * made to depend on a project name or a retention window by a later edit, because\n * it cannot see them.\n */\ninterface Secrets {\n readonly ingestToken: string;\n readonly reviewToken: string;\n}\n\n/**\n * Which token was presented, compared in constant time.\n *\n * Both are hashed to a fixed 32 bytes before comparison and the comparison never\n * exits early. `===` on strings leaks the length of the common prefix through\n * timing; comparing raw strings of different lengths leaks the token's length. A\n * digest makes every comparison the same shape whatever arrives.\n *\n * Both candidates are always checked, even after the first one matches, so that\n * \"which token is this\" costs the same either way.\n */\nexport async function grant(request: Request, secrets: Secrets): Promise<Granted | null> {\n const header = request.headers.get('authorization');\n if (header === null) return null;\n\n const match = /^Bearer (.+)$/i.exec(header.trim());\n const presented = match?.[1];\n if (presented === undefined) return null;\n\n const digest = await fingerprint(presented);\n const isIngest = equal(digest, await fingerprint(secrets.ingestToken));\n const isReview = equal(digest, await fingerprint(secrets.reviewToken));\n\n return isIngest ? 'ingest' : isReview ? 'review' : null;\n}\n\nasync function fingerprint(token: string): Promise<Uint8Array> {\n return new Uint8Array(await crypto.subtle.digest('SHA-256', new TextEncoder().encode(token)));\n}\n\nfunction equal(left: Uint8Array, right: Uint8Array): boolean {\n if (left.length !== right.length) return false;\n let difference = 0;\n for (let index = 0; index < left.length; index += 1) {\n difference |= (left[index] ?? 0) ^ (right[index] ?? 0);\n }\n return difference === 0;\n}\n\nexport function refuseWeakTokens(secrets: Secrets): void {\n for (const [name, token] of [\n ['ingestToken', secrets.ingestToken],\n ['reviewToken', secrets.reviewToken],\n ] as const) {\n if (token.trim().length < MIN_TOKEN) {\n throw new Error(\n `\\`${name}\\` is shorter than ${MIN_TOKEN} characters. This deployment holds every ` +\n 'baseline and every observation a project has produced, and it can promote one; a ' +\n 'guessable shared secret in front of that is not a configuration mistake anybody ' +\n 'notices until it matters',\n );\n }\n }\n\n if (secrets.ingestToken === secrets.reviewToken) {\n throw new Error(\n 'the ingest token and the review token are the same value, so this deployment has one ' +\n 'secret and not two. The separation is the whole point: the ingest token lives in CI ' +\n 'configuration, and approving promotes a baseline — anything that can read a build log ' +\n 'would be able to approve a regression',\n );\n }\n}\n\nexport function requires(granted: Granted, needed: Granted, path: string): void {\n if (granted === needed) return;\n throw new Forbidden(\n `${path} is served to the ${needed} token and this request presented the ${granted} one. ` +\n (needed === 'review'\n ? 'Deciding promotes a baseline, so it is not something a build log can do'\n : 'Writing to this deployment is something CI does, not something a reviewer does'),\n );\n}\n"]}
@@ -0,0 +1,58 @@
1
+ import type { D1Like, R2Like } from './bindings.js';
2
+ /**
3
+ * The deployable Worker: the twenty lines that were missing between a package and
4
+ * a running service.
5
+ *
6
+ * Everything below this file is platform-free by design — `worker.ts` composes
7
+ * routes over a `D1Like` and an `R2Like` and names no Cloudflare type. That is
8
+ * why the package is testable without a Workers runtime, and it is also why it
9
+ * had **no entry point at all**: `createTribunal` returns an object with a
10
+ * `fetch` method, and a Worker needs a module whose default export has one. The
11
+ * two are one adapter apart, and until now the adapter did not exist, so the
12
+ * surface shipped and could not be deployed.
13
+ *
14
+ * This file is the only one in the package that reads an environment.
15
+ *
16
+ * ## Why the instance is memoised
17
+ *
18
+ * `createTribunal` validates the secrets and builds the routing table. An isolate
19
+ * serves many requests, and rebuilding per request would re-run the token checks
20
+ * on every call — which is not a correctness problem and is a pointless one. The
21
+ * cache is keyed on the `env` object identity rather than held unconditionally,
22
+ * so a different environment cannot be served by an instance built from another.
23
+ *
24
+ * ## Why a bad environment answers 500 rather than throwing at import
25
+ *
26
+ * A Worker that throws while constructing its module fails deployment-wide with a
27
+ * platform error the operator has to go and find. Every refusal in this package
28
+ * is a sentence, and the one an operator most needs — *your token is too short*,
29
+ * *your two tokens are the same* — is worth more than an exception trace. So the
30
+ * construction happens inside `fetch` and its message is the body.
31
+ */
32
+ export interface TribunalEnv {
33
+ /** D1 binding. Provisioned by `wrangler.jsonc`; the schema is in `migrations/`. */
34
+ readonly DB: D1Like;
35
+ /** R2 binding. Holds baseline and candidate bytes; never a row. */
36
+ readonly BUCKET: R2Like;
37
+ /** Written into CI. Writes builds, baselines and history. 16 characters or more. */
38
+ readonly INGEST_TOKEN: string;
39
+ /** Held by people. Reads the review surface and decides. 16 characters or more. */
40
+ readonly REVIEW_TOKEN: string;
41
+ /**
42
+ * Scopes every row and object.
43
+ *
44
+ * A deployment setting today, which is exactly what
45
+ * [spec 0014](../../../docs/specs/0014-hosted-who-the-caller-is-and-what-the-bill-counts.md)
46
+ * says has to change before a second tenant exists: the credential should
47
+ * establish the project, and no route should accept one. Harmless while a
48
+ * deployment serves one project, and the whole of the problem at two.
49
+ */
50
+ readonly PROJECT?: string;
51
+ /** Days of builds `POST /review/sweep` keeps. Absent means the package default. */
52
+ readonly RETENTION_DAYS?: string;
53
+ }
54
+ declare const _default: {
55
+ fetch(request: Request, env: TribunalEnv): Promise<Response>;
56
+ };
57
+ export default _default;
58
+ //# sourceMappingURL=worker-entry.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { createTribunal } from './worker.js';
2
+ const built = new WeakMap();
3
+ function tribunalFor(env) {
4
+ const found = built.get(env);
5
+ if (found !== undefined)
6
+ return found;
7
+ const retentionDays = Number(env.RETENTION_DAYS);
8
+ const made = createTribunal({
9
+ db: env.DB,
10
+ bucket: env.BUCKET,
11
+ project: env.PROJECT ?? 'default',
12
+ ingestToken: env.INGEST_TOKEN ?? '',
13
+ reviewToken: env.REVIEW_TOKEN ?? '',
14
+ // `Number(undefined)` is NaN and `Number('')` is 0, and a retention of zero
15
+ // days would sweep every build the first time anyone asked. Both fall back.
16
+ ...(Number.isFinite(retentionDays) && retentionDays > 0 ? { retentionDays } : {}),
17
+ });
18
+ built.set(env, made);
19
+ return made;
20
+ }
21
+ export default {
22
+ async fetch(request, env) {
23
+ try {
24
+ return await tribunalFor(env).fetch(request);
25
+ }
26
+ catch (error) {
27
+ // Only construction failures reach here — `Tribunal.fetch` answers with a
28
+ // status rather than throwing. The message is the operator's, and it names
29
+ // the setting rather than the stack.
30
+ return new Response(JSON.stringify({
31
+ error: error instanceof Error ? error.message : 'this deployment is misconfigured',
32
+ }), { status: 500, headers: { 'content-type': 'application/json' } });
33
+ }
34
+ },
35
+ };
36
+ //# sourceMappingURL=worker-entry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-entry.js","sourceRoot":"","sources":["../src/worker-entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAyD5D,MAAM,KAAK,GAAG,IAAI,OAAO,EAAoB,CAAC;AAE9C,SAAS,WAAW,CAAC,GAAgB;IACnC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAEtC,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,cAAc,CAAC;QAC1B,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,SAAS;QACjC,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;QACnC,WAAW,EAAE,GAAG,CAAC,YAAY,IAAI,EAAE;QACnC,4EAA4E;QAC5E,4EAA4E;QAC5E,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,IAAI,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClF,CAAC,CAAC;IAEH,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACrB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,eAAe;IACb,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,GAAgB;QAC5C,IAAI,CAAC;YACH,OAAO,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,0EAA0E;YAC1E,2EAA2E;YAC3E,qCAAqC;YACrC,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,kCAAkC;aACnF,CAAC,EACF,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,EAAE,CACjE,CAAC;QACJ,CAAC;IACH,CAAC;CACF,CAAC","sourcesContent":["import { createTribunal, type Tribunal } from './worker.js';\nimport type { D1Like, R2Like } from './bindings.js';\n\n/**\n * The deployable Worker: the twenty lines that were missing between a package and\n * a running service.\n *\n * Everything below this file is platform-free by design — `worker.ts` composes\n * routes over a `D1Like` and an `R2Like` and names no Cloudflare type. That is\n * why the package is testable without a Workers runtime, and it is also why it\n * had **no entry point at all**: `createTribunal` returns an object with a\n * `fetch` method, and a Worker needs a module whose default export has one. The\n * two are one adapter apart, and until now the adapter did not exist, so the\n * surface shipped and could not be deployed.\n *\n * This file is the only one in the package that reads an environment.\n *\n * ## Why the instance is memoised\n *\n * `createTribunal` validates the secrets and builds the routing table. An isolate\n * serves many requests, and rebuilding per request would re-run the token checks\n * on every call — which is not a correctness problem and is a pointless one. The\n * cache is keyed on the `env` object identity rather than held unconditionally,\n * so a different environment cannot be served by an instance built from another.\n *\n * ## Why a bad environment answers 500 rather than throwing at import\n *\n * A Worker that throws while constructing its module fails deployment-wide with a\n * platform error the operator has to go and find. Every refusal in this package\n * is a sentence, and the one an operator most needs — *your token is too short*,\n * *your two tokens are the same* — is worth more than an exception trace. So the\n * construction happens inside `fetch` and its message is the body.\n */\n\nexport interface TribunalEnv {\n /** D1 binding. Provisioned by `wrangler.jsonc`; the schema is in `migrations/`. */\n readonly DB: D1Like;\n /** R2 binding. Holds baseline and candidate bytes; never a row. */\n readonly BUCKET: R2Like;\n /** Written into CI. Writes builds, baselines and history. 16 characters or more. */\n readonly INGEST_TOKEN: string;\n /** Held by people. Reads the review surface and decides. 16 characters or more. */\n readonly REVIEW_TOKEN: string;\n /**\n * Scopes every row and object.\n *\n * A deployment setting today, which is exactly what\n * [spec 0014](../../../docs/specs/0014-hosted-who-the-caller-is-and-what-the-bill-counts.md)\n * says has to change before a second tenant exists: the credential should\n * establish the project, and no route should accept one. Harmless while a\n * deployment serves one project, and the whole of the problem at two.\n */\n readonly PROJECT?: string;\n /** Days of builds `POST /review/sweep` keeps. Absent means the package default. */\n readonly RETENTION_DAYS?: string;\n}\n\nconst built = new WeakMap<object, Tribunal>();\n\nfunction tribunalFor(env: TribunalEnv): Tribunal {\n const found = built.get(env);\n if (found !== undefined) return found;\n\n const retentionDays = Number(env.RETENTION_DAYS);\n const made = createTribunal({\n db: env.DB,\n bucket: env.BUCKET,\n project: env.PROJECT ?? 'default',\n ingestToken: env.INGEST_TOKEN ?? '',\n reviewToken: env.REVIEW_TOKEN ?? '',\n // `Number(undefined)` is NaN and `Number('')` is 0, and a retention of zero\n // days would sweep every build the first time anyone asked. Both fall back.\n ...(Number.isFinite(retentionDays) && retentionDays > 0 ? { retentionDays } : {}),\n });\n\n built.set(env, made);\n return made;\n}\n\nexport default {\n async fetch(request: Request, env: TribunalEnv): Promise<Response> {\n try {\n return await tribunalFor(env).fetch(request);\n } catch (error) {\n // Only construction failures reach here — `Tribunal.fetch` answers with a\n // status rather than throwing. The message is the operator's, and it names\n // the setting rather than the stack.\n return new Response(\n JSON.stringify({\n error: error instanceof Error ? error.message : 'this deployment is misconfigured',\n }),\n { status: 500, headers: { 'content-type': 'application/json' } },\n );\n }\n },\n};\n"]}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * What a request is refused with, how one is read, and how an answer is written.
3
+ *
4
+ * Apart from the routing so that a route reads as a route. The three classes here
5
+ * *are* the status codes — [`worker.ts`](./worker.ts) is the only place they
6
+ * become numbers, and the only place that decides whether a failure was the
7
+ * caller's or the platform's — and the readers below are the vocabulary every
8
+ * route validates in.
9
+ *
10
+ * Each reader refuses rather than defaults, which is not politeness about input.
11
+ * Everything this deployment accepts ends up in D1 or R2, and a row that got in
12
+ * stays in: a field that looked close enough becomes a stored fact that every
13
+ * later query answers confidently and wrongly.
14
+ */
15
+ export declare class BadRequest extends Error {
16
+ readonly name = "BadRequest";
17
+ }
18
+ /** A valid token that is not the one this route wants. Never a 401 and never a 404. */
19
+ export declare class Forbidden extends Error {
20
+ readonly name = "Forbidden";
21
+ }
22
+ export declare class MethodNotAllowed extends Error {
23
+ readonly allow: string;
24
+ readonly name = "MethodNotAllowed";
25
+ constructor(message: string, allow: string);
26
+ }
27
+ export declare function requireMethod(request: Request, method: string): void;
28
+ export declare function json(status: number, body: unknown, headers?: Record<string, string>): Response;
29
+ export declare function asRecordBody(request: Request): Promise<Readonly<Record<string, unknown>>>;
30
+ export declare function record(value: unknown, what: string): Readonly<Record<string, unknown>>;
31
+ export declare function string(source: Readonly<Record<string, unknown>>, key: string, what: string): string;
32
+ export declare function required(url: URL, name: string): string;
33
+ export declare function optional(url: URL, name: string): string | undefined;
34
+ export declare function count(value: string): number;
35
+ export declare function array(value: unknown, what: string): readonly unknown[];
36
+ export declare function instant(source: Readonly<Record<string, unknown>>, key: string, what: string): string;
37
+ export declare function flag(source: Readonly<Record<string, unknown>>, key: string, what: string): boolean;
38
+ export declare function describe(value: unknown): string;
39
+ //# sourceMappingURL=worker-http.d.ts.map
@@ -0,0 +1,111 @@
1
+ /**
2
+ * What a request is refused with, how one is read, and how an answer is written.
3
+ *
4
+ * Apart from the routing so that a route reads as a route. The three classes here
5
+ * *are* the status codes — [`worker.ts`](./worker.ts) is the only place they
6
+ * become numbers, and the only place that decides whether a failure was the
7
+ * caller's or the platform's — and the readers below are the vocabulary every
8
+ * route validates in.
9
+ *
10
+ * Each reader refuses rather than defaults, which is not politeness about input.
11
+ * Everything this deployment accepts ends up in D1 or R2, and a row that got in
12
+ * stays in: a field that looked close enough becomes a stored fact that every
13
+ * later query answers confidently and wrongly.
14
+ */
15
+ export class BadRequest extends Error {
16
+ name = 'BadRequest';
17
+ }
18
+ /** A valid token that is not the one this route wants. Never a 401 and never a 404. */
19
+ export class Forbidden extends Error {
20
+ name = 'Forbidden';
21
+ }
22
+ export class MethodNotAllowed extends Error {
23
+ allow;
24
+ name = 'MethodNotAllowed';
25
+ constructor(message, allow) {
26
+ super(message);
27
+ this.allow = allow;
28
+ }
29
+ }
30
+ export function requireMethod(request, method) {
31
+ if (request.method === method)
32
+ return;
33
+ throw new MethodNotAllowed(`this path is answered over ${method}, not ${request.method}`, method);
34
+ }
35
+ export function json(status, body, headers = {}) {
36
+ return new Response(JSON.stringify(body), {
37
+ status,
38
+ headers: { 'content-type': 'application/json; charset=utf-8', ...headers },
39
+ });
40
+ }
41
+ export async function asRecordBody(request) {
42
+ let parsed;
43
+ try {
44
+ parsed = await request.json();
45
+ }
46
+ catch (error) {
47
+ throw new BadRequest(`the request body is not JSON: ${error instanceof Error ? error.message : String(error)}`);
48
+ }
49
+ return record(parsed, 'the request body');
50
+ }
51
+ export function record(value, what) {
52
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
53
+ throw new BadRequest(`${what} must be an object; received ${describe(value)}`);
54
+ }
55
+ return value;
56
+ }
57
+ export function string(source, key, what) {
58
+ const value = source[key];
59
+ if (typeof value !== 'string' || value === '') {
60
+ throw new BadRequest(`${what}.${key} must be a non-empty string; received ${describe(value)}`);
61
+ }
62
+ return value;
63
+ }
64
+ export function required(url, name) {
65
+ const value = url.searchParams.get(name);
66
+ if (value === null || value === '') {
67
+ throw new BadRequest(`\`${name}\` is required on ${url.pathname}`);
68
+ }
69
+ return value;
70
+ }
71
+ export function optional(url, name) {
72
+ const value = url.searchParams.get(name);
73
+ return value === null || value === '' ? undefined : value;
74
+ }
75
+ export function count(value) {
76
+ const parsed = Number(value);
77
+ if (!Number.isInteger(parsed) || parsed < 0) {
78
+ throw new BadRequest(`a count must be a whole number of at least 0; received "${value}"`);
79
+ }
80
+ return parsed;
81
+ }
82
+ export function array(value, what) {
83
+ if (!Array.isArray(value)) {
84
+ throw new BadRequest(`${what} must be an array; received ${describe(value)}`);
85
+ }
86
+ return value;
87
+ }
88
+ export function instant(source, key, what) {
89
+ const value = string(source, key, what);
90
+ if (Number.isNaN(Date.parse(value))) {
91
+ throw new BadRequest(`${what}.${key} must be an ISO-8601 instant; received "${value}". A row whose time cannot ` +
92
+ 'be parsed orders a journey wrongly, and a journey read backwards is a confident sentence ' +
93
+ 'that is exactly reversed');
94
+ }
95
+ return value;
96
+ }
97
+ export function flag(source, key, what) {
98
+ const value = source[key];
99
+ if (typeof value !== 'boolean') {
100
+ throw new BadRequest(`${what}.${key} must be a boolean; received ${describe(value)}`);
101
+ }
102
+ return value;
103
+ }
104
+ export function describe(value) {
105
+ if (value === undefined)
106
+ return 'nothing';
107
+ if (value === null)
108
+ return 'null';
109
+ return `${typeof value} (${String(value).slice(0, 60)})`;
110
+ }
111
+ //# sourceMappingURL=worker-http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"worker-http.js","sourceRoot":"","sources":["../src/worker-http.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a request is refused with, how one is read, and how an answer is written.\n *\n * Apart from the routing so that a route reads as a route. The three classes here\n * *are* the status codes — [`worker.ts`](./worker.ts) is the only place they\n * become numbers, and the only place that decides whether a failure was the\n * caller's or the platform's — and the readers below are the vocabulary every\n * route validates in.\n *\n * Each reader refuses rather than defaults, which is not politeness about input.\n * Everything this deployment accepts ends up in D1 or R2, and a row that got in\n * stays in: a field that looked close enough becomes a stored fact that every\n * later query answers confidently and wrongly.\n */\n\nexport class BadRequest extends Error {\n override readonly name = 'BadRequest';\n}\n\n/** A valid token that is not the one this route wants. Never a 401 and never a 404. */\nexport class Forbidden extends Error {\n override readonly name = 'Forbidden';\n}\n\nexport class MethodNotAllowed extends Error {\n override readonly name = 'MethodNotAllowed';\n constructor(\n message: string,\n readonly allow: string,\n ) {\n super(message);\n }\n}\n\nexport function requireMethod(request: Request, method: string): void {\n if (request.method === method) return;\n throw new MethodNotAllowed(\n `this path is answered over ${method}, not ${request.method}`,\n method,\n );\n}\n\nexport function json(status: number, body: unknown, headers: Record<string, string> = {}): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { 'content-type': 'application/json; charset=utf-8', ...headers },\n });\n}\n\nexport async function asRecordBody(request: Request): Promise<Readonly<Record<string, unknown>>> {\n let parsed: unknown;\n try {\n parsed = await request.json();\n } catch (error) {\n throw new BadRequest(\n `the request body is not JSON: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n return record(parsed, 'the request body');\n}\n\nexport function record(value: unknown, what: string): Readonly<Record<string, unknown>> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n throw new BadRequest(`${what} must be an object; received ${describe(value)}`);\n }\n return value as Readonly<Record<string, unknown>>;\n}\n\nexport function string(\n source: Readonly<Record<string, unknown>>,\n key: string,\n what: string,\n): string {\n const value = source[key];\n if (typeof value !== 'string' || value === '') {\n throw new BadRequest(`${what}.${key} must be a non-empty string; received ${describe(value)}`);\n }\n return value;\n}\n\nexport function required(url: URL, name: string): string {\n const value = url.searchParams.get(name);\n if (value === null || value === '') {\n throw new BadRequest(`\\`${name}\\` is required on ${url.pathname}`);\n }\n return value;\n}\n\nexport function optional(url: URL, name: string): string | undefined {\n const value = url.searchParams.get(name);\n return value === null || value === '' ? undefined : value;\n}\n\nexport function count(value: string): number {\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < 0) {\n throw new BadRequest(`a count must be a whole number of at least 0; received \"${value}\"`);\n }\n return parsed;\n}\n\nexport function array(value: unknown, what: string): readonly unknown[] {\n if (!Array.isArray(value)) {\n throw new BadRequest(`${what} must be an array; received ${describe(value)}`);\n }\n return value as readonly unknown[];\n}\n\nexport function instant(\n source: Readonly<Record<string, unknown>>,\n key: string,\n what: string,\n): string {\n const value = string(source, key, what);\n if (Number.isNaN(Date.parse(value))) {\n throw new BadRequest(\n `${what}.${key} must be an ISO-8601 instant; received \"${value}\". A row whose time cannot ` +\n 'be parsed orders a journey wrongly, and a journey read backwards is a confident sentence ' +\n 'that is exactly reversed',\n );\n }\n return value;\n}\n\nexport function flag(\n source: Readonly<Record<string, unknown>>,\n key: string,\n what: string,\n): boolean {\n const value = source[key];\n if (typeof value !== 'boolean') {\n throw new BadRequest(`${what}.${key} must be a boolean; received ${describe(value)}`);\n }\n return value;\n}\n\nexport function describe(value: unknown): string {\n if (value === undefined) return 'nothing';\n if (value === null) return 'null';\n return `${typeof value} (${String(value).slice(0, 60)})`;\n}\n"]}
@@ -0,0 +1,81 @@
1
+ import { type Approval, type Band, type Instability, type Observation, type RunRecord, type TokenValue, type Window } from '@variance-authority/history';
2
+ import { identityFrom } from '@variance-authority/raster';
3
+ import type { BuildIngest, Decision } from './review-types.js';
4
+ /**
5
+ * Every request body and query string this deployment accepts, checked before any
6
+ * of it reaches a store.
7
+ *
8
+ * One file because these share a deadline rather than a subject. The baseline
9
+ * routes, the history routes and the build ingest all write to something that
10
+ * does not forget, and each function below is the last moment its route can still
11
+ * refuse — the history record is append-only, a promoted baseline is what the
12
+ * next run compares against, and a build row outlives the request that made it.
13
+ * What each one checks differs, and the argument for it sits on the function.
14
+ *
15
+ * Reading a *response* is a different job with a different consequence and is not
16
+ * here: `identityFrom` and `rasterFrom` come from `@variance-authority/raster`
17
+ * because a misread response becomes a verdict, while everything below becomes a
18
+ * 4xx nobody mistakes for an answer.
19
+ */
20
+ export declare function asKey(value: unknown): {
21
+ readonly subject: string;
22
+ readonly label?: string;
23
+ };
24
+ export declare function asIdentity(value: unknown): ReturnType<typeof identityFrom> & object;
25
+ export declare function asDecision(value: unknown): Decision;
26
+ /**
27
+ * A build, checked before any of it is stored.
28
+ *
29
+ * The report itself is handed to `review.ingest` as-is rather than rebuilt field
30
+ * by field, and that is a deliberate asymmetry with the baseline routes. A
31
+ * misread *response* becomes a verdict; a misread *request* becomes a 4xx nobody
32
+ * mistakes for an answer. What is checked here is what the store would otherwise
33
+ * crash on or silently mis-record — the version, and the shape of the wrapper.
34
+ */
35
+ export declare function asBuildIngest(request: Request): Promise<BuildIngest>;
36
+ /**
37
+ * The window, validated before it can quietly select nothing.
38
+ *
39
+ * An unparseable `since` would reach the query as `NaN`, bind as NULL, match no
40
+ * row, and answer a churn of zero over zero runs — indistinguishable from a
41
+ * component that has never changed. A limit of zero is refused for the same
42
+ * reason: it asks for an answer computed over nothing, dressed as an answer.
43
+ */
44
+ export declare function windowOf(url: URL): Window;
45
+ /**
46
+ * A write, validated completely before any of it is appended.
47
+ *
48
+ * The history store is append-only, so this is the last moment anything can be
49
+ * refused. A row with an unknown band or an unparseable instant that gets in
50
+ * stays in, and every later query over that window either throws or silently
51
+ * misorders — so the strictness here is not politeness about input, it is the
52
+ * only place the invariant can still be enforced.
53
+ */
54
+ export declare function asRecordRequest(request: Request): Promise<{
55
+ readonly run: RunRecord;
56
+ readonly observations: readonly Observation[];
57
+ readonly tokens: readonly TokenValue[];
58
+ readonly instabilities: readonly Instability[];
59
+ }>;
60
+ /**
61
+ * An acceptance, and who made it when the surface that recorded it knows.
62
+ *
63
+ * The route this parses is how churn learns that anything shipped. Every
64
+ * observation is written unapproved — a run has not been reviewed at the moment
65
+ * it writes — so a deployment that could not take approvals answers *this
66
+ * component has never changed* about a component that changed forty times.
67
+ */
68
+ export declare function asApprovals(request: Request): Promise<readonly Approval[]>;
69
+ /**
70
+ * The subject list a run asks about before it writes.
71
+ *
72
+ * The only validation here that refuses a request for being *large* rather than
73
+ * malformed, and the reason is that the answer cannot be trimmed: a `previous`
74
+ * set with a hole in it is a change that did not happen, appended to a store that
75
+ * does not forget. So a caller naming more than one request may carry is told the
76
+ * cap rather than answered with the first {@link MAX_CURRENT_SUBJECTS} and left
77
+ * looking successful.
78
+ */
79
+ export declare function asCurrentRequest(request: Request): Promise<readonly string[]>;
80
+ export declare function asBand(value: unknown, what: string): Band;
81
+ //# sourceMappingURL=worker-input.d.ts.map