@crvouga/atlas 1.20.0 → 1.22.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 +15 -1
- package/dist/core/src/cli.d.ts +2 -2
- package/dist/core/src/cli.js +7 -1
- package/dist/critics/src/catalog-client.d.ts +91 -0
- package/dist/critics/src/catalog-client.js +194 -0
- package/dist/critics/src/cli-calibrate.d.ts +4 -0
- package/dist/critics/src/cli-calibrate.js +211 -0
- package/dist/critics/src/cli-common.d.ts +108 -0
- package/dist/critics/src/cli-common.js +155 -0
- package/dist/critics/src/cli-review.d.ts +4 -0
- package/dist/critics/src/cli-review.js +174 -0
- package/dist/critics/src/cli-watch.d.ts +4 -0
- package/dist/critics/src/cli-watch.js +191 -0
- package/dist/critics/src/cli.d.ts +13 -0
- package/dist/critics/src/cli.js +490 -0
- package/dist/critics/src/index.d.ts +3 -0
- package/dist/critics/src/index.js +3 -0
- package/dist/critics/src/judging.d.ts +8 -0
- package/dist/critics/src/llm.d.ts +120 -0
- package/dist/critics/src/llm.js +266 -0
- package/dist/critics/src/marks.d.ts +74 -0
- package/dist/critics/src/marks.js +76 -0
- package/dist/critics/src/registry.d.ts +125 -0
- package/dist/critics/src/registry.js +203 -0
- package/dist/critics/src/spool.d.ts +32 -0
- package/dist/critics/src/spool.js +129 -0
- package/dist/critics/src/uiclip.d.ts +82 -0
- package/dist/critics/src/uiclip.js +167 -0
- package/dist/reports/src/compose.d.ts +23 -1
- package/dist/reports/src/compose.js +29 -19
- package/dist/reports/src/critique-review.d.ts +111 -0
- package/dist/reports/src/critique-review.js +123 -0
- package/dist/reports/src/critique-row.d.ts +24 -0
- package/dist/reports/src/critique-row.js +39 -0
- package/dist/reports/src/evidence-types.d.ts +5 -0
- package/dist/reports/src/layer-merge.js +5 -0
- package/dist/reports/src/report-cli.d.ts +13 -0
- package/dist/reports/src/report-cli.js +6 -4
- package/dist/reports/src/report-index.d.ts +6 -0
- package/dist/reports/src/report-index.js +12 -0
- package/dist/reports/src/ui-catalog-cli.d.ts +10 -0
- package/dist/reports/src/ui-catalog-cli.js +391 -0
- package/dist/reports/src/ui-catalog-diff.d.ts +104 -0
- package/dist/reports/src/ui-catalog-diff.js +130 -0
- package/dist/reports/src/ui-catalog-entries.d.ts +147 -0
- package/dist/reports/src/ui-catalog-entries.js +190 -0
- package/dist/reports/src/ui-catalog-export.d.ts +82 -0
- package/dist/reports/src/ui-catalog-export.js +221 -0
- package/dist/reports/src/ui-catalog-publish.js +2 -2
- package/dist/reports/src/ui-catalog.d.ts +2 -0
- package/dist/reports/src/ui-catalog.js +1 -1
- package/dist/reports/src/ui-state.d.ts +2 -0
- package/dist/reports/src/ui-state.js +2 -2
- package/dist/schema/src/api.d.ts +653 -1
- package/dist/schema/src/api.js +44 -0
- package/dist/schema/src/cli-results.d.ts +991 -1
- package/dist/schema/src/cli-results.js +265 -1
- package/dist/schema/src/critique.d.ts +31 -0
- package/dist/schema/src/critique.js +26 -0
- package/dist/schema/src/server-config.d.ts +3 -3
- package/dist/server/src/app.js +28 -1
- package/dist/server/src/openapi.js +19 -2
- package/docs/agents.md +16 -2
- package/docs/critics.md +273 -9
- package/docs/extending.md +1 -1
- package/docs/reference/cli.md +126 -1
- package/docs/reference/core.md +1 -1
- package/docs/reference/critics.md +1131 -29
- package/docs/reference/http-api.md +40 -0
- package/docs/reference/reports.md +855 -0
- package/docs/reference/schema.md +197 -1
- package/docs/report-contract.md +2 -1
- package/docs/server.md +2 -0
- package/docs/ui-catalog.md +110 -13
- package/docs/ui.md +69 -2
- package/llms-full.txt +636 -26
- package/llms.txt +1 -1
- package/package.json +9 -1
- package/packages/critics/README.md +1 -0
- package/packages/schema/schemas/critic-calibrate-result.schema.json +274 -0
- package/packages/schema/schemas/critic-doctor-result.schema.json +74 -0
- package/packages/schema/schemas/critic-list-result.schema.json +90 -0
- package/packages/schema/schemas/critic-review-result.schema.json +397 -0
- package/packages/schema/schemas/critic-run-result.schema.json +249 -0
- package/packages/schema/schemas/critic-setup-result.schema.json +34 -0
- package/packages/schema/schemas/critic-watch-result.schema.json +211 -0
- package/packages/schema/schemas/critique-mark.schema.json +81 -0
- package/packages/schema/schemas/effective-delta.schema.json +337 -0
- package/packages/schema/schemas/effective-evidence.schema.json +337 -0
- package/packages/schema/schemas/layer-rows.schema.json +337 -0
- package/packages/schema/schemas/ui-catalog-diff-result.schema.json +201 -0
- package/packages/schema/schemas/ui-catalog-export-result.schema.json +83 -0
- package/packages/schema/schemas/ui-catalog-publish-result.schema.json +116 -0
- package/packages/schema/schemas/ui-catalog-status.schema.json +197 -0
- package/packages/server/openapi.json +6755 -4738
- package/packages/visualizer/src/app/AppShell.tsx +3 -1
- package/packages/visualizer/src/app/router.tsx +20 -7
- package/packages/visualizer/src/catalog/Picture.tsx +32 -0
- package/packages/visualizer/src/catalog/UiCatalogCompare.tsx +289 -0
- package/packages/visualizer/src/catalog/UiCatalogPage.tsx +18 -28
- package/packages/visualizer/src/catalog/history.ts +61 -0
- package/packages/visualizer/src/catalog/search.ts +14 -3
- package/packages/visualizer/src/components/Header.tsx +16 -4
- package/packages/visualizer/src/critiques/CritiquesPage.tsx +402 -0
- package/packages/visualizer/src/critiques/FindingOverlay.tsx +50 -0
- package/packages/visualizer/src/critiques/ranking.ts +54 -0
- package/packages/visualizer/src/critiques/search.ts +41 -0
- package/packages/visualizer/src/evidence/applicability-view.tsx +11 -7
- package/packages/visualizer/src/evidence/behaviors-page.tsx +22 -15
- package/packages/visualizer/src/evidence/evidence-context.tsx +2 -1
- package/packages/visualizer/src/explorer/Explorer.tsx +5 -0
- package/packages/visualizer/src/explorer/TestDetail.tsx +4 -1
- package/packages/visualizer/src/explorer/parts.tsx +19 -1
- package/packages/visualizer/src/explorer/playlist.tsx +11 -7
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module Command results (`--format json`)
|
|
3
3
|
*
|
|
4
|
-
* What `atlas report publish`, `stream`, `merge`, `doctor` and `verify-recordings` print on standard output with `--format json` (alias
|
|
4
|
+
* What `atlas report publish`, `stream`, `merge`, `doctor` and `verify-recordings`, `atlas ui-catalog plan`, `publish`, `status`, `diff` and `export`, and `atlas critic list`, `setup`, `doctor` and `run`, print on standard output with `--format json` (alias
|
|
5
5
|
* `--json`), and what any `atlas` command prints on standard error for a command that was wrong: one JSON document,
|
|
6
6
|
* so an agent or a script never has to parse prose. (`status` and `policy` already print documents:
|
|
7
7
|
* `ReportStatusSchema`, and an IAM policy.) Each is also published as a JSON Schema.
|
|
@@ -154,6 +154,258 @@ export const InitPlanSchema = z.looseObject({
|
|
|
154
154
|
nextSteps: z.array(z.string()).describe('What to do next, in order, as plain instructions an agent can follow'),
|
|
155
155
|
warnings: z.array(z.string())
|
|
156
156
|
});
|
|
157
|
+
/** One critic as `atlas critic list` shows it. */
|
|
158
|
+
export const CriticListEntrySchema = z.looseObject({
|
|
159
|
+
name: z.string().describe('The name in the configuration; also the producer suffix (`critic.<name>`)'),
|
|
160
|
+
type: z.string().describe('`uiclip`, `llm`, `module`, or `not-configured` for a built-in critic that has no configuration yet'),
|
|
161
|
+
kind: z.string().nullable().describe('`model`, `llm`, `heuristic`… as the critic says; null when it is not configured'),
|
|
162
|
+
version: z.string().nullable(),
|
|
163
|
+
status: z.enum(['ready', 'needs-setup', 'misconfigured', 'not-configured']).describe('`needs-setup`: run `atlas critic setup <name>`. `misconfigured`: the configuration or a credential reference cannot be used (see `detail`)'),
|
|
164
|
+
sendsImagesTo: z.string().nullable().describe('Where images are sent: a host, or null when they never leave this machine. A run that sends images to a third party needs confirmation'),
|
|
165
|
+
model: z.string().nullable(),
|
|
166
|
+
detail: z.string().optional().describe('Why it is not ready, and what to do. Never holds a credential')
|
|
167
|
+
});
|
|
168
|
+
/** What `atlas critic list --format json` prints. */
|
|
169
|
+
export const CriticListResultSchema = z.looseObject({
|
|
170
|
+
ok: z.literal(true),
|
|
171
|
+
config: z.string().nullable().describe('The configuration file that was read, or null when there is none'),
|
|
172
|
+
critics: z.array(CriticListEntrySchema)
|
|
173
|
+
});
|
|
174
|
+
/** What `atlas critic setup --format json` prints. */
|
|
175
|
+
export const CriticSetupResultSchema = z.looseObject({
|
|
176
|
+
ok: z.boolean(),
|
|
177
|
+
critic: z.string(),
|
|
178
|
+
type: z.string(),
|
|
179
|
+
details: z.array(z.string()).describe('What was provisioned or checked. A checkpoint is verified against its pinned SHA-256; inference never downloads anything'),
|
|
180
|
+
error: z.string().optional().describe('Why setup failed, with no credential in it. The exit code is then 1')
|
|
181
|
+
});
|
|
182
|
+
/** One thing `atlas critic doctor` checked. */
|
|
183
|
+
export const CriticDoctorCheckSchema = z.looseObject({
|
|
184
|
+
name: z.string().describe('`server`, `token`, `scope`, `catalog`, `spool`, or `<critic>:<aspect>` such as `uiclip:model-file`'),
|
|
185
|
+
status: z.enum(['ok', 'warning', 'problem']).describe('`problem` makes the exit code 1; `warning` does not'),
|
|
186
|
+
detail: z.string(),
|
|
187
|
+
fix: z.string().optional().describe('What to change, when the status is not `ok`')
|
|
188
|
+
});
|
|
189
|
+
/** What `atlas critic doctor --format json` prints. */
|
|
190
|
+
export const CriticDoctorResultSchema = z.looseObject({
|
|
191
|
+
ok: z.boolean().describe('False when any check is a `problem` (the exit code is then 1)'),
|
|
192
|
+
server: z.string().nullable().describe('The server that was checked, with no secrets'),
|
|
193
|
+
role: z.string().nullable().describe('What the token may do: `read`, `critic`, `publish` or `admin`; null when the server could not be asked'),
|
|
194
|
+
open: z.boolean().nullable().describe('True when the server has no tokens, so every caller is an administrator; null when unknown'),
|
|
195
|
+
checks: z.array(CriticDoctorCheckSchema)
|
|
196
|
+
});
|
|
197
|
+
/** What `atlas critic run` did for one critic. */
|
|
198
|
+
export const CriticRunCriticSchema = z.looseObject({
|
|
199
|
+
critic: z.string(),
|
|
200
|
+
version: z.string(),
|
|
201
|
+
sendsImagesTo: z.string().nullable(),
|
|
202
|
+
entries: z.number().int().nonnegative().describe('Entries in the chosen scope'),
|
|
203
|
+
judged: z.number().int().nonnegative(),
|
|
204
|
+
skipped: z.number().int().nonnegative().describe('Entries that already had a current critique of the same identity (critic, version, model, parameters, image hash): no call was made'),
|
|
205
|
+
failed: z.number().int().nonnegative().describe('Entries recorded as failed with a reason code; the next run retries them'),
|
|
206
|
+
waiting: z.number().int().nonnegative().describe('Entries left unjudged because the run stopped'),
|
|
207
|
+
stoppedBy: z.enum(['budget', 'aborted']).optional(),
|
|
208
|
+
failures: z.array(z.looseObject({ entry: z.string(), reason: z.string() })).describe('Each failed entry and its reason code'),
|
|
209
|
+
spent: z.looseObject({ images: z.number(), tokens: z.number(), costUsd: z.number() }),
|
|
210
|
+
published: z.looseObject({ layer: z.string().nullable().describe('The layer id of the critiques, or null when nothing was published (a dry run, or nothing to publish)'), critiques: z.number().int().nonnegative() }),
|
|
211
|
+
gate: z.looseObject({
|
|
212
|
+
mode: z.enum(['report', 'warn', 'gate']),
|
|
213
|
+
exitCode: z.number().int(),
|
|
214
|
+
below: z.number().int().nonnegative().describe('Scores below their state kind\'s threshold'),
|
|
215
|
+
warnings: z.array(z.string()),
|
|
216
|
+
refusal: z.array(z.string()).describe('Why `gate` was refused, when it was')
|
|
217
|
+
}).optional()
|
|
218
|
+
});
|
|
219
|
+
/** What `atlas critic run --format json` prints. A dry run publishes and sends nothing. */
|
|
220
|
+
export const CriticRunResultSchema = z.looseObject({
|
|
221
|
+
ok: z.boolean().describe('False when a critic failed on any entry or a gate was crossed (the exit code is then 1)'),
|
|
222
|
+
dryRun: z.boolean(),
|
|
223
|
+
server: z.string(),
|
|
224
|
+
scope: z.record(z.string(), z.string()).describe('The choices that narrowed the catalog, as given'),
|
|
225
|
+
critics: z.array(CriticRunCriticSchema),
|
|
226
|
+
spool: z.looseObject({
|
|
227
|
+
flushed: z.number().int().nonnegative().describe('Objects from an earlier run that were sent now'),
|
|
228
|
+
spooled: z.number().int().nonnegative().describe('Objects of this run kept locally because the server could not be reached; the next invocation sends them'),
|
|
229
|
+
pending: z.number().int().nonnegative().describe('Objects still waiting in the spool')
|
|
230
|
+
}),
|
|
231
|
+
warnings: z.array(z.string())
|
|
232
|
+
});
|
|
233
|
+
/** What `atlas critic watch` did for one critic. */
|
|
234
|
+
export const CriticWatchCriticSchema = z.looseObject({
|
|
235
|
+
critic: z.string(),
|
|
236
|
+
version: z.string(),
|
|
237
|
+
sendsImagesTo: z.string().nullable(),
|
|
238
|
+
entries: z.number().int().nonnegative().describe('Entries handed over by the catalog, counting a changed picture again'),
|
|
239
|
+
judged: z.number().int().nonnegative(),
|
|
240
|
+
skipped: z.number().int().nonnegative().describe('Entries that already had a current critique of the same identity: no call was made'),
|
|
241
|
+
failed: z.number().int().nonnegative().describe('Entries recorded as failed with a reason code; the next batch that brings them, or the next run, retries them'),
|
|
242
|
+
failures: z.array(z.looseObject({ entry: z.string(), reason: z.string() })),
|
|
243
|
+
spent: z.looseObject({ images: z.number(), tokens: z.number(), costUsd: z.number() }),
|
|
244
|
+
published: z.looseObject({ layers: z.number().int().nonnegative(), critiques: z.number().int().nonnegative() })
|
|
245
|
+
});
|
|
246
|
+
/** What `atlas critic watch --format json` prints, once, when the watch ends. */
|
|
247
|
+
export const CriticWatchResultSchema = z.looseObject({
|
|
248
|
+
ok: z.boolean().describe('False when a critic failed on any entry (the exit code is then 1)'),
|
|
249
|
+
dryRun: z.boolean(),
|
|
250
|
+
server: z.string(),
|
|
251
|
+
scope: z.record(z.string(), z.string()).describe('The choices that narrowed the catalog, as given'),
|
|
252
|
+
stoppedBy: z.enum(['interrupted', 'max-batches', 'budget', 'dry-run']).describe('Why the watch ended: Ctrl-C or a signal, `--max-batches`, a budget for the whole watch, or a dry run (one read)'),
|
|
253
|
+
batches: z.number().int().nonnegative().describe('How many times the catalog handed over something new, the catch-up on connecting included'),
|
|
254
|
+
version: z.string().nullable().describe('The report version the watch had reached: resume with `--since` this. Null when nothing was read'),
|
|
255
|
+
critics: z.array(CriticWatchCriticSchema),
|
|
256
|
+
spool: z.looseObject({ flushed: z.number().int().nonnegative(), spooled: z.number().int().nonnegative(), pending: z.number().int().nonnegative() }).describe('As for `critic-run-result`'),
|
|
257
|
+
warnings: z.array(z.string())
|
|
258
|
+
});
|
|
259
|
+
/** What reviewers decided about one opinion, as `atlas critic review` reports it. */
|
|
260
|
+
export const CriticMarkStandingSchema = z.looseObject({
|
|
261
|
+
state: z.enum(['accepted', 'dismissed', 'disputed', 'unmarked']).describe('`disputed`: reviewers disagree. `unmarked`: nobody has marked it at the current picture'),
|
|
262
|
+
accepted: z.array(z.string()).describe('Reviewers who accepted it'),
|
|
263
|
+
dismissed: z.array(z.string()).describe('Reviewers who dismissed it'),
|
|
264
|
+
earlier: z.number().int().nonnegative().describe('Marks made about an earlier picture, which count for nothing now'),
|
|
265
|
+
notes: z.array(z.string())
|
|
266
|
+
});
|
|
267
|
+
/** What `atlas critic review --format json` prints. */
|
|
268
|
+
export const CriticReviewResultSchema = z.looseObject({
|
|
269
|
+
ok: z.literal(true),
|
|
270
|
+
server: z.string(),
|
|
271
|
+
critic: z.string().nullable().describe('The critic whose opinions are listed; null when none has judged anything'),
|
|
272
|
+
critics: z.array(z.string()).describe('Every critic with an opinion, most opinions first'),
|
|
273
|
+
reviewer: z.string().describe('The name marks are recorded under'),
|
|
274
|
+
rankedBy: z.enum(['standing-within-kind', 'score']),
|
|
275
|
+
summary: z.looseObject({ ranked: z.number().int().nonnegative(), unranked: z.number().int().nonnegative().describe('Judged at an earlier picture, or the critic could not judge'), unjudged: z.number().int().nonnegative() }),
|
|
276
|
+
entries: z.array(z.looseObject({
|
|
277
|
+
rank: z.number().int().positive().nullable().describe('Null for an entry that is not ranked'),
|
|
278
|
+
key: z.string(), title: z.string(), state: z.string(), imageSha256: z.string(),
|
|
279
|
+
score: z.number().nullable(), percentile: z.number().nullable().describe('Standing among the scores of the same state kind, 0..100, higher is better'), label: z.string().nullable(),
|
|
280
|
+
currency: z.enum(['current', 'earlier-image']),
|
|
281
|
+
failure: z.string().optional().describe('The reason code, when the critic could not judge it'),
|
|
282
|
+
marks: CriticMarkStandingSchema.describe('The marks of the whole opinion'),
|
|
283
|
+
findings: z.array(z.looseObject({
|
|
284
|
+
id: z.string().nullable().describe('A finding without an id cannot be marked on its own'),
|
|
285
|
+
category: z.string().nullable(), severity: z.string().nullable(),
|
|
286
|
+
region: z.looseObject({ unit: z.string(), x: z.number(), y: z.number(), w: z.number(), h: z.number() }).optional(),
|
|
287
|
+
rationale: z.string().nullable(), suggestion: z.string().nullable(),
|
|
288
|
+
marks: CriticMarkStandingSchema.nullable()
|
|
289
|
+
}))
|
|
290
|
+
})),
|
|
291
|
+
marked: z.looseObject({
|
|
292
|
+
dryRun: z.boolean(), reviewer: z.string(),
|
|
293
|
+
layer: z.string().nullable().describe('The layer the marks were published as; null on a dry run or when nothing was marked'),
|
|
294
|
+
marks: z.array(z.looseObject({ entry: z.string(), critic: z.string(), finding: z.string().nullable().describe('Null: the whole opinion'), decision: z.enum(['accept', 'dismiss']) }))
|
|
295
|
+
}).optional().describe('Present when this call recorded (or, with --dry-run, would record) marks')
|
|
296
|
+
});
|
|
297
|
+
/** What `atlas critic calibrate --format json` prints. A dry run measures nothing and writes nothing. */
|
|
298
|
+
export const CriticCalibrateResultSchema = z.looseObject({
|
|
299
|
+
ok: z.literal(true),
|
|
300
|
+
dryRun: z.boolean(),
|
|
301
|
+
critic: z.looseObject({ name: z.string(), version: z.string().nullable() }),
|
|
302
|
+
sendsImagesTo: z.string().nullable().describe('Where the labelled pictures were (or, on a dry run, would be) sent; null when they stay on this machine or only marks were used'),
|
|
303
|
+
examples: z.looseObject({
|
|
304
|
+
files: z.number().int().nonnegative().describe('Examples from labelled pictures (on a dry run, the pictures found)'),
|
|
305
|
+
marks: z.number().int().nonnegative().describe('Examples from reviewers\' whole-opinion marks'),
|
|
306
|
+
used: z.number().int().nonnegative(),
|
|
307
|
+
skipped: z.array(z.looseObject({ source: z.string(), reason: z.string() })).describe('Examples left out, and why: no score, a critic failure, disputed marks, a budget')
|
|
308
|
+
}),
|
|
309
|
+
calibration: z.looseObject({
|
|
310
|
+
schema: z.literal(1),
|
|
311
|
+
critic: z.looseObject({ name: z.string(), version: z.string(), model: z.looseObject({ id: z.string(), provider: z.string().optional() }).optional(), parameters: z.record(z.string(), z.unknown()).optional() }),
|
|
312
|
+
createdAtMs: z.number().optional(),
|
|
313
|
+
kinds: z.array(z.looseObject({
|
|
314
|
+
stateKind: z.string(),
|
|
315
|
+
threshold: z.number().nullable().describe('Scores below this are flagged as needing polish; null when the examples cannot tell (one class is missing)'),
|
|
316
|
+
samples: z.number().int().nonnegative(), positives: z.number().int().nonnegative(), negatives: z.number().int().nonnegative(),
|
|
317
|
+
precision: z.number().nullable(), recall: z.number().nullable(),
|
|
318
|
+
inSample: z.boolean().describe('True when precision and recall are measured on the examples the threshold was chosen on, which flatters them'),
|
|
319
|
+
evaluatedOn: z.enum(['fit', 'held-out'])
|
|
320
|
+
}))
|
|
321
|
+
}).nullable().describe('The calibration `atlas critic run --calibration` reads; null on a dry run'),
|
|
322
|
+
written: z.string().nullable().describe('The file it was written to (`--out`), or null'),
|
|
323
|
+
gate: z.looseObject({ eligible: z.boolean().describe('Whether `--mode gate` would accept this calibration'), reasons: z.array(z.string()).describe('What is missing, per state kind, when it would not') }).nullable(),
|
|
324
|
+
warnings: z.array(z.string())
|
|
325
|
+
});
|
|
326
|
+
/** The coverage of one state in one profile, as the UI catalog words it. */
|
|
327
|
+
const UiCoverageSchema = z.enum(['captured', 'failed', 'capturing', 'skipped', 'missing']);
|
|
328
|
+
/** What `atlas ui-catalog plan --format json` and `atlas ui-catalog publish --format json` print: the one layer that was written. */
|
|
329
|
+
export const UiCatalogPublishResultSchema = z.looseObject({
|
|
330
|
+
ok: z.literal(true).describe('True: the layer was written. A state whose picture could not be read is published as `failed` and named in `warnings`; it does not fail the command'),
|
|
331
|
+
store: z.string().describe('Where it was written: a folder, `s3://…` or a server URL'),
|
|
332
|
+
layer: z.looseObject({
|
|
333
|
+
id: z.string().describe('The layer id: 64 hex digits, the SHA-256 of its content. Publishing the same input again gives the same id'),
|
|
334
|
+
kind: z.enum(['ui-state', 'ui-plan']).describe('`ui-state`: captures (`publish`). `ui-plan`: the expected set (`plan`)'),
|
|
335
|
+
producer: z.string(),
|
|
336
|
+
environment: z.string().nullable(),
|
|
337
|
+
parameters: z.record(z.string(), z.string()).describe('The rest of the profile, such as `{ "viewport": "390x844" }`'),
|
|
338
|
+
states: z.number().int().nonnegative().describe('States in the layer'),
|
|
339
|
+
captured: z.number().int().nonnegative().describe('For `publish`: states that were captured (`passed`). Zero for a plan'),
|
|
340
|
+
failed: z.number().int().nonnegative().describe('For `publish`: states recorded as failed, including those whose picture could not be read'),
|
|
341
|
+
pictures: z.number().int().nonnegative().describe('Pictures stored in the layer'),
|
|
342
|
+
pictureBytes: z.number().int().nonnegative(),
|
|
343
|
+
shard: z.string().nullable().describe('The `--shard` label, which describes the layer and never splits its slot')
|
|
344
|
+
}),
|
|
345
|
+
warnings: z.array(z.string()).describe('What was skipped and why, such as a picture file that could not be read. Never fatal')
|
|
346
|
+
});
|
|
347
|
+
/** One thing that keeps a UI catalog from being complete and good. */
|
|
348
|
+
export const UiCatalogProblemSchema = z.looseObject({
|
|
349
|
+
kind: z.enum(['failed', 'missing', 'capturing', 'unavailable', 'unreadable', 'empty']).describe('`failed`: a capture was attempted and failed. `missing`: a plan expects it and nothing was captured. `capturing`: still being taken, so not known. `unavailable`: a backend could not be read. `unreadable`: a layer could not be read. `empty`: the catalog holds no state at all'),
|
|
350
|
+
state: z.string().optional().describe('The state id, for `failed`, `missing` and `capturing`'),
|
|
351
|
+
profile: z.string().optional().describe('The profile label, for `failed`, `missing` and `capturing`'),
|
|
352
|
+
detail: z.string().optional().describe('Why, when the producer said')
|
|
353
|
+
});
|
|
354
|
+
/** What `atlas ui-catalog status --format json` prints. */
|
|
355
|
+
export const UiCatalogStatusSchema = z.looseObject({
|
|
356
|
+
ok: z.boolean().describe('True only when the catalog holds a state and has no problem: nothing failed, nothing expected is missing, nothing is still being captured, and every backend and layer was read (otherwise the exit code is 1). Skipped cells are counted, not problems'),
|
|
357
|
+
summary: z.looseObject({
|
|
358
|
+
states: z.number().int().nonnegative(),
|
|
359
|
+
profiles: z.number().int().nonnegative(),
|
|
360
|
+
cells: z.number().int().nonnegative().describe('States counted once per profile'),
|
|
361
|
+
coverage: z.record(UiCoverageSchema, z.number().int().nonnegative()).describe('Cells by coverage'),
|
|
362
|
+
undeclared: z.number().int().nonnegative().describe('Captured cells no plan expects; zero when no plan exists'),
|
|
363
|
+
planned: z.boolean().describe('Whether any plan was published. Without one, `missing` is always zero because what should exist is unknown'),
|
|
364
|
+
surfaces: z.number().int().nonnegative(),
|
|
365
|
+
uncoveredSurfaces: z.number().int().nonnegative()
|
|
366
|
+
}),
|
|
367
|
+
profiles: z.array(z.looseObject({ key: z.string(), label: z.string() })),
|
|
368
|
+
problems: z.array(UiCatalogProblemSchema),
|
|
369
|
+
sources: z.looseObject({ layers: z.number().int().nonnegative(), backends: z.array(z.looseObject({ id: z.string(), label: z.string(), available: z.boolean() })), notices: z.array(z.string()) })
|
|
370
|
+
});
|
|
371
|
+
/** One side of `atlas ui-catalog diff`. */
|
|
372
|
+
const UiCatalogCommitSchema = z.looseObject({ sha: z.string(), states: z.number().int().nonnegative(), layers: z.number().int().nonnegative() });
|
|
373
|
+
/** What `atlas ui-catalog diff --format json` prints. */
|
|
374
|
+
export const UiCatalogDiffResultSchema = z.looseObject({
|
|
375
|
+
ok: z.boolean().describe('False when a layer or backend could not be read (the comparison may be wrong), or, with `--fail-on-change`, when anything differs. Otherwise true: a difference is not a failure by itself'),
|
|
376
|
+
from: UiCatalogCommitSchema,
|
|
377
|
+
to: UiCatalogCommitSchema,
|
|
378
|
+
summary: z.looseObject({
|
|
379
|
+
coverage: z.number().int().nonnegative().describe('Pairs that stand differently (captured against failed, for example)'),
|
|
380
|
+
picture: z.number().int().nonnegative().describe('Both captured, and the stored bytes differ. Not a measure of how many pixels'),
|
|
381
|
+
unverified: z.number().int().nonnegative().describe('Both captured, but a side has no content hash, so whether the picture changed is unknown'),
|
|
382
|
+
added: z.number().int().nonnegative().describe('Only `to` has it'),
|
|
383
|
+
removed: z.number().int().nonnegative().describe('Only `from` has it. Not a regression: the other side may not have run it'),
|
|
384
|
+
unchanged: z.number().int().nonnegative(),
|
|
385
|
+
total: z.number().int().nonnegative()
|
|
386
|
+
}),
|
|
387
|
+
changes: z.array(z.looseObject({
|
|
388
|
+
state: z.string(), title: z.string(), profile: z.string(),
|
|
389
|
+
change: z.enum(['coverage', 'picture', 'unverified', 'added', 'removed']),
|
|
390
|
+
from: UiCoverageSchema.nullable(), to: UiCoverageSchema.nullable()
|
|
391
|
+
})).describe('Every pair that is not unchanged'),
|
|
392
|
+
notices: z.array(z.string()).describe('What could not be read. Any makes `ok` false: an unreadable layer is unknown, never unchanged')
|
|
393
|
+
});
|
|
394
|
+
/** What `atlas ui-catalog export --format json` prints. */
|
|
395
|
+
export const UiCatalogExportResultSchema = z.looseObject({
|
|
396
|
+
ok: z.literal(true),
|
|
397
|
+
out: z.string().describe('The HTML file that was written'),
|
|
398
|
+
bytes: z.number().int().nonnegative().describe('Its size'),
|
|
399
|
+
states: z.number().int().nonnegative(),
|
|
400
|
+
cells: z.number().int().nonnegative(),
|
|
401
|
+
pictures: z.looseObject({
|
|
402
|
+
embedded: z.number().int().nonnegative(),
|
|
403
|
+
embeddedBytes: z.number().int().nonnegative(),
|
|
404
|
+
left: z.number().int().nonnegative().describe('Pictures not embedded (over the size limit, not stored, not an image, or `--images none`); each is listed on the page with the reason')
|
|
405
|
+
}),
|
|
406
|
+
manifest: z.string().nullable().describe('The JSON manifest written beside it with `--manifest`, or null'),
|
|
407
|
+
warnings: z.array(z.string()).describe('Backends and layers that could not be read, and pictures left out. The page says so too')
|
|
408
|
+
});
|
|
157
409
|
/**
|
|
158
410
|
* JSON command contracts, shared by schema publication, agent documentation and executable command checks.
|
|
159
411
|
* A new result needs an entry here and a runtime scenario in the documentation contract tests.
|
|
@@ -166,5 +418,17 @@ export const CLI_RESULTS = {
|
|
|
166
418
|
doctor: { command: 'atlas report doctor', schemaName: 'doctor-result', schema: DoctorResultSchema },
|
|
167
419
|
status: { command: 'atlas report status', schemaName: 'report-status', schema: ReportStatusSchema },
|
|
168
420
|
verifyRecordings: { command: 'atlas report verify-recordings', schemaName: 'verify-recordings-result', schema: VerifyRecordingsResultSchema },
|
|
421
|
+
criticList: { command: 'atlas critic list', schemaName: 'critic-list-result', schema: CriticListResultSchema },
|
|
422
|
+
criticSetup: { command: 'atlas critic setup', schemaName: 'critic-setup-result', schema: CriticSetupResultSchema },
|
|
423
|
+
criticDoctor: { command: 'atlas critic doctor', schemaName: 'critic-doctor-result', schema: CriticDoctorResultSchema },
|
|
424
|
+
criticRun: { command: 'atlas critic run', schemaName: 'critic-run-result', schema: CriticRunResultSchema },
|
|
425
|
+
criticWatch: { command: 'atlas critic watch', schemaName: 'critic-watch-result', schema: CriticWatchResultSchema },
|
|
426
|
+
criticReview: { command: 'atlas critic review', schemaName: 'critic-review-result', schema: CriticReviewResultSchema },
|
|
427
|
+
criticCalibrate: { command: 'atlas critic calibrate', schemaName: 'critic-calibrate-result', schema: CriticCalibrateResultSchema },
|
|
428
|
+
uiCatalogPlan: { command: 'atlas ui-catalog plan', schemaName: 'ui-catalog-publish-result', schema: UiCatalogPublishResultSchema },
|
|
429
|
+
uiCatalogPublish: { command: 'atlas ui-catalog publish', schemaName: 'ui-catalog-publish-result', schema: UiCatalogPublishResultSchema },
|
|
430
|
+
uiCatalogStatus: { command: 'atlas ui-catalog status', schemaName: 'ui-catalog-status', schema: UiCatalogStatusSchema },
|
|
431
|
+
uiCatalogDiff: { command: 'atlas ui-catalog diff', schemaName: 'ui-catalog-diff-result', schema: UiCatalogDiffResultSchema },
|
|
432
|
+
uiCatalogExport: { command: 'atlas ui-catalog export', schemaName: 'ui-catalog-export-result', schema: UiCatalogExportResultSchema },
|
|
169
433
|
error: { command: 'a command that was wrong (standard error)', schemaName: 'command-error', schema: CommandErrorSchema }
|
|
170
434
|
};
|
|
@@ -142,3 +142,34 @@ export declare const CritiqueSchema: z.ZodObject<{
|
|
|
142
142
|
export type Critique = z.output<typeof CritiqueSchema>;
|
|
143
143
|
/** One finding of a critique. */
|
|
144
144
|
export type CritiqueFinding = z.output<typeof CritiqueFindingSchema>;
|
|
145
|
+
/** The key under which a reviewer's mark of a critique is stored in a check's `extensions`. */
|
|
146
|
+
export declare const CRITIQUE_MARK_EXTENSION = "atlas.critique-mark";
|
|
147
|
+
/** The version of the `atlas.critique-mark` extension a reader of this release understands. A mark of another version is unknown, not wrong. */
|
|
148
|
+
export declare const CRITIQUE_MARK_SCHEMA_VERSION = 1;
|
|
149
|
+
/** The prefix of the `producer` of a layer of marks: `critic.review.<reviewer>`. It is within what a critic-role token may publish. */
|
|
150
|
+
export declare const CRITIQUE_MARK_PRODUCER_PREFIX = "critic.review.";
|
|
151
|
+
/**
|
|
152
|
+
* The `atlas.critique-mark` extension, version 1: a person's verdict on a critic's opinion. `accept` says the opinion was right
|
|
153
|
+
* (the finding is valid, the UI needs polish); `dismiss` says it was not. A mark names the entry and the exact picture it was made
|
|
154
|
+
* about, the critic whose opinion it marks, and optionally one finding of it; without `finding` it marks the critique as a whole.
|
|
155
|
+
* Like the critique it is evidence in a part of kind `critique`: it is never counted and never changes a status, a slot or the gate.
|
|
156
|
+
* A newer mark of the same reviewer for the same subject replaces an older one. Emitted as `critique-mark.schema.json`.
|
|
157
|
+
*/
|
|
158
|
+
export declare const CritiqueMarkSchema: z.ZodObject<{
|
|
159
|
+
schema: z.ZodNumber;
|
|
160
|
+
subject: z.ZodObject<{
|
|
161
|
+
entryKey: z.ZodString;
|
|
162
|
+
imageSha256: z.ZodString;
|
|
163
|
+
}, z.core.$loose>;
|
|
164
|
+
critic: z.ZodObject<{
|
|
165
|
+
name: z.ZodString;
|
|
166
|
+
version: z.ZodOptional<z.ZodString>;
|
|
167
|
+
}, z.core.$loose>;
|
|
168
|
+
finding: z.ZodOptional<z.ZodString>;
|
|
169
|
+
decision: z.ZodString;
|
|
170
|
+
reviewer: z.ZodOptional<z.ZodString>;
|
|
171
|
+
note: z.ZodOptional<z.ZodString>;
|
|
172
|
+
layer: z.ZodOptional<z.ZodString>;
|
|
173
|
+
}, z.core.$loose>;
|
|
174
|
+
/** A mark as stored in a check's `extensions["atlas.critique-mark"]`. */
|
|
175
|
+
export type CritiqueMark = z.output<typeof CritiqueMarkSchema>;
|
|
@@ -94,3 +94,29 @@ export const CritiqueSchema = z.looseObject({
|
|
|
94
94
|
costUsd: z.number().nonnegative().optional()
|
|
95
95
|
}).optional()
|
|
96
96
|
});
|
|
97
|
+
/** The key under which a reviewer's mark of a critique is stored in a check's `extensions`. */
|
|
98
|
+
export const CRITIQUE_MARK_EXTENSION = 'atlas.critique-mark';
|
|
99
|
+
/** The version of the `atlas.critique-mark` extension a reader of this release understands. A mark of another version is unknown, not wrong. */
|
|
100
|
+
export const CRITIQUE_MARK_SCHEMA_VERSION = 1;
|
|
101
|
+
/** The prefix of the `producer` of a layer of marks: `critic.review.<reviewer>`. It is within what a critic-role token may publish. */
|
|
102
|
+
export const CRITIQUE_MARK_PRODUCER_PREFIX = 'critic.review.';
|
|
103
|
+
/**
|
|
104
|
+
* The `atlas.critique-mark` extension, version 1: a person's verdict on a critic's opinion. `accept` says the opinion was right
|
|
105
|
+
* (the finding is valid, the UI needs polish); `dismiss` says it was not. A mark names the entry and the exact picture it was made
|
|
106
|
+
* about, the critic whose opinion it marks, and optionally one finding of it; without `finding` it marks the critique as a whole.
|
|
107
|
+
* Like the critique it is evidence in a part of kind `critique`: it is never counted and never changes a status, a slot or the gate.
|
|
108
|
+
* A newer mark of the same reviewer for the same subject replaces an older one. Emitted as `critique-mark.schema.json`.
|
|
109
|
+
*/
|
|
110
|
+
export const CritiqueMarkSchema = z.looseObject({
|
|
111
|
+
schema: z.number().int().positive().describe('The version of this extension. 1'),
|
|
112
|
+
subject: z.looseObject({
|
|
113
|
+
entryKey: z.string().min(1).describe('The catalog entry the marked critique is about'),
|
|
114
|
+
imageSha256: z.string().min(1).describe('The hash of the picture the marked critique judged, so a mark about an earlier picture is shown as that')
|
|
115
|
+
}),
|
|
116
|
+
critic: z.looseObject({ name: z.string().min(1), version: z.string().min(1).optional() }).describe('The critic whose opinion is marked'),
|
|
117
|
+
finding: z.string().min(1).optional().describe('The `id` of the marked finding. Absent: the critique as a whole'),
|
|
118
|
+
decision: z.string().min(1).describe('Open vocabulary; this release understands `accept` (the opinion is right) and `dismiss` (it is not). Anything else is shown as written and counts as neither'),
|
|
119
|
+
reviewer: z.string().min(1).optional().describe('Who marked it: a name the reviewer chose, never an account'),
|
|
120
|
+
note: z.string().optional().describe('Why, in the reviewer\'s words'),
|
|
121
|
+
layer: z.string().optional().describe('The layer of the critique that was marked, for tracing')
|
|
122
|
+
});
|
|
@@ -16,8 +16,8 @@ import { z } from 'zod/v4';
|
|
|
16
16
|
* anything) or `admin` (also remove scratch objects).
|
|
17
17
|
*/
|
|
18
18
|
export declare const ServerRoleSchema: z.ZodEnum<{
|
|
19
|
-
read: "read";
|
|
20
19
|
critic: "critic";
|
|
20
|
+
read: "read";
|
|
21
21
|
publish: "publish";
|
|
22
22
|
admin: "admin";
|
|
23
23
|
}>;
|
|
@@ -30,8 +30,8 @@ export type ServerRole = z.infer<typeof ServerRoleSchema>;
|
|
|
30
30
|
export declare const ServerTokenSchema: z.ZodObject<{
|
|
31
31
|
name: z.ZodString;
|
|
32
32
|
role: z.ZodEnum<{
|
|
33
|
-
read: "read";
|
|
34
33
|
critic: "critic";
|
|
34
|
+
read: "read";
|
|
35
35
|
publish: "publish";
|
|
36
36
|
admin: "admin";
|
|
37
37
|
}>;
|
|
@@ -49,8 +49,8 @@ export declare const ServerConfigSchema: z.ZodObject<{
|
|
|
49
49
|
tokens: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
50
50
|
name: z.ZodString;
|
|
51
51
|
role: z.ZodEnum<{
|
|
52
|
-
read: "read";
|
|
53
52
|
critic: "critic";
|
|
53
|
+
read: "read";
|
|
54
54
|
publish: "publish";
|
|
55
55
|
admin: "admin";
|
|
56
56
|
}>;
|
package/dist/server/src/app.js
CHANGED
|
@@ -29,6 +29,7 @@ import { latestSpecSet, readSpecBody } from '../../reports/src/spec-set.js';
|
|
|
29
29
|
import { catalogHealth, healthMarkdown, parseRootMapping, parseRootMappings } from '../../reports/src/catalog-health.js';
|
|
30
30
|
import { parseArtifactFilter } from '../../reports/src/artifact-query.js';
|
|
31
31
|
import { filterUiCatalog, pageUiCatalog, parseUiCatalogQuery, uiCatalog, uiCatalogMarkdown } from '../../reports/src/ui-catalog.js';
|
|
32
|
+
import { catalogEntries, pageCatalogEntries, parseCatalogEntriesQuery, CATALOG_ENTRIES_VERSION } from '../../reports/src/ui-catalog-entries.js';
|
|
32
33
|
import { parseReliabilityScope, reliability, reliabilityMarkdown } from '../../reports/src/reliability.js';
|
|
33
34
|
import { artifactList, artifactsMarkdown, catalogMarkdown, catalogModel, historyMarkdown, parseTestQuery, runDetail, runList, runMarkdown, runsMarkdown, testCatalog, testHistory } from '../../reports/src/test-catalog.js';
|
|
34
35
|
import { llmsTxt } from './llms.js';
|
|
@@ -261,7 +262,7 @@ function createProjectApplication(options) {
|
|
|
261
262
|
name: 'atlas', version: options.version ?? null, role: identity?.role ?? 'read', open: auth.open,
|
|
262
263
|
write: options.write, ready: index.ready, viewers: events.count, time: now(),
|
|
263
264
|
backends: snapshot.backends.map(({ id, label, available }) => ({ id, label, available, write: id === options.write })),
|
|
264
|
-
...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true, artifactSearch: true, reviewCollections: true, recordingVerification: true }
|
|
265
|
+
...(({ backends: _unused, ...rest }) => rest)(index.stats()), capabilities: { events: true, ingest: true, store: true, checks: true, status: true, effective: true, live: true, tests: true, artifactTags: true, artifactSearch: true, reviewCollections: true, recordingVerification: true, catalogEntries: true }
|
|
265
266
|
});
|
|
266
267
|
});
|
|
267
268
|
app.get('/api/live', async (c) => c.json({ runs: await live.list() }, 200, { 'cache-control': 'no-store' }));
|
|
@@ -420,6 +421,7 @@ function createProjectApplication(options) {
|
|
|
420
421
|
}
|
|
421
422
|
return model.value;
|
|
422
423
|
};
|
|
424
|
+
let entriesModel;
|
|
423
425
|
let uiModel;
|
|
424
426
|
const wantsMarkdown = (c) => {
|
|
425
427
|
const format = c.req.query('format');
|
|
@@ -522,6 +524,31 @@ function createProjectApplication(options) {
|
|
|
522
524
|
const document = () => pageUiCatalog(filterUiCatalog(whole, parseUiCatalogQuery(search)), { limit: number('limit', 200), offset: number('offset', 0) });
|
|
523
525
|
return answer(c, snapshot.version, document, () => uiCatalogMarkdown(document()), `ui-catalog|${search.toString()}|${wantsMarkdown(c)}`);
|
|
524
526
|
});
|
|
527
|
+
app.get('/api/reports/ui-catalog/entries', async (c) => {
|
|
528
|
+
if (!index.ready)
|
|
529
|
+
return notReady(c);
|
|
530
|
+
const snapshot = index.snapshot();
|
|
531
|
+
const search = new URL(c.req.url).searchParams;
|
|
532
|
+
const query = parseCatalogEntriesQuery(search);
|
|
533
|
+
// Entries are a function of the report, so they are listed once per version and narrowed per question.
|
|
534
|
+
if (entriesModel?.version !== snapshot.version)
|
|
535
|
+
entriesModel = { version: snapshot.version, value: catalogEntries(snapshot.layers) };
|
|
536
|
+
const whole = entriesModel.value;
|
|
537
|
+
const changed = query.since === undefined ? null : index.changedSince(query.since);
|
|
538
|
+
let page;
|
|
539
|
+
try {
|
|
540
|
+
page = pageCatalogEntries(whole.entries, query, changed);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
return problemResponse(400, error instanceof Error ? error.message : 'Bad cursor.');
|
|
544
|
+
}
|
|
545
|
+
const document = () => ({
|
|
546
|
+
schemaVersion: CATALOG_ENTRIES_VERSION, version: snapshot.version, ...page,
|
|
547
|
+
...(query.since === undefined ? {} : { since: { requested: query.since, honored: changed !== null } }),
|
|
548
|
+
unjudgeable: whole.unjudgeable, sources: { backends: snapshot.backends, notices: snapshot.notices }
|
|
549
|
+
});
|
|
550
|
+
return answer(c, snapshot.version, document, () => `${page.entries.map(entry => `- ${entry.key} (${entry.state}): ${entry.title}`).join('\n')}\n`, `ui-entries|${search.toString()}|${wantsMarkdown(c)}`);
|
|
551
|
+
});
|
|
525
552
|
app.get('/api/reports/runs/detail', async (c) => {
|
|
526
553
|
if (!index.ready)
|
|
527
554
|
return notReady(c);
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { z } from 'zod/v4';
|
|
16
16
|
import { ProjectsSchema } from '../../schema/src/project.js';
|
|
17
|
-
import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, TestReliabilitySchema, RunListSchema, RunDetailSchema, ArtifactListSchema, UiCatalogSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema, ReviewCollectionRequestSchema, ReviewCollectionSchema, ReviewCollectionListSchema, ReviewCollectionDetailSchema, ReviewNoteInputSchema, ReviewNoteSchema, ReviewManifestSchema, RecordingVerificationSchema, RecordingVerifyRequestSchema } from '../../schema/src/api.js';
|
|
17
|
+
import { EffectiveDeltaSchema, EffectiveEvidenceSchema, EvidencePageSchema, HealthSchema, LayerRowsSchema, CheckDetailSchema, OutlineSchema, ProblemSchema, ReadySchema, ServerInfoSchema, StoreListingSchema, StoreWriteResultSchema, ChecksResultSchema, HelloEventSchema, ReportEventSchema, PingEventSchema, TestCatalogSchema, CatalogHealthSchema, TestHistorySchema, TestReliabilitySchema, RunListSchema, RunDetailSchema, ArtifactListSchema, UiCatalogSchema, CatalogEntriesSchema, ArtifactTagEditSchema, ArtifactTagEntrySchema, ArtifactTagsSchema, ReviewCollectionRequestSchema, ReviewCollectionSchema, ReviewCollectionListSchema, ReviewCollectionDetailSchema, ReviewNoteInputSchema, ReviewNoteSchema, ReviewManifestSchema, RecordingVerificationSchema, RecordingVerifyRequestSchema } from '../../schema/src/api.js';
|
|
18
18
|
import { LiveRunSchema, LiveRunStartSchema, LiveRunUpdateSchema, ServerEditableSettingsSchema, ServerSettingsSnapshotSchema, GcPolicySchema, GcSnapshotSchema, GcRunResultSchema, AtlasChangeSchema, PublishChecksSchema, ReportSourcesSchema, ReportStatusSchema, RunsIndexSchema, SpecIndexSchema } from '../../schema/src/index.js';
|
|
19
19
|
/** The version of the API described, `major.minor.patch`: the minor grows when an operation or field is added, the major never within `/api`. */
|
|
20
20
|
export const API_VERSION = '1.7.0';
|
|
@@ -39,7 +39,7 @@ const COMPONENTS = {
|
|
|
39
39
|
ArtifactTagEdit: [ArtifactTagEditSchema, 'input'], ArtifactTagEntry: [ArtifactTagEntrySchema, 'output'], ArtifactTags: [ArtifactTagsSchema, 'output'],
|
|
40
40
|
ReviewCollectionRequest: [ReviewCollectionRequestSchema, 'input'], ReviewCollection: [ReviewCollectionSchema, 'output'], ReviewCollectionList: [ReviewCollectionListSchema, 'output'], ReviewCollectionDetail: [ReviewCollectionDetailSchema, 'output'], ReviewNoteInput: [ReviewNoteInputSchema, 'input'], ReviewNote: [ReviewNoteSchema, 'output'], ReviewManifest: [ReviewManifestSchema, 'output'],
|
|
41
41
|
RecordingVerification: [RecordingVerificationSchema, 'output'], RecordingVerifyRequest: [RecordingVerifyRequestSchema, 'input'],
|
|
42
|
-
TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], TestReliability: [TestReliabilitySchema, 'output'], RunList: [RunListSchema, 'output'], ArtifactList: [ArtifactListSchema, 'output'], UiCatalog: [UiCatalogSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
|
|
42
|
+
TestCatalog: [TestCatalogSchema, 'output'], CatalogHealth: [CatalogHealthSchema, 'output'], TestHistory: [TestHistorySchema, 'output'], TestReliability: [TestReliabilitySchema, 'output'], RunList: [RunListSchema, 'output'], ArtifactList: [ArtifactListSchema, 'output'], UiCatalog: [UiCatalogSchema, 'output'], CatalogEntries: [CatalogEntriesSchema, 'output'], RunDetail: [RunDetailSchema, 'output'],
|
|
43
43
|
SpecIndex: [SpecIndexSchema, 'output'], ReportSources: [ReportSourcesSchema, 'output'], RunsIndex: [RunsIndexSchema, 'output'],
|
|
44
44
|
RunEvent: [LiveRunSchema, 'output'], HelloEvent: [HelloEventSchema, 'output'], ReportEvent: [ReportEventSchema, 'output'], AtlasChangeEvent: [AtlasChangeSchema, 'output'], PingEvent: [PingEventSchema, 'output']
|
|
45
45
|
};
|
|
@@ -503,6 +503,23 @@ const BASE_OPERATIONS = [
|
|
|
503
503
|
],
|
|
504
504
|
responses: { '200': json('The catalog.', 'UiCatalog'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '401': UNAUTHORIZED, '503': NOT_READY }
|
|
505
505
|
},
|
|
506
|
+
{
|
|
507
|
+
route: 'GET /api/reports/ui-catalog/entries', path: '/api/reports/ui-catalog/entries', operationId: 'getUiCatalogEntries', tag: 'Report', role: 'read',
|
|
508
|
+
summary: 'The UI catalog as a list a critic iterates: captured states with their picture hash and critiques',
|
|
509
|
+
description: 'The captured states of the UI catalog, one entry per state per profile that has a picture stored in its layer, in key order, for a process that reviews them (a critic: see the critics guide). Page with `limit` and the opaque `cursor` of the previous page (`nextCursor`): the cursor names the last key, so each entry is seen exactly once and in the same order. An entry carries `image.sha256` (the hash a critique is anchored to) and `image.path` (fetch the bytes from `/api/reports` + path, through the same safe attachment path as every file), its `state` kind, and `critiques`: those already made, newest first, each marked `current` or `earlier-image`. Keep the `version` of the first page; later ask `since=<that version>` to read only the entries whose layer was added or changed since. A `since` the server does not remember is answered with the whole catalog and `since.honored: false`. `unjudgedBy=<critic>@<version>` keeps entries with no current critique with a verdict from that critic (a failed critique is not a judgement). To be told that something changed, hold `/api/events`: a `report` event carries the new version; then read `since`. A critic-role token may read this.',
|
|
510
|
+
parameters: [
|
|
511
|
+
{ name: 'limit', in: 'query', description: 'At most this many entries per page (default 100, at most 1000).', schema: { type: 'integer', minimum: 1, maximum: 1000 } },
|
|
512
|
+
{ name: 'cursor', in: 'query', description: 'The `nextCursor` of the previous page. Anything else is `400`.', schema: str },
|
|
513
|
+
{ name: 'since', in: 'query', description: 'A report version (`version` of an earlier answer): only entries whose layer was added or changed since. An unknown version is answered with everything.', schema: str },
|
|
514
|
+
{ name: 'unjudgedBy', in: 'query', description: '`name` or `name@version` of a critic: only entries it has no current verdict for.', schema: str },
|
|
515
|
+
{ name: 'q', in: 'query', description: 'Words that must all appear in an entry\'s key, title, route or area.', schema: str },
|
|
516
|
+
{ name: 'area', in: 'query', description: 'Areas. Comma-separated.', schema: str },
|
|
517
|
+
{ name: 'profile', in: 'query', description: 'Profile keys, as listed in `profile.key`. Comma-separated.', schema: str },
|
|
518
|
+
{ name: 'state', in: 'query', description: 'State kinds: `screen`, `overlay`, `loading`, `error`, `empty`, `unknown`… Comma-separated.', schema: str },
|
|
519
|
+
{ name: 'route', in: 'query', description: 'Routes. Comma-separated.', schema: str }
|
|
520
|
+
],
|
|
521
|
+
responses: { '200': json('One page of entries.', 'CatalogEntries'), '304': { description: 'Unchanged since the `If-None-Match` ETag.' }, '400': problem('The cursor is not one this server made.'), '401': UNAUTHORIZED, '503': NOT_READY }
|
|
522
|
+
},
|
|
506
523
|
{
|
|
507
524
|
route: 'GET /api/reports/runs/detail', path: '/api/reports/runs/detail', operationId: 'getReportRun', tag: 'Report', role: 'read',
|
|
508
525
|
summary: 'Every test one run recorded',
|
package/docs/agents.md
CHANGED
|
@@ -104,6 +104,8 @@ The complete public export map is generated from the package manifest (wildcards
|
|
|
104
104
|
@crvouga/atlas/schema
|
|
105
105
|
@crvouga/atlas/server
|
|
106
106
|
@crvouga/atlas/critics
|
|
107
|
+
@crvouga/atlas/critics/uiclip
|
|
108
|
+
@crvouga/atlas/critics/llm
|
|
107
109
|
@crvouga/atlas/reports/*
|
|
108
110
|
@crvouga/atlas/schemas/*
|
|
109
111
|
@crvouga/atlas/openapi.json
|
|
@@ -151,7 +153,7 @@ and authentication. Keep synthetic failure checks in a scratch store rather than
|
|
|
151
153
|
## What an agent can rely on
|
|
152
154
|
|
|
153
155
|
**Machine-readable output.** Add `--format json` (alias `--json`) to `atlas init`, `atlas report publish`, `stream`,
|
|
154
|
-
`merge`, `doctor` and `
|
|
156
|
+
`merge`, `doctor`, `status` and `verify-recordings`, to `atlas ui-catalog`, and to `atlas critic list`, `setup`, `doctor`, `run`, `watch`, `review` and `calibrate`. Each prints one JSON document on standard output that validates against a published
|
|
155
157
|
JSON Schema (`@crvouga/atlas/schemas/<name>.schema.json`, draft 2020-12):
|
|
156
158
|
|
|
157
159
|
<!-- generated:json-command-schemas -->
|
|
@@ -164,6 +166,18 @@ JSON Schema (`@crvouga/atlas/schemas/<name>.schema.json`, draft 2020-12):
|
|
|
164
166
|
| `atlas report doctor` | `doctor-result` |
|
|
165
167
|
| `atlas report status` | `report-status` |
|
|
166
168
|
| `atlas report verify-recordings` | `verify-recordings-result` |
|
|
169
|
+
| `atlas critic list` | `critic-list-result` |
|
|
170
|
+
| `atlas critic setup` | `critic-setup-result` |
|
|
171
|
+
| `atlas critic doctor` | `critic-doctor-result` |
|
|
172
|
+
| `atlas critic run` | `critic-run-result` |
|
|
173
|
+
| `atlas critic watch` | `critic-watch-result` |
|
|
174
|
+
| `atlas critic review` | `critic-review-result` |
|
|
175
|
+
| `atlas critic calibrate` | `critic-calibrate-result` |
|
|
176
|
+
| `atlas ui-catalog plan` | `ui-catalog-publish-result` |
|
|
177
|
+
| `atlas ui-catalog publish` | `ui-catalog-publish-result` |
|
|
178
|
+
| `atlas ui-catalog status` | `ui-catalog-status` |
|
|
179
|
+
| `atlas ui-catalog diff` | `ui-catalog-diff-result` |
|
|
180
|
+
| `atlas ui-catalog export` | `ui-catalog-export-result` |
|
|
167
181
|
| `a command that was wrong (standard error)` | `command-error` |
|
|
168
182
|
<!-- /generated:json-command-schemas -->
|
|
169
183
|
|
|
@@ -172,7 +186,7 @@ pass, a store that needs attention, checks that could not be sent, a token that
|
|
|
172
186
|
itself was wrong (nothing was written). With `--format json` a wrong command is `{"ok":false,"error":{"message":…,"code":"usage"}}`
|
|
173
187
|
on standard error.
|
|
174
188
|
|
|
175
|
-
**No prompts, no interactive steps, no hidden state.** Configuration is flags and environment variables:
|
|
189
|
+
**No prompts, no interactive steps, no hidden state.** (`atlas critic` asks questions only when a person is at a terminal and never with `--non-interactive` or `--format json`.) Configuration is flags and environment variables:
|
|
176
190
|
`ATLAS_STORE` (a folder, `s3://…`, or the server's `https://…`), `ATLAS_TOKEN` (or `ATLAS_TOKEN_FILE`). A token is
|
|
177
191
|
a secret of one server and is never sent over plain `http://` beyond this machine.
|
|
178
192
|
For HTTP stores, `ATLAS_PROJECT` (or `--project <slug>` on report commands) selects the project. Native runner
|