@crvouga/atlas 1.12.0 → 1.13.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 +7 -1
- package/dist/critics/src/contract.d.ts +113 -0
- package/dist/critics/src/contract.js +48 -0
- package/dist/critics/src/credentials.d.ts +72 -0
- package/dist/critics/src/credentials.js +136 -0
- package/dist/critics/src/critique.d.ts +128 -0
- package/dist/critics/src/critique.js +163 -0
- package/dist/critics/src/index.d.ts +21 -0
- package/dist/critics/src/index.js +21 -0
- package/dist/critics/src/judging.d.ts +153 -0
- package/dist/critics/src/judging.js +145 -0
- package/dist/critics/src/publish.d.ts +23 -0
- package/dist/critics/src/publish.js +21 -0
- package/dist/critics/src/runner.d.ts +104 -0
- package/dist/critics/src/runner.js +202 -0
- package/dist/critics/src/scope.d.ts +8 -0
- package/dist/critics/src/scope.js +26 -0
- package/dist/reports/src/compose.d.ts +2 -2
- package/dist/reports/src/compose.js +6 -5
- package/dist/reports/src/evidence-view.d.ts +2 -1
- package/dist/reports/src/evidence-view.js +5 -2
- package/dist/reports/src/layer-merge.d.ts +11 -3
- package/dist/reports/src/layer-merge.js +14 -3
- package/dist/reports/src/report-status.d.ts +1 -1
- package/dist/reports/src/report-status.js +6 -4
- package/dist/schema/src/api.d.ts +1 -0
- package/dist/schema/src/api.js +1 -1
- package/dist/schema/src/critique.d.ts +144 -0
- package/dist/schema/src/critique.js +96 -0
- package/dist/schema/src/index.d.ts +2 -1
- package/dist/schema/src/index.js +2 -1
- package/dist/schema/src/server-config.d.ts +7 -1
- package/dist/schema/src/server-config.js +6 -3
- package/dist/server/src/auth.d.ts +1 -1
- package/dist/server/src/auth.js +2 -2
- package/dist/server/src/config.js +2 -2
- package/dist/server/src/openapi.d.ts +3 -3
- package/dist/server/src/openapi.js +7 -4
- package/dist/server/src/store-api.js +39 -1
- package/docs/agents.md +1 -0
- package/docs/critics.md +190 -0
- package/docs/extending.md +1 -0
- package/docs/reference/critics.md +840 -0
- package/docs/reference/http-api.md +6 -2
- package/docs/reference/reports.md +20 -7
- package/docs/reference/schema.md +119 -2
- package/docs/reference/server.md +3 -3
- package/docs/report-contract.md +4 -0
- package/docs/server.md +6 -4
- package/llms-full.txt +211 -6
- package/llms.txt +2 -0
- package/package.json +7 -2
- package/packages/critics/README.md +18 -0
- package/packages/schema/schemas/critique.schema.json +254 -0
- package/packages/schema/schemas/server-config.schema.json +2 -1
- package/packages/schema/schemas/server-info.schema.json +1 -0
- package/packages/server/openapi.json +7 -6
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
# Reference: critics
|
|
2
|
+
|
|
3
|
+
Automated reviewers of the UI catalog: the critic contract, the runner (caching, retries, rate limits, budgets), credential references and redaction, the `atlas.critique` evidence as layers, calibration and report, warn and gate modes. Import path `@crvouga/atlas/critics`.
|
|
4
|
+
|
|
5
|
+
> Generated from the doc comments of `packages/critics/src` by `bun run docs`. Do not edit: change the comment next to the code.
|
|
6
|
+
|
|
7
|
+
### The critic contract
|
|
8
|
+
|
|
9
|
+
Source: `packages/critics/src/contract.ts`
|
|
10
|
+
|
|
11
|
+
A critic is an automated reviewer of the UI catalog: it is handed one captured UI (an image and what is known about
|
|
12
|
+
it) and answers with a judgement. Model-based, LLM-based and classic ML or heuristic critics are the same thing
|
|
13
|
+
here, with no kind privileged: the contract is `describe`, optionally `setup`, and `evaluate` of one image. The
|
|
14
|
+
runner (`runCritic`) owns everything else: concurrency, retries, rate limits, budgets, caching, publishing and
|
|
15
|
+
progress. A critic never talks to Atlas, never publishes, and never sees a credential it was not configured with.
|
|
16
|
+
|
|
17
|
+
Critics are producers that publish layers. Atlas never executes one inside the server.
|
|
18
|
+
|
|
19
|
+
See docs/critics.md
|
|
20
|
+
|
|
21
|
+
#### `CatalogEntry`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
export type CatalogEntry = {
|
|
25
|
+
/** The entry's stable identity in the catalog. */
|
|
26
|
+
readonly key: string;
|
|
27
|
+
/** What a person calls it. */
|
|
28
|
+
readonly title: string;
|
|
29
|
+
/** The route or screen it shows, when known. */
|
|
30
|
+
readonly route?: string;
|
|
31
|
+
/** The kind of UI state: `screen`, `overlay`, `loading`, `error`, `empty`, or anything else the catalog declares. */
|
|
32
|
+
readonly state: string;
|
|
33
|
+
/** The device or browser profile it was captured with, in whatever shape the catalog records it. */
|
|
34
|
+
readonly profile?: unknown;
|
|
35
|
+
};
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
One captured UI state of the catalog, as a critic is told about it.
|
|
39
|
+
|
|
40
|
+
#### `JudgeableEntry`
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
export type JudgeableEntry = CatalogEntry & {
|
|
44
|
+
/** SHA-256 of the exact image bytes, lowercase hex. A critique is anchored to it, and an identical image is never judged twice. */
|
|
45
|
+
readonly imageSha256: string;
|
|
46
|
+
};
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
An entry to judge together with the hash of the image bytes that were stored for it.
|
|
50
|
+
|
|
51
|
+
#### `CriticDescription`
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
export type CriticDescription = {
|
|
55
|
+
/** Where images are sent: a host or provider name, or `null` when they never leave the machine. A run that sends images to a third party needs explicit confirmation. */
|
|
56
|
+
readonly sendsImagesTo: string | null;
|
|
57
|
+
/** The model that judges, when there is one. Part of what makes two critiques the same. */
|
|
58
|
+
readonly model?: { readonly id: string; readonly provider?: string };
|
|
59
|
+
/** Everything else that decides the answer and is fixed for the run: temperature, seed, the SHA-256 of the rubric. Part of what makes two critiques the same. Never put a credential here. */
|
|
60
|
+
readonly parameters?: Readonly<Record<string, unknown>>;
|
|
61
|
+
/** Whether `setup` must run first (for example to download and verify a checkpoint). */
|
|
62
|
+
readonly needsSetup: boolean;
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
What a critic says about itself before it runs, so a person can decide whether to run it.
|
|
67
|
+
|
|
68
|
+
#### `CriticInput`
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
export type CriticInput = {
|
|
72
|
+
/** The exact bytes that were stored. */
|
|
73
|
+
readonly image: Uint8Array;
|
|
74
|
+
/** Their SHA-256, lowercase hex. */
|
|
75
|
+
readonly imageSha256: string;
|
|
76
|
+
/** What is known about the UI. */
|
|
77
|
+
readonly entry: CatalogEntry;
|
|
78
|
+
/** Aborted when the call times out or the run is cancelled. A critic that can stop early should. */
|
|
79
|
+
readonly signal: AbortSignal;
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
What `evaluate` is handed: the exact stored bytes, their hash, the entry and a signal that fires on timeout or cancellation.
|
|
84
|
+
|
|
85
|
+
#### `CriticAnswer`
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
export type CriticAnswer = Pick<Critique, 'verdict' | 'findings' | 'usage'>;
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
What `evaluate` returns: a critique without its `schema`, `subject`, `critic`, `model` and `parameters`, which the
|
|
92
|
+
runner fills in from the critic and the entry so they cannot disagree with what was run. A critic that has only a
|
|
93
|
+
score returns `{ verdict: { score } }`; a richer one adds `findings`.
|
|
94
|
+
|
|
95
|
+
#### `Critic`
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
export type Critic = {
|
|
99
|
+
/** Short and stable: `uiclip`, `vision-llm`. With the version it names the critic in every critique and, as `critic.<name>`, the producer of its layers. */
|
|
100
|
+
readonly name: string;
|
|
101
|
+
/** Of the critic's own logic. A newer version replaces an older one's critiques and is judged again. */
|
|
102
|
+
readonly version: string;
|
|
103
|
+
/** Open vocabulary: `model`, `llm`, `ml`, `heuristic`, or anything else. */
|
|
104
|
+
readonly kind?: string;
|
|
105
|
+
/** Where images go, which model, which fixed parameters, whether setup is needed. */
|
|
106
|
+
describe(): CriticDescription;
|
|
107
|
+
/** Provisions what the critic needs, such as downloading and verifying a checkpoint. Inference never downloads anything. */
|
|
108
|
+
setup?(): Promise<void>;
|
|
109
|
+
/** Judges one image. */
|
|
110
|
+
evaluate(input: CriticInput): Promise<CriticAnswer>;
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A critic. `evaluate` judges one image and may throw: throw a `CriticError` subclass (`RateLimitedError`,
|
|
115
|
+
`InvalidOutputError`) to say why, and the runner records the failure and retries it on the next run. It never
|
|
116
|
+
becomes a score.
|
|
117
|
+
|
|
118
|
+
#### `CriticError`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
export class CriticError extends Error
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Why a critic could not produce a verdict. `reason` is a code in an open vocabulary and is what is recorded in the
|
|
125
|
+
critique (`failure.reason`); the message is recorded too, after credentials are redacted from it.
|
|
126
|
+
|
|
127
|
+
#### `RateLimitedError`
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
export class RateLimitedError extends CriticError
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
A provider answered that it is rate limited. `retryAfterMs` is its `Retry-After`, when it said: the runner waits at least that long before trying again.
|
|
134
|
+
|
|
135
|
+
#### `InvalidOutputError`
|
|
136
|
+
|
|
137
|
+
```ts
|
|
138
|
+
export class InvalidOutputError extends CriticError
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
A provider answered with something that is not the structure that was asked for. The entry is recorded as failed and no score is invented.
|
|
142
|
+
|
|
143
|
+
#### `defineCritic`
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export function defineCritic<T extends Critic>(critic: T): T
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Checks a critic's shape and returns it unchanged, so a typo is found where the critic is defined and not in the middle
|
|
150
|
+
of a run. Throws when the name is not made of lowercase letters, digits, `-` and `.`, or when the version is empty.
|
|
151
|
+
|
|
152
|
+
**Example**
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
export default defineCritic({
|
|
156
|
+
name: 'contrast', version: '1.0.0', kind: 'heuristic',
|
|
157
|
+
describe: () => ({ sendsImagesTo: null, needsSetup: false }),
|
|
158
|
+
evaluate: async ({ image }) => ({ verdict: { score: measureContrast(image), scale: '0..1, higher is better' } })
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Credentials for bring-your-own-AI critics
|
|
163
|
+
|
|
164
|
+
Source: `packages/critics/src/credentials.ts`
|
|
165
|
+
|
|
166
|
+
Configuration names a credential by reference (`env:OPENAI_API_KEY`, `file:~/.config/key`) and never contains one,
|
|
167
|
+
so it can be committed. A credential is read at run time and never written to a layer, a log, an error message, a
|
|
168
|
+
progress display, a cache file or JSON output: everything a run writes goes through a redactor built from the
|
|
169
|
+
credentials the run holds. A configuration that contains a literal credential is refused with a message that names
|
|
170
|
+
where it is and never repeats it.
|
|
171
|
+
|
|
172
|
+
See docs/critics.md#credentials
|
|
173
|
+
|
|
174
|
+
#### `CredentialRef`
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
export type CredentialRef = { readonly scheme: 'env'; readonly name: string } | { readonly scheme: 'file'; readonly path: string };
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A credential by reference: the environment variable that holds it, or the file that does.
|
|
181
|
+
|
|
182
|
+
#### `REDACTED`
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
export const REDACTED = …
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The text that stands for a credential in anything written.
|
|
189
|
+
|
|
190
|
+
#### `parseCredentialRef`
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
export function parseCredentialRef(text: string): CredentialRef | null
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Reads a reference such as `env:OPENAI_API_KEY` or `file:/run/secrets/key`.
|
|
197
|
+
|
|
198
|
+
Returns `null` when the text is not a reference (so it may be a literal credential, which configuration must not
|
|
199
|
+
contain).
|
|
200
|
+
|
|
201
|
+
**Example**
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
parseCredentialRef('env:SOME_API_KEY'); // { scheme: 'env', name: 'SOME_API_KEY' }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### `resolveCredential`
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
export async function resolveCredential(ref: string, source: { readonly env?: Readonly<Record<string, string | undefined>>; readonly readFile?: (path: string) => Promise<string> } = {}): Promise<string>
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Resolves a reference to the credential it names, at run time.
|
|
214
|
+
|
|
215
|
+
Throws, naming the reference and never a value, when the text is not a reference, the variable is unset or empty, or
|
|
216
|
+
the file cannot be read or is empty. A trailing newline in a file is not part of the credential.
|
|
217
|
+
|
|
218
|
+
- `ref`: the reference from configuration.
|
|
219
|
+
|
|
220
|
+
- `source`: where to read from; the defaults are the process environment and the file system. Tests pass fakes.
|
|
221
|
+
|
|
222
|
+
#### `LiteralCredential`
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
export type LiteralCredential = { readonly path: string; readonly reason: string };
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
A place in a configuration that holds a literal credential: where, and why it was judged one. Never the value.
|
|
229
|
+
|
|
230
|
+
#### `findLiteralCredentials`
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
export function findLiteralCredentials(config: unknown, path = ''): LiteralCredential[]
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Finds literal credentials in a configuration: a string under a key named like a credential (`apiKey`, `token`,
|
|
237
|
+
`secret`…) that is not a reference, and any string anywhere that has the shape of a provider key, token or JWT.
|
|
238
|
+
A key whose name ends in `Ref` or `Env`, or whose value is a reference, is fine.
|
|
239
|
+
|
|
240
|
+
Returns the findings in document order; the value of a finding is never included.
|
|
241
|
+
|
|
242
|
+
#### `assertNoLiteralCredentials`
|
|
243
|
+
|
|
244
|
+
```ts
|
|
245
|
+
export function assertNoLiteralCredentials(config: unknown): void
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Refuses a configuration that contains a literal credential. Throws one error that lists every place, with the reason
|
|
249
|
+
and never the value.
|
|
250
|
+
|
|
251
|
+
#### `Redactor`
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
export type Redactor = (text: string) => string;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Replaces credentials in text with {@link REDACTED}.
|
|
258
|
+
|
|
259
|
+
#### `createRedactor`
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
export function createRedactor(secrets: readonly string[]): Redactor
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Builds a redactor for the credentials a run holds. It removes each secret (and its URL-encoded and base64 forms) and
|
|
266
|
+
anything shaped like a provider key, token or JWT. Secrets shorter than 6 characters are ignored, because replacing
|
|
267
|
+
them would damage ordinary text and they are not credentials.
|
|
268
|
+
|
|
269
|
+
- `secrets`: the resolved credentials of the run.
|
|
270
|
+
|
|
271
|
+
### Critiques as layers
|
|
272
|
+
|
|
273
|
+
Source: `packages/critics/src/critique.ts`
|
|
274
|
+
|
|
275
|
+
Turns a critic's judgements into the checks of a layer (`critiqueChecks`, `critiqueLayer`) and reads them back
|
|
276
|
+
(`readCritique`, `critiquesOfLayer`). A critique is a check in a part of kind `critique` whose `status` says whether
|
|
277
|
+
the critic ran; the opinion is in `extensions["atlas.critique"]`, and is repeated in standard evidence (a
|
|
278
|
+
measurement for the score, an annotation for each finding) so a viewer that has never heard of critics still shows
|
|
279
|
+
it. Critiques are anchored to the image hash they judged: when an entry's image changes, an older critique is still
|
|
280
|
+
shown, as judging an earlier image, and the entry counts as not yet judged (`critiqueCurrency`).
|
|
281
|
+
|
|
282
|
+
See docs/critics.md
|
|
283
|
+
See docs/extending.md
|
|
284
|
+
|
|
285
|
+
#### `CRITIQUE_CHECK_PREFIX`
|
|
286
|
+
|
|
287
|
+
```ts
|
|
288
|
+
export const CRITIQUE_CHECK_PREFIX = …
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The prefix of the id of every critique check: `critique/<entry key>`. It keeps a critique from ever sharing a slot with a test.
|
|
292
|
+
|
|
293
|
+
#### `CritiqueOutcome`
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
export type CritiqueOutcome = {
|
|
297
|
+
/** The entry and the image that was judged. */
|
|
298
|
+
readonly entry: JudgeableEntry;
|
|
299
|
+
/** The critique with its subject, critic and verdict (or failure) filled in. */
|
|
300
|
+
readonly critique: Critique;
|
|
301
|
+
};
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
The result of judging one entry: a critique, or the reason there is none. Either way it has what it judged.
|
|
305
|
+
|
|
306
|
+
#### `critiqueCheckId`
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
export function critiqueCheckId(entryKey: string): string
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The check ids and labels one critique is published under.
|
|
313
|
+
|
|
314
|
+
#### `criticProducer`
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
export function criticProducer(criticName: string): string
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
`critic.<name>`: the producer of every layer of the critic's critiques. A critic-scoped token may publish only such layers.
|
|
321
|
+
|
|
322
|
+
#### `buildCritique`
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
export function buildCritique(critic: Pick<Critic, 'name' | 'version' | 'kind'>, description: CriticDescription, entry: JudgeableEntry, answer: Pick<Critique, 'verdict' | 'findings' | 'usage' | 'failure'>): Critique
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Builds the critique a critic produced for an entry. It fills in `schema`, `subject`, `critic`, `model` and
|
|
329
|
+
`parameters` from the critic and the entry (so they cannot disagree with what was run) and keeps the verdict,
|
|
330
|
+
findings and usage as given.
|
|
331
|
+
|
|
332
|
+
- `critic`: the critic that ran.
|
|
333
|
+
|
|
334
|
+
- `description`: the critic's `describe()` at the time of the run.
|
|
335
|
+
|
|
336
|
+
- `entry`: the entry and the image hash that was judged.
|
|
337
|
+
|
|
338
|
+
- `answer`: the critic's answer, or a `failure` instead of one.
|
|
339
|
+
|
|
340
|
+
#### `critiqueChecks`
|
|
341
|
+
|
|
342
|
+
```ts
|
|
343
|
+
export function critiqueChecks(outcomes: readonly CritiqueOutcome[], options: { readonly threshold?: number } = {}): NonNullable<PublishChecksInput['checks']>
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
The checks of one layer for a set of outcomes, in the order given. A verdict is a `passed` check (the critic ran), a
|
|
347
|
+
failure a `failed` one with its reason code in `detail`. The score is a measurement named `polish`; each finding is
|
|
348
|
+
also an annotation of type `critique`. Nothing here decides a result: a low score is `passed`.
|
|
349
|
+
|
|
350
|
+
#### `critiqueLayer`
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
export function critiqueLayer(args: {
|
|
354
|
+
readonly critic: Pick<Critic, 'name' | 'version'>;
|
|
355
|
+
readonly outcomes: readonly CritiqueOutcome[];
|
|
356
|
+
readonly recordedAtMs: number;
|
|
357
|
+
readonly sha?: string | null;
|
|
358
|
+
readonly branch?: string | null;
|
|
359
|
+
readonly runUrl?: string | null;
|
|
360
|
+
readonly environment?: string | null;
|
|
361
|
+
readonly parameters?: Readonly<Record<string, string>>;
|
|
362
|
+
readonly threshold?: number;
|
|
363
|
+
}): PublishChecksInput
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
The input of `publishChecks` for one critic's outcomes: producer `critic.<name>`, part kind `critique`. The model and
|
|
367
|
+
critic versions are labels and not part of the slot, so a newer version replaces an older one's critique of the same
|
|
368
|
+
entry. Pass `parameters` to move something (such as a model version) into the slot instead, to compare side by side.
|
|
369
|
+
|
|
370
|
+
- `args.critic`: the critic that ran.
|
|
371
|
+
|
|
372
|
+
- `args.outcomes`: what it produced, successes and failures.
|
|
373
|
+
|
|
374
|
+
- `args.recordedAtMs`: when, epoch milliseconds. Nothing here reads a clock, so the same input is the same layer.
|
|
375
|
+
|
|
376
|
+
#### `readCritique`
|
|
377
|
+
|
|
378
|
+
```ts
|
|
379
|
+
export function readCritique(check: { readonly extensions?: Readonly<Record<string, unknown>> | undefined }): Critique | null
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
Reads the critique of a check, or `null` when there is none that this reader can read: no `atlas.critique` extension,
|
|
383
|
+
one that is malformed (a missing `subject`, a score that is not a number), or one of a version this release does not
|
|
384
|
+
know. The check and its status are never affected: an unreadable opinion is unknown, not a pass and not a failure.
|
|
385
|
+
Fields and values it does not know (a new category, severity or label) are kept.
|
|
386
|
+
|
|
387
|
+
#### `FoundCritique`
|
|
388
|
+
|
|
389
|
+
```ts
|
|
390
|
+
export type FoundCritique = {
|
|
391
|
+
readonly layerId: string;
|
|
392
|
+
readonly producer: string;
|
|
393
|
+
readonly recordedAtMs: number;
|
|
394
|
+
readonly checkId: string;
|
|
395
|
+
/** The check's own status: whether the critic ran, never what it thought. */
|
|
396
|
+
readonly status: string;
|
|
397
|
+
readonly critique: Critique;
|
|
398
|
+
};
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
A critique found in a layer, with where it was found and whether the critic ran.
|
|
402
|
+
|
|
403
|
+
#### `critiquesOfLayer`
|
|
404
|
+
|
|
405
|
+
```ts
|
|
406
|
+
export function critiquesOfLayer(layer: { readonly id: string; readonly producer: string; readonly parts: readonly { readonly kind: string; readonly recordedAtMs: number; readonly checks: readonly { readonly id: string; readonly status: string; readonly extensions?: Readonly<Record<string, unknown>> | undefined }[] }[] }): FoundCritique[]
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
Every readable critique in a layer's parts of kind `critique`. Checks whose critique cannot be read are left out, and
|
|
410
|
+
so is any `atlas.critique` extension in a part of another kind: a test result is never mistaken for an opinion.
|
|
411
|
+
|
|
412
|
+
#### `critiqueCurrency`
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
export function critiqueCurrency(critique: Pick<Critique, 'subject'>, currentImageSha256: string): 'current' | 'earlier-image'
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
Whether a critique judged the image an entry has now. `current`: yes. `earlier-image`: the entry's image has changed
|
|
419
|
+
since, so the critique is shown as judging an earlier image and the entry counts as not yet judged by that critic.
|
|
420
|
+
|
|
421
|
+
#### `critiqueIdentity`
|
|
422
|
+
|
|
423
|
+
```ts
|
|
424
|
+
export function critiqueIdentity(args: { readonly critic: { readonly name: string; readonly version: string }; readonly model?: { readonly id: string; readonly provider?: string | undefined } | undefined; readonly parameters?: Readonly<Record<string, unknown>> | undefined; readonly imageSha256: string }): string
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The identity of a critique for caching: the critic, its version, the model, the fixed parameters (the rubric hash
|
|
428
|
+
among them) and the image hash. Two critiques with the same identity are the same judgement, and the second is not
|
|
429
|
+
made. Key order in `parameters` does not matter.
|
|
430
|
+
|
|
431
|
+
#### `identityOfCritique`
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
export function identityOfCritique(critique: Critique): string
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The identity of an existing critique, as {@link critiqueIdentity} computes it for a run.
|
|
438
|
+
|
|
439
|
+
### Critics (`@crvouga/atlas/critics`)
|
|
440
|
+
|
|
441
|
+
Source: `packages/critics/src/index.ts`. Import: `@crvouga/atlas/critics`
|
|
442
|
+
|
|
443
|
+
Automated reviewers of the UI catalog that mark which UIs need polish. A critic is a producer: it judges one captured
|
|
444
|
+
UI at a time through one small contract (`defineCritic`), the runner (`runCritic`) owns concurrency, retries, rate
|
|
445
|
+
limits, budgets and caching, and the opinions are stored as ordinary layers of parts of kind `critique` with the
|
|
446
|
+
`atlas.critique` evidence (`critiqueLayer`, `readCritique`). A critique never changes whether a report passes: it is
|
|
447
|
+
not a test result, is not counted, and only a project that opts in to `gate` mode can fail a command on a score, and
|
|
448
|
+
only from calibrated, held-out evidence. Model-based, LLM-based and heuristic critics are the same on every
|
|
449
|
+
path. Nothing here needs a model, a provider SDK or a network; critics bring their own.
|
|
450
|
+
|
|
451
|
+
See docs/critics.md
|
|
452
|
+
|
|
453
|
+
### State-aware judging, calibration and modes
|
|
454
|
+
|
|
455
|
+
Source: `packages/critics/src/judging.ts`
|
|
456
|
+
|
|
457
|
+
A raw score is not absolute. A loading overlay is not a full screen, so a score is read against the project's scores
|
|
458
|
+
of the same state kind (`relativeVerdict`), and a threshold is measured per critic and per state kind against labelled
|
|
459
|
+
examples (`calibrate`), recording sample sizes, precision and recall and whether the figures are in-sample or
|
|
460
|
+
held-out. What happens when a score is low is the project's choice (`gateDecision`): `report` (shown, never fails
|
|
461
|
+
anything), `warn` (surfaced on the command line) or `gate` (exit code 1 when a threshold is crossed, but only on a
|
|
462
|
+
calibration with enough held-out evidence). A gate is computed here, by the reader, from critique data. It never
|
|
463
|
+
changes a check's status, a slot or a winner.
|
|
464
|
+
|
|
465
|
+
All scores are "higher is better". Everything here is pure.
|
|
466
|
+
|
|
467
|
+
See docs/critics.md#state-aware-judging-calibration-and-modes
|
|
468
|
+
|
|
469
|
+
#### `Relative`
|
|
470
|
+
|
|
471
|
+
```ts
|
|
472
|
+
export type Relative = NonNullable<NonNullable<Critique['verdict']>['relative']>;
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
The relative reading of a score, as stored in `verdict.relative`.
|
|
476
|
+
|
|
477
|
+
#### `relativeVerdict`
|
|
478
|
+
|
|
479
|
+
```ts
|
|
480
|
+
export function relativeVerdict(args: { readonly score: number; readonly stateKind: string; readonly population: readonly { readonly stateKind: string; readonly score: number }[]; readonly previousScore?: number }): Relative
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Reads a score against the scores of the same state kind, and against the same entry's previous image.
|
|
484
|
+
|
|
485
|
+
`percentile` is where the score falls among the population's scores of `stateKind` (ties count half), 0..100, higher
|
|
486
|
+
is better; it is absent when no score of that state kind is known. Scores of other state kinds are ignored, so a
|
|
487
|
+
loading overlay is compared with other loading overlays and never with full screens. `delta` is the score minus
|
|
488
|
+
`previousScore` when there was one.
|
|
489
|
+
|
|
490
|
+
- `args.score`: the entry's raw score.
|
|
491
|
+
|
|
492
|
+
- `args.stateKind`: the kind of UI state of the entry.
|
|
493
|
+
|
|
494
|
+
- `args.population`: every known score of the critic, with its state kind. May include the entry's own.
|
|
495
|
+
|
|
496
|
+
- `args.previousScore`: the critic's score for the entry's previous image, when there was one.
|
|
497
|
+
|
|
498
|
+
#### `LabelledExample`
|
|
499
|
+
|
|
500
|
+
```ts
|
|
501
|
+
export type LabelledExample = {
|
|
502
|
+
readonly score: number;
|
|
503
|
+
readonly stateKind: string;
|
|
504
|
+
/** True when the UI needs polish: a positive for the critic to find. */
|
|
505
|
+
readonly bad: boolean;
|
|
506
|
+
/** True for an example the threshold was not chosen on. */
|
|
507
|
+
readonly heldOut?: boolean;
|
|
508
|
+
};
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
A labelled example for calibration: the critic's score for a UI, its state kind, and whether people judged it bad (needs polish).
|
|
512
|
+
|
|
513
|
+
#### `KindCalibration`
|
|
514
|
+
|
|
515
|
+
```ts
|
|
516
|
+
export type KindCalibration = {
|
|
517
|
+
readonly stateKind: string;
|
|
518
|
+
/** Scores below this are flagged as needing polish. `null`: none could be chosen. */
|
|
519
|
+
readonly threshold: number | null;
|
|
520
|
+
/** Examples of this state kind, all together. */
|
|
521
|
+
readonly samples: number;
|
|
522
|
+
/** How many of them are bad, and how many good. */
|
|
523
|
+
readonly positives: number;
|
|
524
|
+
readonly negatives: number;
|
|
525
|
+
/** Of what was flagged, the share that was bad; `null` when nothing was flagged. Measured on the examples named by `evaluatedOn`. */
|
|
526
|
+
readonly precision: number | null;
|
|
527
|
+
/** Of what was bad, the share that was flagged; `null` when nothing was bad. */
|
|
528
|
+
readonly recall: number | null;
|
|
529
|
+
/** True when the figures are measured on the examples the threshold was chosen on, which flatters them. */
|
|
530
|
+
readonly inSample: boolean;
|
|
531
|
+
/** Which examples the precision and recall are over: the `fit` examples (in-sample) or the `held-out` ones. */
|
|
532
|
+
readonly evaluatedOn: 'fit' | 'held-out';
|
|
533
|
+
};
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
The calibration of one state kind. `threshold` flags a score below it; `null` when the examples cannot tell (one class missing).
|
|
537
|
+
|
|
538
|
+
#### `Calibration`
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
export type Calibration = {
|
|
542
|
+
readonly schema: 1;
|
|
543
|
+
readonly critic: { readonly name: string; readonly version: string };
|
|
544
|
+
readonly kinds: readonly KindCalibration[];
|
|
545
|
+
};
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
The result of `calibrate`: per state kind, with the critic's provenance.
|
|
549
|
+
|
|
550
|
+
#### `calibrate`
|
|
551
|
+
|
|
552
|
+
```ts
|
|
553
|
+
export function calibrate(critic: { readonly name: string; readonly version: string }, examples: readonly LabelledExample[]): Calibration
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Measures a critic against labelled examples, per state kind, and proposes a threshold for each.
|
|
557
|
+
|
|
558
|
+
The threshold of a state kind is chosen on its examples that are not `heldOut` (best F1, then best precision, then
|
|
559
|
+
the lower threshold), among the midpoints between neighbouring scores. If there are held-out examples of that kind,
|
|
560
|
+
precision and recall are measured on them and `inSample` is false; otherwise they are measured on the same examples
|
|
561
|
+
the threshold was chosen on and `inSample` is true, so nobody mistakes them for evidence. A state kind without both
|
|
562
|
+
a good and a bad example gets `threshold: null`. State kinds come out sorted.
|
|
563
|
+
|
|
564
|
+
- `critic`: the critic whose scores these are.
|
|
565
|
+
|
|
566
|
+
- `examples`: labelled examples; scores must be finite.
|
|
567
|
+
|
|
568
|
+
#### `GateMode`
|
|
569
|
+
|
|
570
|
+
```ts
|
|
571
|
+
export type GateMode = 'report' | 'warn' | 'gate';
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
What happens when a score is below its threshold. `report` shows it and never fails anything. `warn` surfaces it on the command line. `gate` exits 1.
|
|
575
|
+
|
|
576
|
+
#### `MinimumEvidence`
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
export type MinimumEvidence = {
|
|
580
|
+
/** Bad examples and good examples of the state kind, each. Default 10. */
|
|
581
|
+
readonly perClass: number;
|
|
582
|
+
/** Whether precision and recall must be measured on held-out examples. Default true. */
|
|
583
|
+
readonly heldOut: boolean;
|
|
584
|
+
};
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
The least evidence a calibration must have before `gate` is allowed.
|
|
588
|
+
|
|
589
|
+
#### `DEFAULT_MINIMUM_EVIDENCE`
|
|
590
|
+
|
|
591
|
+
```ts
|
|
592
|
+
export const DEFAULT_MINIMUM_EVIDENCE: MinimumEvidence
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
The default minimum evidence for `gate`: 10 bad and 10 good examples per state kind, measured on held-out examples.
|
|
596
|
+
|
|
597
|
+
#### `BelowThreshold`
|
|
598
|
+
|
|
599
|
+
```ts
|
|
600
|
+
export type BelowThreshold = { readonly entryKey: string; readonly stateKind: string; readonly score: number; readonly threshold: number };
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
A score below its state kind's threshold.
|
|
604
|
+
|
|
605
|
+
#### `GateDecision`
|
|
606
|
+
|
|
607
|
+
```ts
|
|
608
|
+
export type GateDecision = {
|
|
609
|
+
readonly mode: GateMode;
|
|
610
|
+
/** 0 it passes (always, in `report` and `warn`), 1 a threshold was crossed in `gate`, 2 `gate` was refused for lack of evidence. */
|
|
611
|
+
readonly exitCode: 0 | 1 | 2;
|
|
612
|
+
/** Scores below their threshold, in the order given. */
|
|
613
|
+
readonly below: readonly BelowThreshold[];
|
|
614
|
+
/** Entries with no score to judge (the critic failed or gave none). Never counted as below, never as fine. */
|
|
615
|
+
readonly unjudged: readonly string[];
|
|
616
|
+
/** For `warn`: one line per score below its threshold. */
|
|
617
|
+
readonly warnings: readonly string[];
|
|
618
|
+
/** For a refused `gate`: what is missing, per state kind. */
|
|
619
|
+
readonly refusal: readonly string[];
|
|
620
|
+
};
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
The decision: the exit code and why. `refusal` is set when `gate` was asked for without enough evidence.
|
|
624
|
+
|
|
625
|
+
#### `gateDecision`
|
|
626
|
+
|
|
627
|
+
```ts
|
|
628
|
+
export function gateDecision(args: {
|
|
629
|
+
readonly mode: GateMode;
|
|
630
|
+
readonly critic: { readonly name: string; readonly version: string };
|
|
631
|
+
readonly scores: readonly { readonly entryKey: string; readonly stateKind: string; readonly score: number | null }[];
|
|
632
|
+
readonly calibration?: Calibration;
|
|
633
|
+
readonly thresholds?: Readonly<Record<string, number>>;
|
|
634
|
+
readonly minimum?: MinimumEvidence;
|
|
635
|
+
}): GateDecision
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Decides what a set of critic scores means for the exit code of a command.
|
|
639
|
+
|
|
640
|
+
`report` and `warn` always give exit code 0 (`warn` also lists a warning for each score below its threshold).
|
|
641
|
+
`gate` gives 1 when any score is below its state kind's threshold, but first requires, for every state kind present, a
|
|
642
|
+
calibration of this critic and version with a threshold and at least `minimum.perClass` bad and good examples,
|
|
643
|
+
measured on held-out examples when `minimum.heldOut`. Without that it refuses (exit 2) and says what is missing.
|
|
644
|
+
An explicit `thresholds` entry replaces the calibrated threshold of its state kind but never waives the evidence.
|
|
645
|
+
|
|
646
|
+
- `args.scores`: the critic's scores, one per entry; `score: null` for an entry it could not judge.
|
|
647
|
+
|
|
648
|
+
- `args.calibration`: the critic's calibration, when there is one.
|
|
649
|
+
|
|
650
|
+
- `args.thresholds`: thresholds by state kind that replace the calibrated ones.
|
|
651
|
+
|
|
652
|
+
### Publishing critiques
|
|
653
|
+
|
|
654
|
+
Source: `packages/critics/src/publish.ts`
|
|
655
|
+
|
|
656
|
+
`publishCritiques` publishes a critic's outcomes as one layer through any store, after checking that the layer is
|
|
657
|
+
within what a critic-scoped credential may publish. The layer is content-addressed, so publishing the same outcomes
|
|
658
|
+
again writes the same layer and changes nothing.
|
|
659
|
+
|
|
660
|
+
See docs/critics.md
|
|
661
|
+
|
|
662
|
+
#### `publishCritiques`
|
|
663
|
+
|
|
664
|
+
```ts
|
|
665
|
+
export async function publishCritiques(store: ObjectStore, args: Parameters<typeof critiqueLayer>[0], options: PublishChecksOptions = {}): Promise<ReportLayer | null>
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
Publishes the layer {@link critiqueLayer} builds for `args`. Returns the stored layer, or `null` when there is nothing
|
|
669
|
+
to publish (no outcomes). Throws when the layer would be outside a critic's scope, which cannot happen for a layer
|
|
670
|
+
built here and is checked so that a change cannot make it happen quietly.
|
|
671
|
+
|
|
672
|
+
- `store`: where to publish.
|
|
673
|
+
|
|
674
|
+
- `args`: see {@link critiqueLayer}.
|
|
675
|
+
|
|
676
|
+
- `options`: attachment limits, as for `publishChecks`.
|
|
677
|
+
|
|
678
|
+
### The critic runner
|
|
679
|
+
|
|
680
|
+
Source: `packages/critics/src/runner.ts`
|
|
681
|
+
|
|
682
|
+
Runs one critic over entries of the UI catalog and returns the critiques to publish. The runner, not the critic, owns
|
|
683
|
+
everything around the judgement: skipping what was already judged (same critic, version, model, parameters and image
|
|
684
|
+
hash), fetching images only when needed, a bounded number of concurrent calls, a per-call timeout, retries with
|
|
685
|
+
jittered backoff that honour a provider's `Retry-After`, budgets (images, tokens, money) that stop the run cleanly,
|
|
686
|
+
and credential redaction of every message it records. Failures are data: a crash, a timeout, a rate limit that never
|
|
687
|
+
cleared and an unparseable answer each become a critique with a `failure` and a reason code, which the next run
|
|
688
|
+
retries, and never a score.
|
|
689
|
+
|
|
690
|
+
Results are returned in the order of the entries, so the same inputs publish the same layer. Nothing here reads a
|
|
691
|
+
clock, the environment or the network on its own: images, time, sleeping and randomness are passed in.
|
|
692
|
+
|
|
693
|
+
See docs/critics.md#running-critics
|
|
694
|
+
|
|
695
|
+
#### `Budget`
|
|
696
|
+
|
|
697
|
+
```ts
|
|
698
|
+
export type Budget = {
|
|
699
|
+
/** The most images to start judging in one run (retries of one image count once). */
|
|
700
|
+
readonly maxImages?: number;
|
|
701
|
+
/** The most tokens (input and output) to spend, summed from what critics report as `usage`. */
|
|
702
|
+
readonly maxTokens?: number;
|
|
703
|
+
/** The most money to spend, in US dollars, summed from what critics report as `usage.costUsd`. */
|
|
704
|
+
readonly maxCostUsd?: number;
|
|
705
|
+
};
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
The most a run may spend before it stops cleanly. Any limit left out is unlimited.
|
|
709
|
+
|
|
710
|
+
#### `RunOptions`
|
|
711
|
+
|
|
712
|
+
```ts
|
|
713
|
+
export type RunOptions = {
|
|
714
|
+
/** The critic to run. */
|
|
715
|
+
readonly critic: Critic;
|
|
716
|
+
/** The entries to judge, each with the hash of its stored image. Judged in this order; a repeated key is judged once. */
|
|
717
|
+
readonly entries: Iterable<JudgeableEntry> | AsyncIterable<JudgeableEntry>;
|
|
718
|
+
/** Fetches an entry's image bytes. Called only for entries that will be judged. The runner checks the bytes against `imageSha256`. */
|
|
719
|
+
readonly loadImage: (entry: JudgeableEntry) => Promise<Uint8Array>;
|
|
720
|
+
/** Critiques that already exist (read with `critiquesOfLayer`). One with a verdict and the same identity as this run's makes the image skipped; a failed one does not. */
|
|
721
|
+
readonly existing?: readonly Critique[];
|
|
722
|
+
/** Judge again even when a current critique exists. */
|
|
723
|
+
readonly force?: boolean;
|
|
724
|
+
/** Plan only: count what would be judged and skipped and say where images would go. Nothing is loaded, sent or returned to publish. */
|
|
725
|
+
readonly dryRun?: boolean;
|
|
726
|
+
/** Most calls in flight at once. Default 4. */
|
|
727
|
+
readonly concurrency?: number;
|
|
728
|
+
/** Most attempts for one image when the provider is rate limited. Default 4. */
|
|
729
|
+
readonly maxAttempts?: number;
|
|
730
|
+
/** A call that takes longer than this is recorded as `timeout`, and the run goes on. Default 60 000. */
|
|
731
|
+
readonly timeoutMs?: number;
|
|
732
|
+
/** Backoff between attempts: `baseMs` doubles each attempt up to `maxMs`, with full jitter, and never less than `Retry-After`. Defaults 500 and 30 000. */
|
|
733
|
+
readonly backoff?: { readonly baseMs?: number; readonly maxMs?: number };
|
|
734
|
+
/** Stop cleanly when any limit is reached. */
|
|
735
|
+
readonly budget?: Budget;
|
|
736
|
+
/** Credentials this run holds. Every recorded message has them removed. */
|
|
737
|
+
readonly secrets?: readonly string[];
|
|
738
|
+
/** Cancels the run: in-flight calls are aborted, what finished is returned. */
|
|
739
|
+
readonly signal?: AbortSignal;
|
|
740
|
+
/** Waits. Default `setTimeout`. Tests pass a fake. */
|
|
741
|
+
readonly sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
|
|
742
|
+
/** A number in [0, 1) for jitter. Default `Math.random`. */
|
|
743
|
+
readonly random?: () => number;
|
|
744
|
+
/** Called after each entry is finished or skipped, for progress. Receives no image bytes and no credentials. */
|
|
745
|
+
readonly onProgress?: (event: ProgressEvent) => void;
|
|
746
|
+
};
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
What a run was told to do and how. Everything but the critic, the entries and `loadImage` has a default.
|
|
750
|
+
|
|
751
|
+
#### `ProgressEvent`
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
export type ProgressEvent = {
|
|
755
|
+
readonly entryKey: string;
|
|
756
|
+
readonly result: 'judged' | 'skipped' | 'failed';
|
|
757
|
+
readonly judged: number;
|
|
758
|
+
readonly skipped: number;
|
|
759
|
+
readonly failed: number;
|
|
760
|
+
readonly waiting: number;
|
|
761
|
+
};
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
One step of progress. `waiting` counts entries not yet started.
|
|
765
|
+
|
|
766
|
+
#### `StoppedBy`
|
|
767
|
+
|
|
768
|
+
```ts
|
|
769
|
+
export type StoppedBy = 'budget' | 'aborted';
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Why a run ended before every entry was handled: it `budget`ed out or was `aborted`.
|
|
773
|
+
|
|
774
|
+
#### `RunResult`
|
|
775
|
+
|
|
776
|
+
```ts
|
|
777
|
+
export type RunResult = {
|
|
778
|
+
readonly critic: { readonly name: string; readonly version: string };
|
|
779
|
+
/** Where images are sent, as the critic described it. */
|
|
780
|
+
readonly sendsImagesTo: string | null;
|
|
781
|
+
/** The critiques to publish, in entry order: verdicts and failures. */
|
|
782
|
+
readonly outcomes: readonly CritiqueOutcome[];
|
|
783
|
+
/** Entries with a current critique that were not judged again. */
|
|
784
|
+
readonly skipped: number;
|
|
785
|
+
/** Entries judged with a verdict. */
|
|
786
|
+
readonly judged: number;
|
|
787
|
+
/** Entries recorded as failed. */
|
|
788
|
+
readonly failed: number;
|
|
789
|
+
/** Entries left unhandled because the run stopped. */
|
|
790
|
+
readonly waiting: number;
|
|
791
|
+
/** Set when the run stopped early, and why. */
|
|
792
|
+
readonly stoppedBy?: StoppedBy;
|
|
793
|
+
/** What the run spent, from the usage critics reported. */
|
|
794
|
+
readonly spent: { readonly images: number; readonly tokens: number; readonly costUsd: number };
|
|
795
|
+
/** Whether this was a dry run: nothing was loaded, sent or to publish. */
|
|
796
|
+
readonly dryRun: boolean;
|
|
797
|
+
};
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
What a run did. `outcomes` are what to publish (verdicts and failures); counts and `spent` are for the summary.
|
|
801
|
+
|
|
802
|
+
#### `runCritic`
|
|
803
|
+
|
|
804
|
+
```ts
|
|
805
|
+
export async function runCritic(options: RunOptions): Promise<RunResult>
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
Runs a critic over entries. Entries with a current critique of the same identity are skipped without fetching their
|
|
809
|
+
image; the rest are judged with bounded concurrency, a timeout and retries. The run never throws for a critic's
|
|
810
|
+
failure: it records it. It throws only for a wrong call (a non-positive `concurrency`, `maxAttempts` or `timeoutMs`).
|
|
811
|
+
|
|
812
|
+
A run stops cleanly when a budget is reached or the signal aborts: calls in flight finish (or are aborted), what was
|
|
813
|
+
finished is returned in `outcomes` to publish, and `stoppedBy` says why. With concurrency above 1 a budget can be
|
|
814
|
+
overshot by the calls already in flight.
|
|
815
|
+
|
|
816
|
+
**Example**
|
|
817
|
+
|
|
818
|
+
```ts
|
|
819
|
+
const result = await runCritic({ critic, entries, loadImage: entry => fetchImage(entry), existing, budget: { maxImages: 200 } });
|
|
820
|
+
await publishChecks(store, critiqueLayer({ critic, outcomes: result.outcomes, recordedAtMs: Date.now() }));
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### The scope of a critic's credential
|
|
824
|
+
|
|
825
|
+
Source: `packages/critics/src/scope.ts`
|
|
826
|
+
|
|
827
|
+
A critic only needs to add opinions, so its credential may read and publish layers whose producer is `critic.<name>`
|
|
828
|
+
and whose parts are all of kind `critique`, and nothing else. `criticScopeViolation` names the rule a layer breaks;
|
|
829
|
+
the server applies it to every layer a critic-role token uploads, so a stolen critic token cannot forge test results.
|
|
830
|
+
|
|
831
|
+
See docs/critics.md#authentication-and-authorization
|
|
832
|
+
|
|
833
|
+
#### `criticScopeViolation`
|
|
834
|
+
|
|
835
|
+
```ts
|
|
836
|
+
export function criticScopeViolation(layer: { readonly producer?: unknown; readonly parts?: unknown }): string | null
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
The rule a layer breaks for a critic-scoped credential, or `null` when it is within scope. The message says which rule
|
|
840
|
+
and what was found, so the critic can fix it.
|