@evolvingmachines/evolve 0.0.60 → 0.0.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/chunk-N2LMMVL4.js +427 -0
  2. package/dist/cli/index.cjs +38 -37
  3. package/dist/cli/index.d.cts +3 -1
  4. package/dist/cli/index.d.ts +3 -1
  5. package/dist/cli/index.js +29 -28
  6. package/dist/index.cjs +54 -54
  7. package/dist/index.d.cts +111 -7
  8. package/dist/index.d.ts +111 -7
  9. package/dist/index.js +38 -38
  10. package/dist/{types-DlpTxdR_.d.cts → types-CMEpx9QI.d.cts} +313 -5
  11. package/dist/{types-DlpTxdR_.d.ts → types-CMEpx9QI.d.ts} +313 -5
  12. package/hosted-error-codes.json +6 -0
  13. package/package.json +5 -5
  14. package/skills/evolve-evals/references/cli-reference/analysis.mdx +69 -0
  15. package/skills/evolve-evals/references/cli-reference/check.mdx +86 -0
  16. package/skills/evolve-evals/references/cli-reference/dataset.mdx +13 -0
  17. package/skills/evolve-evals/references/cli-reference/run.mdx +6 -0
  18. package/skills/evolve-evals/references/cli-reference/trial.mdx +71 -2
  19. package/skills/evolve-evals/references/core-concepts/trial-outputs.mdx +14 -0
  20. package/skills/evolve-evals/references/sdk/python.mdx +22 -0
  21. package/skills/evolve-evals/references/sdk/typescript.mdx +22 -0
  22. package/skills/evolve-evals/references/sdk-reference/analyses.mdx +70 -0
  23. package/skills/evolve-evals/references/sdk-reference/checks.mdx +105 -4
  24. package/skills/evolve-evals/references/sdk-reference/datasets.mdx +40 -0
  25. package/skills/evolve-evals/references/sdk-reference/errors.mdx +11 -0
  26. package/skills/evolve-evals/references/sdk-reference/jobs.mdx +1 -1
  27. package/skills/evolve-evals/references/sdk-reference/trials.mdx +70 -0
  28. package/spec/openapi.yaml +3021 -360
  29. package/dist/chunk-JS2UTK2I.js +0 -427
package/spec/openapi.yaml CHANGED
@@ -2082,6 +2082,16 @@ paths:
2082
2082
  — `text/plain` named `<trialId>_<stream>.log` for the raw logs,
2083
2083
  `application/json` named `<trialId>_trace-atif.json` for the
2084
2084
  ATIF trajectory; an absent one still answers the JSON `{"log": null}`.
2085
+ - `filesystem`: the run's FILE SYSTEM as one gzipped tarball — the
2086
+ same archive `GET /api/trials/{trialId}/filesystem/archive`
2087
+ streams for `path=/` (captured after the run, with `MANIFEST.tsv`
2088
+ naming the unchanged image files it does not carry; live from the
2089
+ box while it runs); `source=` forces one, and a source that is not
2090
+ there is 409 `filesystem_state` — never an empty archive. The
2091
+ archive route's own codes answer here too: 422
2092
+ `feature_unsupported` for a live tree too large to leave the box
2093
+ inside the capture budget, 503 `provider_unreachable` when the
2094
+ provider does not answer.
2085
2095
  - `agent-home`: the agent home tree as JSON — the utf8 TEXT VIEW of
2086
2096
  the captured bytes
2087
2097
  (`{"files": {"<sandbox-path>": "<text>", ...} | null}`; a file that
@@ -2150,6 +2160,7 @@ paths:
2150
2160
  - trace-atif # the normalized ATIF v1.7 trajectory
2151
2161
  - trajectory # x-wave: 2 — the harness-native session file slot
2152
2162
  - agent-home
2163
+ - filesystem # the run's file system as one .tar.gz (the archive route's answer)
2153
2164
  - name: format
2154
2165
  in: query
2155
2166
  description: >
@@ -2195,13 +2206,22 @@ paths:
2195
2206
  schema:
2196
2207
  type: string
2197
2208
  format: binary
2198
- description: '`format=tgz` delivery of agent-home.'
2209
+ description: '`format=tgz` delivery of agent-home, or the `filesystem` archive.'
2199
2210
  '400':
2200
2211
  $ref: '#/components/responses/BadRequest'
2201
2212
  '401':
2202
2213
  $ref: '#/components/responses/Unauthorized'
2203
2214
  '404':
2204
2215
  $ref: '#/components/responses/NotFound'
2216
+ '409':
2217
+ description: >
2218
+ `filesystem_state` — `stream=filesystem` with `source=live` while
2219
+ the box is gone, or `source=capture` before a capture exists
2220
+ (details carry the requested source and the state).
2221
+ content:
2222
+ application/json:
2223
+ schema:
2224
+ $ref: '#/components/schemas/Error'
2205
2225
  '413':
2206
2226
  description: >
2207
2227
  The agent-home JSON text view is over the server's whole-read
@@ -2211,10 +2231,27 @@ paths:
2211
2231
  application/json:
2212
2232
  schema:
2213
2233
  $ref: '#/components/schemas/Error'
2234
+ '422':
2235
+ description: >
2236
+ `feature_unsupported` — `stream=filesystem` on a live box whose
2237
+ provider has no live view, or whose subtree cannot leave the box
2238
+ inside the capture budget (details.cause names it).
2239
+ content:
2240
+ application/json:
2241
+ schema:
2242
+ $ref: '#/components/schemas/Error'
2214
2243
  '429':
2215
2244
  $ref: '#/components/responses/TooManyRequests'
2216
2245
  '500':
2217
2246
  $ref: '#/components/responses/InternalError'
2247
+ '503':
2248
+ description: >
2249
+ `provider_unreachable` — `stream=filesystem` and the box's
2250
+ provider did not answer; retry after `Retry-After`.
2251
+ content:
2252
+ application/json:
2253
+ schema:
2254
+ $ref: '#/components/schemas/Error'
2218
2255
 
2219
2256
  /api/trials/{trialId}/files:
2220
2257
  get:
@@ -2332,33 +2369,44 @@ paths:
2332
2369
  '500':
2333
2370
  $ref: '#/components/responses/InternalError'
2334
2371
 
2335
- /api/trials/{trialId}/regrade:
2336
- post:
2372
+ /api/trials/{trialId}/filesystem:
2373
+ get:
2337
2374
  tags: [trials]
2338
- operationId: regradeTrial
2339
- summary: Regrade one trial
2375
+ operationId: getTrialFilesystem
2376
+ summary: The trial's file system — which source it has
2340
2377
  description: |
2341
- Verifier-only re-run of a single trial. **The response is a Job** — a
2342
- one-trial regrade job with `source_jobs` recording the provenance.
2343
- The source trial is immutable. A trial of an UPLOADED job is refused
2344
- (`job_uploaded`, 409): no recorded verifier inputs exist to re-run.
2378
+ The Files surface of one trial (the file system family, one shape
2379
+ under every run owner: trials, analyses, task checks — and the task
2380
+ package as a fourth, read-only owner). `state` says what the
2381
+ listing, read, search, archive and change routes will answer from:
2382
+ `live` — the box is running and the routes read it as it changes;
2383
+ `captured` — the run ended and its file system was kept, the same
2384
+ routes read the kept tree; `capturing` — the run just ended and the
2385
+ capture is still being written; `none` — nothing to read yet (a
2386
+ queued run, a run older than the feature, a capture that failed —
2387
+ `capture.status` says which). `box` names the running box and its
2388
+ role, `watcher` how change events arrive (`native` from the
2389
+ provider, `poll` from the folders the viewer declared open), and
2390
+ `capture` the kept tree's record: when, after which phase, how many
2391
+ entries and changed files, `ready` | `incomplete` (`left_out` names
2392
+ what did not fit the budget) | `failed`.
2345
2393
  parameters:
2346
2394
  - $ref: '#/components/parameters/TrialId'
2347
2395
  responses:
2348
- '202':
2349
- description: The regrade job.
2396
+ '200':
2397
+ description: The file system's state.
2350
2398
  content:
2351
2399
  application/json:
2352
2400
  schema:
2353
- $ref: '#/components/schemas/Job'
2401
+ $ref: '#/components/schemas/FilesystemStatus'
2354
2402
  '401':
2355
2403
  $ref: '#/components/responses/Unauthorized'
2356
2404
  '404':
2357
2405
  $ref: '#/components/responses/NotFound'
2358
- '409':
2406
+ '503':
2359
2407
  description: >
2360
- `job_not_terminal`, `no_regradable_trials`, or
2361
- `regrade_source_ineligible`.
2408
+ `provider_unreachable` — the box's provider did not answer;
2409
+ retry after `Retry-After`.
2362
2410
  content:
2363
2411
  application/json:
2364
2412
  schema:
@@ -2368,296 +2416,2042 @@ paths:
2368
2416
  '500':
2369
2417
  $ref: '#/components/responses/InternalError'
2370
2418
 
2371
- /api/trials/{trialId}/retry:
2372
- post:
2419
+ /api/trials/{trialId}/filesystem/files:
2420
+ get:
2373
2421
  tags: [trials]
2374
- operationId: retryTrial
2375
- summary: Retry one settled trial
2422
+ operationId: listTrialFilesystemFolder
2423
+ summary: List one folder of the trial's file system
2376
2424
  description: |
2377
- Run ONE settled trial again. **The response is a Job** a one-trial
2378
- retry job inheriting the source job's config, with `source_jobs`
2379
- recording `action: "retry"`; view it with `GET /api/jobs/{jobId}`
2380
- (the 202 carries a `Location` header naming it). The source trial is
2381
- immutable.
2382
-
2383
- The same operation as `POST /api/jobs/{jobId}/retry` with
2384
- `{"trial_ids": [this]}` one selection rule, one builder, one
2385
- fingerprint kept as its own door because the trial page (and
2386
- Harbor's hosted `trial retry <id>`) address the trial, not the job.
2387
- The source JOB may still be running: a settled trial's facts are
2388
- final, so "run it again" is well-defined the moment it settles. A
2389
- trial of an UPLOADED job is refused (`job_uploaded`, 409).
2390
-
2391
- Supports `Idempotency-Key` — the identical fingerprint the job-level
2392
- door computes for this one-trial selection, because it IS the
2393
- identical request.
2425
+ One page of a folder, sorted by name, paged with `cursor` (the last
2426
+ name served) and `limit` (default 500, max 1000). `source` forces
2427
+ `live` or `capture`; omitted, whichever the run has (`state` on the
2428
+ status route). Every entry carries its type (`dir` | `file` |
2429
+ `symlink` — with `target` — | `other`), size, mtime, mode and owner;
2430
+ `changed` and `phase` say whether the run created or modified it
2431
+ and in which phase (null when not known for the source); on a
2432
+ captured listing `captured: false` marks a file the run never
2433
+ touched, which only the image holds it lists but does not open.
2434
+ The kernel trees (`/proc`, `/sys`, `/dev`) are never listed.
2394
2435
  parameters:
2395
2436
  - $ref: '#/components/parameters/TrialId'
2396
- - $ref: '#/components/parameters/IdempotencyKey'
2437
+ - $ref: '#/components/parameters/FilesystemPath'
2438
+ - $ref: '#/components/parameters/FilesystemSource'
2439
+ - $ref: '#/components/parameters/FilesystemCursor'
2440
+ - $ref: '#/components/parameters/FilesystemLimit'
2397
2441
  responses:
2398
- '202':
2399
- description: >
2400
- The retry job. `Location` names it (`/api/jobs/{jobId}`).
2401
- content:
2402
- application/json:
2403
- schema:
2404
- $ref: '#/components/schemas/Job'
2405
2442
  '200':
2406
- description: Idempotent replay of an identical retry request.
2443
+ description: One page of the folder.
2407
2444
  content:
2408
2445
  application/json:
2409
2446
  schema:
2410
- $ref: '#/components/schemas/Job'
2447
+ $ref: '#/components/schemas/FilesystemListing'
2448
+ '400':
2449
+ $ref: '#/components/responses/BadRequest'
2411
2450
  '401':
2412
2451
  $ref: '#/components/responses/Unauthorized'
2413
- '402':
2414
- description: Out of credits (`insufficient_credits`).
2452
+ '404':
2453
+ $ref: '#/components/responses/NotFound'
2454
+ '409':
2455
+ description: >
2456
+ `filesystem_state` — `source=live` while the box is gone, or
2457
+ `source=capture` before a capture exists (details carry the
2458
+ requested source and the state).
2459
+ content:
2460
+ application/json:
2461
+ schema:
2462
+ $ref: '#/components/schemas/Error'
2463
+ '503':
2464
+ description: >
2465
+ `provider_unreachable` — the box's provider did not answer;
2466
+ retry after `Retry-After`.
2415
2467
  content:
2416
2468
  application/json:
2417
2469
  schema:
2418
2470
  $ref: '#/components/schemas/Error'
2471
+ '429':
2472
+ $ref: '#/components/responses/TooManyRequests'
2473
+ '500':
2474
+ $ref: '#/components/responses/InternalError'
2475
+
2476
+ /api/trials/{trialId}/filesystem/files/{path}:
2477
+ get:
2478
+ tags: [trials]
2479
+ operationId: readTrialFilesystemFile
2480
+ summary: Read one file of the trial's file system, raw bytes
2481
+ description: |
2482
+ RAW BYTES of one file — application/octet-stream, byte fidelity, no
2483
+ translation (whether it is text is the reader's call: the first
2484
+ 4 KiB, a NUL byte means binary). A standard single `Range` header
2485
+ (`bytes=a-b`, `bytes=a-`, `bytes=-n`) reads a slice and answers 206
2486
+ with `Content-Range`; `Accept-Ranges: bytes` advertises it; a
2487
+ malformed or multi-range header is the typed 400, an out-of-file
2488
+ range the 416. An unranged read above the deployment's whole-read
2489
+ ceiling is the typed 413 (`invalid_input`, param `Range`, details
2490
+ `size_bytes` / `max_unranged_bytes`) — read it in slices.
2491
+ `X-Source` names the source that answered, `X-Ms` the server time.
2492
+ A symlink reads as its target (one hop). A path the file system
2493
+ does not hold is 404 `not_found`; on a captured read, a file the
2494
+ run never touched is 404 `not_captured` (only the image holds it).
2495
+ A stream that breaks after the headers ends with an aborted
2496
+ connection, never a partial body delivered as success.
2497
+ parameters:
2498
+ - $ref: '#/components/parameters/TrialId'
2499
+ - name: path
2500
+ in: path
2501
+ required: true
2502
+ description: The absolute box path, e.g. `app/work/main.py` for `/app/work/main.py` (the leading slash is the route's).
2503
+ schema:
2504
+ type: string
2505
+ - $ref: '#/components/parameters/FilesystemSource'
2506
+ - name: Range
2507
+ in: header
2508
+ description: Optional single byte range (`bytes=a-b` / `bytes=a-` / `bytes=-n`).
2509
+ schema:
2510
+ type: string
2511
+ responses:
2512
+ '200':
2513
+ description: The whole file (`X-Source`, `X-Ms`).
2514
+ content:
2515
+ application/octet-stream:
2516
+ schema:
2517
+ type: string
2518
+ format: binary
2519
+ '206':
2520
+ description: The requested byte range (`Content-Range` states position and size).
2521
+ content:
2522
+ application/octet-stream:
2523
+ schema:
2524
+ type: string
2525
+ format: binary
2526
+ '400':
2527
+ $ref: '#/components/responses/BadRequest'
2528
+ '401':
2529
+ $ref: '#/components/responses/Unauthorized'
2419
2530
  '404':
2420
2531
  $ref: '#/components/responses/NotFound'
2421
2532
  '409':
2422
2533
  description: >
2423
- Trial still live (`trial_not_settled` QUEUED, RUNNING, and
2424
- SCORING trials cannot be retried), or `idempotency_key_reused`.
2534
+ `filesystem_state` `source=live` while the box is gone, or
2535
+ `source=capture` before a capture exists (details carry the
2536
+ requested source and the state).
2425
2537
  content:
2426
2538
  application/json:
2427
2539
  schema:
2428
2540
  $ref: '#/components/schemas/Error'
2429
- '429':
2541
+ '413':
2430
2542
  description: >
2431
- Two conditions, distinguished by code: `rate_limited` (the
2432
- standard throttle honor `Retry-After`), and `quota_exceeded`
2433
- — the owning organization's `max_queued_trials` ceiling would
2434
- be crossed by this job (`details`: `{quota, limit, used,
2435
- requested, org}`; the message starts `hosted quota exceeded:`).
2436
- No `Retry-After`: the wait is not a known number. Nothing was
2437
- created; retry once the organization's queue has drained, or
2438
- ask the platform administrator to raise the ceiling. A ceiling
2439
- of 0 means the organization is paused.
2543
+ An unranged read above the whole-read ceiling (`invalid_input`,
2544
+ param `Range`; details `size_bytes` / `max_unranged_bytes`).
2440
2545
  content:
2441
2546
  application/json:
2442
2547
  schema:
2443
2548
  $ref: '#/components/schemas/Error'
2549
+ '416':
2550
+ description: >
2551
+ Range selects nothing inside the file (`invalid_input`;
2552
+ `Content-Range` carries `bytes */<size>`).
2553
+ content:
2554
+ application/json:
2555
+ schema:
2556
+ $ref: '#/components/schemas/Error'
2557
+ '503':
2558
+ description: >
2559
+ `provider_unreachable` — the box's provider did not answer;
2560
+ retry after `Retry-After`.
2561
+ content:
2562
+ application/json:
2563
+ schema:
2564
+ $ref: '#/components/schemas/Error'
2565
+ '429':
2566
+ $ref: '#/components/responses/TooManyRequests'
2444
2567
  '500':
2445
2568
  $ref: '#/components/responses/InternalError'
2446
2569
 
2447
- /api/trials/stop:
2448
- post:
2570
+ /api/trials/{trialId}/filesystem/search:
2571
+ get:
2449
2572
  tags: [trials]
2450
- operationId: stopTrials
2451
- summary: Stop selected running trials or trace analyses
2573
+ operationId: searchTrialFilesystem
2574
+ summary: Search the trial's file system for text
2452
2575
  description: |
2453
- Stops individual in-flight trials without cancelling their job: each
2454
- trial's sandbox is killed and the trial is settled with its spend read
2455
- from the gateway. Only the caller's own trials; ids belonging to
2456
- someone else are reported in `not_found` (existence is never leaked).
2457
- Idempotent already-terminal trials are reported as such and left
2458
- untouched.
2459
-
2460
- Trace-analysis ids ride the same `trial_ids` array — what each id is
2461
- gets resolved server-side, so a client never partitions a selection.
2462
- A running analysis has its analyzer box killed and settles `failed`
2463
- with failure phase `stopped`, its own spend read from the gateway; a
2464
- queued one settles the same way before any box boots (nothing minted,
2465
- nothing spent). Stopped analyses come back in `stopped_analyses` with
2466
- their settled rows; the caller's already-settled analyses land in
2467
- `already_terminal` and foreign or unknown ids in `not_found` the
2468
- trials' refusal semantics, id for id.
2469
- requestBody:
2470
- required: true
2471
- content:
2472
- application/json:
2473
- schema:
2474
- $ref: '#/components/schemas/StopRequest'
2576
+ Content search: `q` is the text (or, with `regex=true`, the
2577
+ pattern), `path` the folder to search under (default `/` the
2578
+ whole box, `scope: "box"`, which can take seconds; a folder answers
2579
+ `scope: "path"` in well under a second), `limit` the hit cap
2580
+ (default 200, max 1000). `truncated: true` means more hits exist or
2581
+ the search budget ran out — narrow the path or the text. Live: the
2582
+ box is searched as it is; captured: the kept text is searched, and
2583
+ `image_files_excluded: true` says files the run never touched were
2584
+ not searchable. Binary files never hit.
2585
+ parameters:
2586
+ - $ref: '#/components/parameters/TrialId'
2587
+ - name: q
2588
+ in: query
2589
+ required: true
2590
+ description: The text to find; a regular expression with `regex=true`.
2591
+ schema:
2592
+ type: string
2593
+ - $ref: '#/components/parameters/FilesystemPath'
2594
+ - name: regex
2595
+ in: query
2596
+ description: Treat `q` as a regular expression (default false — a plain substring).
2597
+ schema:
2598
+ type: boolean
2599
+ default: false
2600
+ - $ref: '#/components/parameters/FilesystemLimit'
2601
+ - $ref: '#/components/parameters/FilesystemSource'
2475
2602
  responses:
2476
2603
  '200':
2477
- description: Per-trial outcome of the stop request.
2604
+ description: The hits found.
2478
2605
  content:
2479
2606
  application/json:
2480
2607
  schema:
2481
- $ref: '#/components/schemas/StopResponse'
2608
+ $ref: '#/components/schemas/FilesystemSearchResult'
2482
2609
  '400':
2483
2610
  $ref: '#/components/responses/BadRequest'
2484
2611
  '401':
2485
2612
  $ref: '#/components/responses/Unauthorized'
2613
+ '404':
2614
+ $ref: '#/components/responses/NotFound'
2615
+ '409':
2616
+ description: >
2617
+ `filesystem_state` — `source=live` while the box is gone, or
2618
+ `source=capture` before a capture exists (details carry the
2619
+ requested source and the state).
2620
+ content:
2621
+ application/json:
2622
+ schema:
2623
+ $ref: '#/components/schemas/Error'
2624
+ '503':
2625
+ description: >
2626
+ `provider_unreachable` — the box's provider did not answer;
2627
+ retry after `Retry-After`.
2628
+ content:
2629
+ application/json:
2630
+ schema:
2631
+ $ref: '#/components/schemas/Error'
2486
2632
  '429':
2487
2633
  $ref: '#/components/responses/TooManyRequests'
2488
2634
  '500':
2489
2635
  $ref: '#/components/responses/InternalError'
2490
2636
 
2491
- # ===========================================================================
2492
- # Analyses — trace-analysis runs as a first-class list
2493
- # ===========================================================================
2494
-
2495
- /api/analyses:
2637
+ /api/trials/{trialId}/filesystem/events:
2496
2638
  get:
2497
- tags: [analyses]
2498
- operationId: listAnalyses
2499
- summary: List trace-analysis runs
2639
+ tags: [trials]
2640
+ operationId: streamTrialFilesystemEvents
2641
+ summary: Change events while the trial's box lives (SSE)
2500
2642
  description: |
2501
- Every trace analysis the caller may read, newest first, cursor-paged.
2502
- Items are the same `TrialAnalysis` object a trial serves as its
2503
- LATEST analysis (`Trial.analysis`) here every analysis answers,
2504
- earlier waves included, each carrying the trial, job, and task it
2505
- judged. Harbor has no analysis list (their analyze writes a local
2506
- `analysis.json` per trial dir); hosted, the analyzer's run is a
2507
- record of its own and this is its catalog.
2508
-
2509
- Visibility follows the analyze verb (`POST /api/jobs/{jobId}/analyze`)
2510
- one relation deeper: an analysis belongs to the analyzed trial's job.
2511
- `scope=my` (the default) lists analyses of jobs you created;
2512
- `scope=shared` lists analyses of your organizations' jobs that
2513
- teammates created. Nothing is public, so Harbor's `all` is refused.
2514
- An API key sees exactly what its owner may read — there is no
2515
- wider scope.
2516
-
2517
- Filters are server-side and compose with the cursor.
2643
+ Server-sent events. `event: state` first (`{state, box}`), then
2644
+ `event: fs` per change (`{seq, t, path, type: create|write|remove|
2645
+ rename, source: watch|poll}`, `id: <seq>`), `event: ping` every
2646
+ 15 s. Resume with `Last-Event-ID: <seq>` (or `?after=`): the events
2647
+ after it replay when the server still holds them; when it does not,
2648
+ a fresh `state` frame is the signal to relist the open folders.
2649
+ On a provider without a native watcher (`watcher: "poll"` on the
2650
+ status route) events come from polling the folders declared with
2651
+ `POST …/filesystem/watch`, so declare them. A `state` frame follows
2652
+ every change of state; the stream ends once the file system settles
2653
+ (`captured` or `none`).
2518
2654
  parameters:
2519
- - $ref: '#/components/parameters/Limit'
2520
- - $ref: '#/components/parameters/Cursor'
2521
- - $ref: '#/components/parameters/ListScope'
2522
- - name: job
2523
- in: query
2524
- description: Only analyses of this job's trials.
2655
+ - $ref: '#/components/parameters/TrialId'
2656
+ - name: Last-Event-ID
2657
+ in: header
2658
+ description: Resume after this event seq.
2525
2659
  schema:
2526
2660
  type: string
2527
- format: uuid
2528
- - name: status
2661
+ - name: after
2529
2662
  in: query
2530
- description: >
2531
- Filter by the analysis's own lifecycle word (the lowercase
2532
- ladder `TrialAnalysis.status` speaks) — repeatable and/or
2533
- comma-separated; an unknown word is refused (`invalid_input`)
2534
- with the ladder in `details.valid_statuses`.
2663
+ description: The same as `Last-Event-ID`, for clients that cannot set headers.
2535
2664
  schema:
2536
- type: array
2537
- items:
2538
- type: string
2539
- enum: [queued, running, completed, failed]
2540
- explode: true
2665
+ type: string
2541
2666
  responses:
2542
2667
  '200':
2543
- description: One page of analyses.
2668
+ description: The event stream.
2544
2669
  content:
2545
- application/json:
2670
+ text/event-stream:
2546
2671
  schema:
2547
- $ref: '#/components/schemas/AnalysisPage'
2672
+ type: string
2548
2673
  '400':
2549
2674
  $ref: '#/components/responses/BadRequest'
2550
2675
  '401':
2551
2676
  $ref: '#/components/responses/Unauthorized'
2677
+ '404':
2678
+ $ref: '#/components/responses/NotFound'
2679
+ '503':
2680
+ description: >
2681
+ `provider_unreachable` — the box's provider did not answer;
2682
+ retry after `Retry-After`.
2683
+ content:
2684
+ application/json:
2685
+ schema:
2686
+ $ref: '#/components/schemas/Error'
2552
2687
  '429':
2553
2688
  $ref: '#/components/responses/TooManyRequests'
2554
2689
  '500':
2555
2690
  $ref: '#/components/responses/InternalError'
2556
2691
 
2692
+ /api/trials/{trialId}/filesystem/watch:
2693
+ post:
2694
+ tags: [trials]
2695
+ operationId: watchTrialFilesystem
2696
+ summary: Declare the folders the viewer has open
2697
+ description: |
2698
+ The folders whose changes the event stream must report on a
2699
+ provider without a native watcher (`watcher: "poll"`): idempotent —
2700
+ the body's `paths` REPLACES the set; an empty set stops polling. At
2701
+ most 8 folders at once (a bigger set is the typed 400 naming the
2702
+ bound). On a provider with a native watcher the set is accepted
2703
+ and unused. Refused with `filesystem_state` when the box is gone.
2704
+ parameters:
2705
+ - $ref: '#/components/parameters/TrialId'
2706
+ requestBody:
2707
+ required: true
2708
+ content:
2709
+ application/json:
2710
+ schema:
2711
+ $ref: '#/components/schemas/FilesystemWatchRequest'
2712
+ responses:
2713
+ '200':
2714
+ description: The set in force.
2715
+ content:
2716
+ application/json:
2717
+ schema:
2718
+ $ref: '#/components/schemas/FilesystemWatchResult'
2719
+ '400':
2720
+ $ref: '#/components/responses/BadRequest'
2721
+ '401':
2722
+ $ref: '#/components/responses/Unauthorized'
2723
+ '404':
2724
+ $ref: '#/components/responses/NotFound'
2725
+ '409':
2726
+ description: >
2727
+ `filesystem_state` — `source=live` while the box is gone, or
2728
+ `source=capture` before a capture exists (details carry the
2729
+ requested source and the state).
2730
+ content:
2731
+ application/json:
2732
+ schema:
2733
+ $ref: '#/components/schemas/Error'
2734
+ '503':
2735
+ description: >
2736
+ `provider_unreachable` — the box's provider did not answer;
2737
+ retry after `Retry-After`.
2738
+ content:
2739
+ application/json:
2740
+ schema:
2741
+ $ref: '#/components/schemas/Error'
2742
+ '429':
2743
+ $ref: '#/components/responses/TooManyRequests'
2744
+ '500':
2745
+ $ref: '#/components/responses/InternalError'
2746
+
2747
+ /api/trials/{trialId}/filesystem/changes:
2748
+ get:
2749
+ tags: [trials]
2750
+ operationId: listTrialFilesystemChanges
2751
+ summary: The files the trial changed
2752
+ description: |
2753
+ The flat list of what the run created, modified or removed since its
2754
+ box started, with `phase` (`setup` | `agent` | `verifier`); `total`
2755
+ and `changed_bytes` are the whole list's, `items` one page (`cursor`
2756
+ = an opaque position, `limit` default 500, max 1000); `phase=`
2757
+ narrows to one phase. Captured: stored with the kept tree. Live: the
2758
+ box start listing against a fresh one, seconds per call; a box whose
2759
+ start listing was not stored is the typed 422.
2760
+ parameters:
2761
+ - $ref: '#/components/parameters/TrialId'
2762
+ - $ref: '#/components/parameters/FilesystemSource'
2763
+ - name: phase
2764
+ in: query
2765
+ description: Only changes of one phase (default all).
2766
+ schema:
2767
+ type: string
2768
+ enum: [setup, agent, verifier, all]
2769
+ default: all
2770
+ - $ref: '#/components/parameters/FilesystemCursor'
2771
+ - $ref: '#/components/parameters/FilesystemLimit'
2772
+ responses:
2773
+ '200':
2774
+ description: One page of the changed-files list.
2775
+ content:
2776
+ application/json:
2777
+ schema:
2778
+ $ref: '#/components/schemas/FilesystemChanges'
2779
+ '400':
2780
+ $ref: '#/components/responses/BadRequest'
2781
+ '401':
2782
+ $ref: '#/components/responses/Unauthorized'
2783
+ '404':
2784
+ $ref: '#/components/responses/NotFound'
2785
+ '409':
2786
+ description: >
2787
+ `filesystem_state` — `source=live` while the box is gone, or
2788
+ `source=capture` before a capture exists (details carry the
2789
+ requested source and the state).
2790
+ content:
2791
+ application/json:
2792
+ schema:
2793
+ $ref: '#/components/schemas/Error'
2794
+ '422':
2795
+ description: >
2796
+ `feature_unsupported` — the shape or the provider has no live view
2797
+ of what was asked (details.cause names it).
2798
+ content:
2799
+ application/json:
2800
+ schema:
2801
+ $ref: '#/components/schemas/Error'
2802
+ '503':
2803
+ description: >
2804
+ `provider_unreachable` — the box's provider did not answer;
2805
+ retry after `Retry-After`.
2806
+ content:
2807
+ application/json:
2808
+ schema:
2809
+ $ref: '#/components/schemas/Error'
2810
+ '429':
2811
+ $ref: '#/components/responses/TooManyRequests'
2812
+ '500':
2813
+ $ref: '#/components/responses/InternalError'
2814
+
2815
+ /api/trials/{trialId}/filesystem/archive:
2816
+ get:
2817
+ tags: [trials]
2818
+ operationId: downloadTrialFilesystemArchive
2819
+ summary: Download one subtree of the trial's file system as a .tar.gz
2820
+ description: |
2821
+ A gzipped tarball of the subtree under `path` (default `/`, the
2822
+ whole tree), streamed. Captured: only the kept bytes ride; the
2823
+ files the run never touched are listed in `MANIFEST.tsv` at the
2824
+ archive root with `captured=false`. Live: read out of the running
2825
+ box under the capture budget — a subtree too large to leave the box
2826
+ inside it is refused up front, 422 `feature_unsupported` with the
2827
+ size in `details.cause`; a read-out that still stalls past the
2828
+ budget aborts the connection, never a truncated archive delivered
2829
+ as success. `X-Source` names the source. A folder the file system
2830
+ does not hold is 404 `not_found`.
2831
+ parameters:
2832
+ - $ref: '#/components/parameters/TrialId'
2833
+ - $ref: '#/components/parameters/FilesystemPath'
2834
+ - $ref: '#/components/parameters/FilesystemSource'
2835
+ responses:
2836
+ '200':
2837
+ description: The archive (`Content-Disposition` names it `<id>_filesystem.tar.gz`).
2838
+ content:
2839
+ application/gzip:
2840
+ schema:
2841
+ type: string
2842
+ format: binary
2843
+ '400':
2844
+ $ref: '#/components/responses/BadRequest'
2845
+ '401':
2846
+ $ref: '#/components/responses/Unauthorized'
2847
+ '404':
2848
+ $ref: '#/components/responses/NotFound'
2849
+ '409':
2850
+ description: >
2851
+ `filesystem_state` — `source=live` while the box is gone, or
2852
+ `source=capture` before a capture exists (details carry the
2853
+ requested source and the state).
2854
+ content:
2855
+ application/json:
2856
+ schema:
2857
+ $ref: '#/components/schemas/Error'
2858
+ '422':
2859
+ description: >
2860
+ `feature_unsupported` — the provider has no live view of what was
2861
+ asked, or the live subtree cannot leave the box inside the
2862
+ capture budget (details.cause names it, with the size).
2863
+ content:
2864
+ application/json:
2865
+ schema:
2866
+ $ref: '#/components/schemas/Error'
2867
+ '503':
2868
+ description: >
2869
+ `provider_unreachable` — the box's provider did not answer;
2870
+ retry after `Retry-After`.
2871
+ content:
2872
+ application/json:
2873
+ schema:
2874
+ $ref: '#/components/schemas/Error'
2875
+ '429':
2876
+ $ref: '#/components/responses/TooManyRequests'
2877
+ '500':
2878
+ $ref: '#/components/responses/InternalError'
2879
+
2880
+ /api/trials/{trialId}/logs:
2881
+ get:
2882
+ tags: [trials]
2883
+ operationId: getTrialSandboxLog
2884
+ summary: One page of a sandbox log stream of the trial
2885
+ description: |
2886
+ The box's own streams, recorded while it runs and kept after:
2887
+ `agent` (the harness's stdout/stderr), `verifier` (the verifier
2888
+ command's) and `system` (the box's system log — only when the job
2889
+ asked for it, `JobCreate.system_log`). `setup` and `metrics` are
2890
+ named but not recorded today. Lines are `{seq, t, fd: out|err, line}`;
2891
+ `cursor` is an opaque position, `limit` default 1000, max 1000. A
2892
+ stream the platform holds nothing for answers an empty page whose
2893
+ `reason` says why — never an error.
2894
+ parameters:
2895
+ - $ref: '#/components/parameters/TrialId'
2896
+ - name: stream
2897
+ in: query
2898
+ required: true
2899
+ schema:
2900
+ $ref: '#/components/schemas/SandboxLogStream'
2901
+ - $ref: '#/components/parameters/FilesystemCursor'
2902
+ - $ref: '#/components/parameters/FilesystemLimit'
2903
+ responses:
2904
+ '200':
2905
+ description: One page of the stream.
2906
+ content:
2907
+ application/json:
2908
+ schema:
2909
+ $ref: '#/components/schemas/SandboxLogLines'
2910
+ '400':
2911
+ $ref: '#/components/responses/BadRequest'
2912
+ '401':
2913
+ $ref: '#/components/responses/Unauthorized'
2914
+ '404':
2915
+ $ref: '#/components/responses/NotFound'
2916
+ '503':
2917
+ description: >
2918
+ `provider_unreachable` — the box's provider did not answer;
2919
+ retry after `Retry-After`.
2920
+ content:
2921
+ application/json:
2922
+ schema:
2923
+ $ref: '#/components/schemas/Error'
2924
+ '429':
2925
+ $ref: '#/components/responses/TooManyRequests'
2926
+ '500':
2927
+ $ref: '#/components/responses/InternalError'
2928
+
2929
+ /api/trials/{trialId}/logs/events:
2930
+ get:
2931
+ tags: [trials]
2932
+ operationId: streamTrialSandboxLogs
2933
+ summary: Sandbox log lines as they arrive (SSE)
2934
+ description: |
2935
+ Server-sent events over every stream at once: `event: line`
2936
+ (`{stream, seq, t, fd, line}`, `id: <stream>:<seq>`), `event: state`
2937
+ on open and on every change of the box's state, `event: ping` every
2938
+ 15 s. Without
2939
+ `Last-Event-ID` every stream replays from its beginning and then
2940
+ follows; with `Last-Event-ID: <stream>:<seq>` that stream resumes
2941
+ after the seq the paged GET handed out. Once the box is gone and
2942
+ every recorded line has been sent, the stream ends — the paged GET
2943
+ is the reader after that.
2944
+ parameters:
2945
+ - $ref: '#/components/parameters/TrialId'
2946
+ - name: Last-Event-ID
2947
+ in: header
2948
+ description: Resume one stream after `<stream>:<seq>`.
2949
+ schema:
2950
+ type: string
2951
+ responses:
2952
+ '200':
2953
+ description: The event stream.
2954
+ content:
2955
+ text/event-stream:
2956
+ schema:
2957
+ type: string
2958
+ '400':
2959
+ $ref: '#/components/responses/BadRequest'
2960
+ '401':
2961
+ $ref: '#/components/responses/Unauthorized'
2962
+ '404':
2963
+ $ref: '#/components/responses/NotFound'
2964
+ '503':
2965
+ description: >
2966
+ `provider_unreachable` — the box's provider did not answer;
2967
+ retry after `Retry-After`.
2968
+ content:
2969
+ application/json:
2970
+ schema:
2971
+ $ref: '#/components/schemas/Error'
2972
+ '429':
2973
+ $ref: '#/components/responses/TooManyRequests'
2974
+ '500':
2975
+ $ref: '#/components/responses/InternalError'
2976
+
2977
+ /api/trials/{trialId}/procs:
2978
+ get:
2979
+ tags: [trials]
2980
+ operationId: getTrialProcs
2981
+ summary: The trial's box process list (live only)
2982
+ description: |
2983
+ The processes running in the box right now, as one text listing
2984
+ (`ps` sorted by memory; on an image without `ps`, the same table
2985
+ read from `/proc`, its first line saying so). Live only: once the
2986
+ box is gone the answer is 409 `filesystem_state`.
2987
+ parameters:
2988
+ - $ref: '#/components/parameters/TrialId'
2989
+ responses:
2990
+ '200':
2991
+ description: The listing.
2992
+ content:
2993
+ application/json:
2994
+ schema:
2995
+ $ref: '#/components/schemas/SandboxProcs'
2996
+ '401':
2997
+ $ref: '#/components/responses/Unauthorized'
2998
+ '404':
2999
+ $ref: '#/components/responses/NotFound'
3000
+ '409':
3001
+ description: >
3002
+ `filesystem_state` — `source=live` while the box is gone, or
3003
+ `source=capture` before a capture exists (details carry the
3004
+ requested source and the state).
3005
+ content:
3006
+ application/json:
3007
+ schema:
3008
+ $ref: '#/components/schemas/Error'
3009
+ '503':
3010
+ description: >
3011
+ `provider_unreachable` — the box's provider did not answer;
3012
+ retry after `Retry-After`.
3013
+ content:
3014
+ application/json:
3015
+ schema:
3016
+ $ref: '#/components/schemas/Error'
3017
+ '429':
3018
+ $ref: '#/components/responses/TooManyRequests'
3019
+ '500':
3020
+ $ref: '#/components/responses/InternalError'
3021
+
3022
+
3023
+ /api/trials/{trialId}/regrade:
3024
+ post:
3025
+ tags: [trials]
3026
+ operationId: regradeTrial
3027
+ summary: Regrade one trial
3028
+ description: |
3029
+ Verifier-only re-run of a single trial. **The response is a Job** — a
3030
+ one-trial regrade job with `source_jobs` recording the provenance.
3031
+ The source trial is immutable. A trial of an UPLOADED job is refused
3032
+ (`job_uploaded`, 409): no recorded verifier inputs exist to re-run.
3033
+ parameters:
3034
+ - $ref: '#/components/parameters/TrialId'
3035
+ responses:
3036
+ '202':
3037
+ description: The regrade job.
3038
+ content:
3039
+ application/json:
3040
+ schema:
3041
+ $ref: '#/components/schemas/Job'
3042
+ '401':
3043
+ $ref: '#/components/responses/Unauthorized'
3044
+ '404':
3045
+ $ref: '#/components/responses/NotFound'
3046
+ '409':
3047
+ description: >
3048
+ `job_not_terminal`, `no_regradable_trials`, or
3049
+ `regrade_source_ineligible`.
3050
+ content:
3051
+ application/json:
3052
+ schema:
3053
+ $ref: '#/components/schemas/Error'
3054
+ '429':
3055
+ $ref: '#/components/responses/TooManyRequests'
3056
+ '500':
3057
+ $ref: '#/components/responses/InternalError'
3058
+
3059
+ /api/trials/{trialId}/retry:
3060
+ post:
3061
+ tags: [trials]
3062
+ operationId: retryTrial
3063
+ summary: Retry one settled trial
3064
+ description: |
3065
+ Run ONE settled trial again. **The response is a Job** — a one-trial
3066
+ retry job inheriting the source job's config, with `source_jobs`
3067
+ recording `action: "retry"`; view it with `GET /api/jobs/{jobId}`
3068
+ (the 202 carries a `Location` header naming it). The source trial is
3069
+ immutable.
3070
+
3071
+ The same operation as `POST /api/jobs/{jobId}/retry` with
3072
+ `{"trial_ids": [this]}` — one selection rule, one builder, one
3073
+ fingerprint — kept as its own door because the trial page (and
3074
+ Harbor's hosted `trial retry <id>`) address the trial, not the job.
3075
+ The source JOB may still be running: a settled trial's facts are
3076
+ final, so "run it again" is well-defined the moment it settles. A
3077
+ trial of an UPLOADED job is refused (`job_uploaded`, 409).
3078
+
3079
+ Supports `Idempotency-Key` — the identical fingerprint the job-level
3080
+ door computes for this one-trial selection, because it IS the
3081
+ identical request.
3082
+ parameters:
3083
+ - $ref: '#/components/parameters/TrialId'
3084
+ - $ref: '#/components/parameters/IdempotencyKey'
3085
+ responses:
3086
+ '202':
3087
+ description: >
3088
+ The retry job. `Location` names it (`/api/jobs/{jobId}`).
3089
+ content:
3090
+ application/json:
3091
+ schema:
3092
+ $ref: '#/components/schemas/Job'
3093
+ '200':
3094
+ description: Idempotent replay of an identical retry request.
3095
+ content:
3096
+ application/json:
3097
+ schema:
3098
+ $ref: '#/components/schemas/Job'
3099
+ '401':
3100
+ $ref: '#/components/responses/Unauthorized'
3101
+ '402':
3102
+ description: Out of credits (`insufficient_credits`).
3103
+ content:
3104
+ application/json:
3105
+ schema:
3106
+ $ref: '#/components/schemas/Error'
3107
+ '404':
3108
+ $ref: '#/components/responses/NotFound'
3109
+ '409':
3110
+ description: >
3111
+ Trial still live (`trial_not_settled` — QUEUED, RUNNING, and
3112
+ SCORING trials cannot be retried), or `idempotency_key_reused`.
3113
+ content:
3114
+ application/json:
3115
+ schema:
3116
+ $ref: '#/components/schemas/Error'
3117
+ '429':
3118
+ description: >
3119
+ Two conditions, distinguished by code: `rate_limited` (the
3120
+ standard throttle — honor `Retry-After`), and `quota_exceeded`
3121
+ — the owning organization's `max_queued_trials` ceiling would
3122
+ be crossed by this job (`details`: `{quota, limit, used,
3123
+ requested, org}`; the message starts `hosted quota exceeded:`).
3124
+ No `Retry-After`: the wait is not a known number. Nothing was
3125
+ created; retry once the organization's queue has drained, or
3126
+ ask the platform administrator to raise the ceiling. A ceiling
3127
+ of 0 means the organization is paused.
3128
+ content:
3129
+ application/json:
3130
+ schema:
3131
+ $ref: '#/components/schemas/Error'
3132
+ '500':
3133
+ $ref: '#/components/responses/InternalError'
3134
+
3135
+ /api/trials/stop:
3136
+ post:
3137
+ tags: [trials]
3138
+ operationId: stopTrials
3139
+ summary: Stop selected running trials or trace analyses
3140
+ description: |
3141
+ Stops individual in-flight trials without cancelling their job: each
3142
+ trial's sandbox is killed and the trial is settled with its spend read
3143
+ from the gateway. Only the caller's own trials; ids belonging to
3144
+ someone else are reported in `not_found` (existence is never leaked).
3145
+ Idempotent — already-terminal trials are reported as such and left
3146
+ untouched.
3147
+
3148
+ Trace-analysis ids ride the same `trial_ids` array — what each id is
3149
+ gets resolved server-side, so a client never partitions a selection.
3150
+ A running analysis has its analyzer box killed and settles `failed`
3151
+ with failure phase `stopped`, its own spend read from the gateway; a
3152
+ queued one settles the same way before any box boots (nothing minted,
3153
+ nothing spent). Stopped analyses come back in `stopped_analyses` with
3154
+ their settled rows; the caller's already-settled analyses land in
3155
+ `already_terminal` and foreign or unknown ids in `not_found` — the
3156
+ trials' refusal semantics, id for id.
3157
+ requestBody:
3158
+ required: true
3159
+ content:
3160
+ application/json:
3161
+ schema:
3162
+ $ref: '#/components/schemas/StopRequest'
3163
+ responses:
3164
+ '200':
3165
+ description: Per-trial outcome of the stop request.
3166
+ content:
3167
+ application/json:
3168
+ schema:
3169
+ $ref: '#/components/schemas/StopResponse'
3170
+ '400':
3171
+ $ref: '#/components/responses/BadRequest'
3172
+ '401':
3173
+ $ref: '#/components/responses/Unauthorized'
3174
+ '429':
3175
+ $ref: '#/components/responses/TooManyRequests'
3176
+ '500':
3177
+ $ref: '#/components/responses/InternalError'
3178
+
3179
+ # ===========================================================================
3180
+ # Analyses — trace-analysis runs as a first-class list
3181
+ # ===========================================================================
3182
+
3183
+ /api/analyses:
3184
+ get:
3185
+ tags: [analyses]
3186
+ operationId: listAnalyses
3187
+ summary: List trace-analysis runs
3188
+ description: |
3189
+ Every trace analysis the caller may read, newest first, cursor-paged.
3190
+ Items are the same `TrialAnalysis` object a trial serves as its
3191
+ LATEST analysis (`Trial.analysis`) — here every analysis answers,
3192
+ earlier waves included, each carrying the trial, job, and task it
3193
+ judged. Harbor has no analysis list (their analyze writes a local
3194
+ `analysis.json` per trial dir); hosted, the analyzer's run is a
3195
+ record of its own and this is its catalog.
3196
+
3197
+ Visibility follows the analyze verb (`POST /api/jobs/{jobId}/analyze`)
3198
+ one relation deeper: an analysis belongs to the analyzed trial's job.
3199
+ `scope=my` (the default) lists analyses of jobs you created;
3200
+ `scope=shared` lists analyses of your organizations' jobs that
3201
+ teammates created. Nothing is public, so Harbor's `all` is refused.
3202
+ An API key sees exactly what its owner may read — there is no
3203
+ wider scope.
3204
+
3205
+ Filters are server-side and compose with the cursor.
3206
+ parameters:
3207
+ - $ref: '#/components/parameters/Limit'
3208
+ - $ref: '#/components/parameters/Cursor'
3209
+ - $ref: '#/components/parameters/ListScope'
3210
+ - name: job
3211
+ in: query
3212
+ description: Only analyses of this job's trials.
3213
+ schema:
3214
+ type: string
3215
+ format: uuid
3216
+ - name: status
3217
+ in: query
3218
+ description: >
3219
+ Filter by the analysis's own lifecycle word (the lowercase
3220
+ ladder `TrialAnalysis.status` speaks) — repeatable and/or
3221
+ comma-separated; an unknown word is refused (`invalid_input`)
3222
+ with the ladder in `details.valid_statuses`.
3223
+ schema:
3224
+ type: array
3225
+ items:
3226
+ type: string
3227
+ enum: [queued, running, completed, failed]
3228
+ explode: true
3229
+ responses:
3230
+ '200':
3231
+ description: One page of analyses.
3232
+ content:
3233
+ application/json:
3234
+ schema:
3235
+ $ref: '#/components/schemas/AnalysisPage'
3236
+ '400':
3237
+ $ref: '#/components/responses/BadRequest'
3238
+ '401':
3239
+ $ref: '#/components/responses/Unauthorized'
3240
+ '429':
3241
+ $ref: '#/components/responses/TooManyRequests'
3242
+ '500':
3243
+ $ref: '#/components/responses/InternalError'
3244
+
3245
+ /api/analyses/{analysisId}/filesystem:
3246
+ get:
3247
+ tags: [analyses]
3248
+ operationId: getAnalysisFilesystem
3249
+ summary: The analysis run's file system — which source it has
3250
+ description: |
3251
+ The Files surface of one analysis run (the file system family, one shape
3252
+ under every run owner: trials, analyses, task checks — and the task
3253
+ package as a fourth, read-only owner). `state` says what the
3254
+ listing, read, search, archive and change routes will answer from:
3255
+ `live` — the box is running and the routes read it as it changes;
3256
+ `captured` — the run ended and its file system was kept, the same
3257
+ routes read the kept tree; `capturing` — the run just ended and the
3258
+ capture is still being written; `none` — nothing to read yet (a
3259
+ queued run, a run older than the feature, a capture that failed —
3260
+ `capture.status` says which). `box` names the running box and its
3261
+ role, `watcher` how change events arrive (`native` from the
3262
+ provider, `poll` from the folders the viewer declared open), and
3263
+ `capture` the kept tree's record: when, after which phase, how many
3264
+ entries and changed files, `ready` | `incomplete` (`left_out` names
3265
+ what did not fit the budget) | `failed`.
3266
+ parameters:
3267
+ - $ref: '#/components/parameters/AnalysisId'
3268
+ responses:
3269
+ '200':
3270
+ description: The file system's state.
3271
+ content:
3272
+ application/json:
3273
+ schema:
3274
+ $ref: '#/components/schemas/FilesystemStatus'
3275
+ '401':
3276
+ $ref: '#/components/responses/Unauthorized'
3277
+ '404':
3278
+ $ref: '#/components/responses/NotFound'
3279
+ '503':
3280
+ description: >
3281
+ `provider_unreachable` — the box's provider did not answer;
3282
+ retry after `Retry-After`.
3283
+ content:
3284
+ application/json:
3285
+ schema:
3286
+ $ref: '#/components/schemas/Error'
3287
+ '429':
3288
+ $ref: '#/components/responses/TooManyRequests'
3289
+ '500':
3290
+ $ref: '#/components/responses/InternalError'
3291
+
3292
+ /api/analyses/{analysisId}/filesystem/files:
3293
+ get:
3294
+ tags: [analyses]
3295
+ operationId: listAnalysisFilesystemFolder
3296
+ summary: List one folder of the analysis run's file system
3297
+ description: |
3298
+ One page of a folder, sorted by name, paged with `cursor` (the last
3299
+ name served) and `limit` (default 500, max 1000). `source` forces
3300
+ `live` or `capture`; omitted, whichever the run has (`state` on the
3301
+ status route). Every entry carries its type (`dir` | `file` |
3302
+ `symlink` — with `target` — | `other`), size, mtime, mode and owner;
3303
+ `changed` and `phase` say whether the run created or modified it
3304
+ and in which phase (null when not known for the source); on a
3305
+ captured listing `captured: false` marks a file the run never
3306
+ touched, which only the image holds — it lists but does not open.
3307
+ The kernel trees (`/proc`, `/sys`, `/dev`) are never listed.
3308
+ parameters:
3309
+ - $ref: '#/components/parameters/AnalysisId'
3310
+ - $ref: '#/components/parameters/FilesystemPath'
3311
+ - $ref: '#/components/parameters/FilesystemSource'
3312
+ - $ref: '#/components/parameters/FilesystemCursor'
3313
+ - $ref: '#/components/parameters/FilesystemLimit'
3314
+ responses:
3315
+ '200':
3316
+ description: One page of the folder.
3317
+ content:
3318
+ application/json:
3319
+ schema:
3320
+ $ref: '#/components/schemas/FilesystemListing'
3321
+ '400':
3322
+ $ref: '#/components/responses/BadRequest'
3323
+ '401':
3324
+ $ref: '#/components/responses/Unauthorized'
3325
+ '404':
3326
+ $ref: '#/components/responses/NotFound'
3327
+ '409':
3328
+ description: >
3329
+ `filesystem_state` — `source=live` while the box is gone, or
3330
+ `source=capture` before a capture exists (details carry the
3331
+ requested source and the state).
3332
+ content:
3333
+ application/json:
3334
+ schema:
3335
+ $ref: '#/components/schemas/Error'
3336
+ '503':
3337
+ description: >
3338
+ `provider_unreachable` — the box's provider did not answer;
3339
+ retry after `Retry-After`.
3340
+ content:
3341
+ application/json:
3342
+ schema:
3343
+ $ref: '#/components/schemas/Error'
3344
+ '429':
3345
+ $ref: '#/components/responses/TooManyRequests'
3346
+ '500':
3347
+ $ref: '#/components/responses/InternalError'
3348
+
3349
+ /api/analyses/{analysisId}/filesystem/files/{path}:
3350
+ get:
3351
+ tags: [analyses]
3352
+ operationId: readAnalysisFilesystemFile
3353
+ summary: Read one file of the analysis run's file system, raw bytes
3354
+ description: |
3355
+ RAW BYTES of one file — application/octet-stream, byte fidelity, no
3356
+ translation (whether it is text is the reader's call: the first
3357
+ 4 KiB, a NUL byte means binary). A standard single `Range` header
3358
+ (`bytes=a-b`, `bytes=a-`, `bytes=-n`) reads a slice and answers 206
3359
+ with `Content-Range`; `Accept-Ranges: bytes` advertises it; a
3360
+ malformed or multi-range header is the typed 400, an out-of-file
3361
+ range the 416. An unranged read above the deployment's whole-read
3362
+ ceiling is the typed 413 (`invalid_input`, param `Range`, details
3363
+ `size_bytes` / `max_unranged_bytes`) — read it in slices.
3364
+ `X-Source` names the source that answered, `X-Ms` the server time.
3365
+ A symlink reads as its target (one hop). A path the file system
3366
+ does not hold is 404 `not_found`; on a captured read, a file the
3367
+ run never touched is 404 `not_captured` (only the image holds it).
3368
+ A stream that breaks after the headers ends with an aborted
3369
+ connection, never a partial body delivered as success.
3370
+ parameters:
3371
+ - $ref: '#/components/parameters/AnalysisId'
3372
+ - name: path
3373
+ in: path
3374
+ required: true
3375
+ description: The absolute box path, e.g. `app/work/main.py` for `/app/work/main.py` (the leading slash is the route's).
3376
+ schema:
3377
+ type: string
3378
+ - $ref: '#/components/parameters/FilesystemSource'
3379
+ - name: Range
3380
+ in: header
3381
+ description: Optional single byte range (`bytes=a-b` / `bytes=a-` / `bytes=-n`).
3382
+ schema:
3383
+ type: string
3384
+ responses:
3385
+ '200':
3386
+ description: The whole file (`X-Source`, `X-Ms`).
3387
+ content:
3388
+ application/octet-stream:
3389
+ schema:
3390
+ type: string
3391
+ format: binary
3392
+ '206':
3393
+ description: The requested byte range (`Content-Range` states position and size).
3394
+ content:
3395
+ application/octet-stream:
3396
+ schema:
3397
+ type: string
3398
+ format: binary
3399
+ '400':
3400
+ $ref: '#/components/responses/BadRequest'
3401
+ '401':
3402
+ $ref: '#/components/responses/Unauthorized'
3403
+ '404':
3404
+ $ref: '#/components/responses/NotFound'
3405
+ '409':
3406
+ description: >
3407
+ `filesystem_state` — `source=live` while the box is gone, or
3408
+ `source=capture` before a capture exists (details carry the
3409
+ requested source and the state).
3410
+ content:
3411
+ application/json:
3412
+ schema:
3413
+ $ref: '#/components/schemas/Error'
3414
+ '413':
3415
+ description: >
3416
+ An unranged read above the whole-read ceiling (`invalid_input`,
3417
+ param `Range`; details `size_bytes` / `max_unranged_bytes`).
3418
+ content:
3419
+ application/json:
3420
+ schema:
3421
+ $ref: '#/components/schemas/Error'
3422
+ '416':
3423
+ description: >
3424
+ Range selects nothing inside the file (`invalid_input`;
3425
+ `Content-Range` carries `bytes */<size>`).
3426
+ content:
3427
+ application/json:
3428
+ schema:
3429
+ $ref: '#/components/schemas/Error'
3430
+ '503':
3431
+ description: >
3432
+ `provider_unreachable` — the box's provider did not answer;
3433
+ retry after `Retry-After`.
3434
+ content:
3435
+ application/json:
3436
+ schema:
3437
+ $ref: '#/components/schemas/Error'
3438
+ '429':
3439
+ $ref: '#/components/responses/TooManyRequests'
3440
+ '500':
3441
+ $ref: '#/components/responses/InternalError'
3442
+
3443
+ /api/analyses/{analysisId}/filesystem/search:
3444
+ get:
3445
+ tags: [analyses]
3446
+ operationId: searchAnalysisFilesystem
3447
+ summary: Search the analysis run's file system for text
3448
+ description: |
3449
+ Content search: `q` is the text (or, with `regex=true`, the
3450
+ pattern), `path` the folder to search under (default `/` — the
3451
+ whole box, `scope: "box"`, which can take seconds; a folder answers
3452
+ `scope: "path"` in well under a second), `limit` the hit cap
3453
+ (default 200, max 1000). `truncated: true` means more hits exist or
3454
+ the search budget ran out — narrow the path or the text. Live: the
3455
+ box is searched as it is; captured: the kept text is searched, and
3456
+ `image_files_excluded: true` says files the run never touched were
3457
+ not searchable. Binary files never hit.
3458
+ parameters:
3459
+ - $ref: '#/components/parameters/AnalysisId'
3460
+ - name: q
3461
+ in: query
3462
+ required: true
3463
+ description: The text to find; a regular expression with `regex=true`.
3464
+ schema:
3465
+ type: string
3466
+ - $ref: '#/components/parameters/FilesystemPath'
3467
+ - name: regex
3468
+ in: query
3469
+ description: Treat `q` as a regular expression (default false — a plain substring).
3470
+ schema:
3471
+ type: boolean
3472
+ default: false
3473
+ - $ref: '#/components/parameters/FilesystemLimit'
3474
+ - $ref: '#/components/parameters/FilesystemSource'
3475
+ responses:
3476
+ '200':
3477
+ description: The hits found.
3478
+ content:
3479
+ application/json:
3480
+ schema:
3481
+ $ref: '#/components/schemas/FilesystemSearchResult'
3482
+ '400':
3483
+ $ref: '#/components/responses/BadRequest'
3484
+ '401':
3485
+ $ref: '#/components/responses/Unauthorized'
3486
+ '404':
3487
+ $ref: '#/components/responses/NotFound'
3488
+ '409':
3489
+ description: >
3490
+ `filesystem_state` — `source=live` while the box is gone, or
3491
+ `source=capture` before a capture exists (details carry the
3492
+ requested source and the state).
3493
+ content:
3494
+ application/json:
3495
+ schema:
3496
+ $ref: '#/components/schemas/Error'
3497
+ '503':
3498
+ description: >
3499
+ `provider_unreachable` — the box's provider did not answer;
3500
+ retry after `Retry-After`.
3501
+ content:
3502
+ application/json:
3503
+ schema:
3504
+ $ref: '#/components/schemas/Error'
3505
+ '429':
3506
+ $ref: '#/components/responses/TooManyRequests'
3507
+ '500':
3508
+ $ref: '#/components/responses/InternalError'
3509
+
3510
+ /api/analyses/{analysisId}/filesystem/events:
3511
+ get:
3512
+ tags: [analyses]
3513
+ operationId: streamAnalysisFilesystemEvents
3514
+ summary: Change events while the analysis run's box lives (SSE)
3515
+ description: |
3516
+ Server-sent events. `event: state` first (`{state, box}`), then
3517
+ `event: fs` per change (`{seq, t, path, type: create|write|remove|
3518
+ rename, source: watch|poll}`, `id: <seq>`), `event: ping` every
3519
+ 15 s. Resume with `Last-Event-ID: <seq>` (or `?after=`): the events
3520
+ after it replay when the server still holds them; when it does not,
3521
+ a fresh `state` frame is the signal to relist the open folders.
3522
+ On a provider without a native watcher (`watcher: "poll"` on the
3523
+ status route) events come from polling the folders declared with
3524
+ `POST …/filesystem/watch`, so declare them. A `state` frame follows
3525
+ every change of state; the stream ends once the file system settles
3526
+ (`captured` or `none`).
3527
+ parameters:
3528
+ - $ref: '#/components/parameters/AnalysisId'
3529
+ - name: Last-Event-ID
3530
+ in: header
3531
+ description: Resume after this event seq.
3532
+ schema:
3533
+ type: string
3534
+ - name: after
3535
+ in: query
3536
+ description: The same as `Last-Event-ID`, for clients that cannot set headers.
3537
+ schema:
3538
+ type: string
3539
+ responses:
3540
+ '200':
3541
+ description: The event stream.
3542
+ content:
3543
+ text/event-stream:
3544
+ schema:
3545
+ type: string
3546
+ '400':
3547
+ $ref: '#/components/responses/BadRequest'
3548
+ '401':
3549
+ $ref: '#/components/responses/Unauthorized'
3550
+ '404':
3551
+ $ref: '#/components/responses/NotFound'
3552
+ '503':
3553
+ description: >
3554
+ `provider_unreachable` — the box's provider did not answer;
3555
+ retry after `Retry-After`.
3556
+ content:
3557
+ application/json:
3558
+ schema:
3559
+ $ref: '#/components/schemas/Error'
3560
+ '429':
3561
+ $ref: '#/components/responses/TooManyRequests'
3562
+ '500':
3563
+ $ref: '#/components/responses/InternalError'
3564
+
3565
+ /api/analyses/{analysisId}/filesystem/watch:
3566
+ post:
3567
+ tags: [analyses]
3568
+ operationId: watchAnalysisFilesystem
3569
+ summary: Declare the folders the viewer has open
3570
+ description: |
3571
+ The folders whose changes the event stream must report on a
3572
+ provider without a native watcher (`watcher: "poll"`): idempotent —
3573
+ the body's `paths` REPLACES the set; an empty set stops polling. At
3574
+ most 8 folders at once (a bigger set is the typed 400 naming the
3575
+ bound). On a provider with a native watcher the set is accepted
3576
+ and unused. Refused with `filesystem_state` when the box is gone.
3577
+ parameters:
3578
+ - $ref: '#/components/parameters/AnalysisId'
3579
+ requestBody:
3580
+ required: true
3581
+ content:
3582
+ application/json:
3583
+ schema:
3584
+ $ref: '#/components/schemas/FilesystemWatchRequest'
3585
+ responses:
3586
+ '200':
3587
+ description: The set in force.
3588
+ content:
3589
+ application/json:
3590
+ schema:
3591
+ $ref: '#/components/schemas/FilesystemWatchResult'
3592
+ '400':
3593
+ $ref: '#/components/responses/BadRequest'
3594
+ '401':
3595
+ $ref: '#/components/responses/Unauthorized'
3596
+ '404':
3597
+ $ref: '#/components/responses/NotFound'
3598
+ '409':
3599
+ description: >
3600
+ `filesystem_state` — `source=live` while the box is gone, or
3601
+ `source=capture` before a capture exists (details carry the
3602
+ requested source and the state).
3603
+ content:
3604
+ application/json:
3605
+ schema:
3606
+ $ref: '#/components/schemas/Error'
3607
+ '503':
3608
+ description: >
3609
+ `provider_unreachable` — the box's provider did not answer;
3610
+ retry after `Retry-After`.
3611
+ content:
3612
+ application/json:
3613
+ schema:
3614
+ $ref: '#/components/schemas/Error'
3615
+ '429':
3616
+ $ref: '#/components/responses/TooManyRequests'
3617
+ '500':
3618
+ $ref: '#/components/responses/InternalError'
3619
+
3620
+ /api/analyses/{analysisId}/filesystem/changes:
3621
+ get:
3622
+ tags: [analyses]
3623
+ operationId: listAnalysisFilesystemChanges
3624
+ summary: The files the analysis run changed
3625
+ description: |
3626
+ The flat list of what the run created, modified or removed since its
3627
+ box started, with `phase` (`setup` | `agent` | `verifier`); `total`
3628
+ and `changed_bytes` are the whole list's, `items` one page (`cursor`
3629
+ = an opaque position, `limit` default 500, max 1000); `phase=`
3630
+ narrows to one phase. Captured: stored with the kept tree. Live: the
3631
+ box start listing against a fresh one, seconds per call; a box whose
3632
+ start listing was not stored is the typed 422.
3633
+ parameters:
3634
+ - $ref: '#/components/parameters/AnalysisId'
3635
+ - $ref: '#/components/parameters/FilesystemSource'
3636
+ - name: phase
3637
+ in: query
3638
+ description: Only changes of one phase (default all).
3639
+ schema:
3640
+ type: string
3641
+ enum: [setup, agent, verifier, all]
3642
+ default: all
3643
+ - $ref: '#/components/parameters/FilesystemCursor'
3644
+ - $ref: '#/components/parameters/FilesystemLimit'
3645
+ responses:
3646
+ '200':
3647
+ description: One page of the changed-files list.
3648
+ content:
3649
+ application/json:
3650
+ schema:
3651
+ $ref: '#/components/schemas/FilesystemChanges'
3652
+ '400':
3653
+ $ref: '#/components/responses/BadRequest'
3654
+ '401':
3655
+ $ref: '#/components/responses/Unauthorized'
3656
+ '404':
3657
+ $ref: '#/components/responses/NotFound'
3658
+ '409':
3659
+ description: >
3660
+ `filesystem_state` — `source=live` while the box is gone, or
3661
+ `source=capture` before a capture exists (details carry the
3662
+ requested source and the state).
3663
+ content:
3664
+ application/json:
3665
+ schema:
3666
+ $ref: '#/components/schemas/Error'
3667
+ '422':
3668
+ description: >
3669
+ `feature_unsupported` — the shape or the provider has no live view
3670
+ of what was asked (details.cause names it).
3671
+ content:
3672
+ application/json:
3673
+ schema:
3674
+ $ref: '#/components/schemas/Error'
3675
+ '503':
3676
+ description: >
3677
+ `provider_unreachable` — the box's provider did not answer;
3678
+ retry after `Retry-After`.
3679
+ content:
3680
+ application/json:
3681
+ schema:
3682
+ $ref: '#/components/schemas/Error'
3683
+ '429':
3684
+ $ref: '#/components/responses/TooManyRequests'
3685
+ '500':
3686
+ $ref: '#/components/responses/InternalError'
3687
+
3688
+ /api/analyses/{analysisId}/filesystem/archive:
3689
+ get:
3690
+ tags: [analyses]
3691
+ operationId: downloadAnalysisFilesystemArchive
3692
+ summary: Download one subtree of the analysis run's file system as a .tar.gz
3693
+ description: |
3694
+ A gzipped tarball of the subtree under `path` (default `/`, the
3695
+ whole tree), streamed. Captured: only the kept bytes ride; the
3696
+ files the run never touched are listed in `MANIFEST.tsv` at the
3697
+ archive root with `captured=false`. Live: read out of the running
3698
+ box under the capture budget — a subtree too large to leave the box
3699
+ inside it is refused up front, 422 `feature_unsupported` with the
3700
+ size in `details.cause`; a read-out that still stalls past the
3701
+ budget aborts the connection, never a truncated archive delivered
3702
+ as success. `X-Source` names the source. A folder the file system
3703
+ does not hold is 404 `not_found`.
3704
+ parameters:
3705
+ - $ref: '#/components/parameters/AnalysisId'
3706
+ - $ref: '#/components/parameters/FilesystemPath'
3707
+ - $ref: '#/components/parameters/FilesystemSource'
3708
+ responses:
3709
+ '200':
3710
+ description: The archive (`Content-Disposition` names it `<id>_filesystem.tar.gz`).
3711
+ content:
3712
+ application/gzip:
3713
+ schema:
3714
+ type: string
3715
+ format: binary
3716
+ '400':
3717
+ $ref: '#/components/responses/BadRequest'
3718
+ '401':
3719
+ $ref: '#/components/responses/Unauthorized'
3720
+ '404':
3721
+ $ref: '#/components/responses/NotFound'
3722
+ '409':
3723
+ description: >
3724
+ `filesystem_state` — `source=live` while the box is gone, or
3725
+ `source=capture` before a capture exists (details carry the
3726
+ requested source and the state).
3727
+ content:
3728
+ application/json:
3729
+ schema:
3730
+ $ref: '#/components/schemas/Error'
3731
+ '422':
3732
+ description: >
3733
+ `feature_unsupported` — the provider has no live view of what was
3734
+ asked, or the live subtree cannot leave the box inside the
3735
+ capture budget (details.cause names it, with the size).
3736
+ content:
3737
+ application/json:
3738
+ schema:
3739
+ $ref: '#/components/schemas/Error'
3740
+ '503':
3741
+ description: >
3742
+ `provider_unreachable` — the box's provider did not answer;
3743
+ retry after `Retry-After`.
3744
+ content:
3745
+ application/json:
3746
+ schema:
3747
+ $ref: '#/components/schemas/Error'
3748
+ '429':
3749
+ $ref: '#/components/responses/TooManyRequests'
3750
+ '500':
3751
+ $ref: '#/components/responses/InternalError'
3752
+
3753
+ /api/analyses/{analysisId}/logs:
3754
+ get:
3755
+ tags: [analyses]
3756
+ operationId: getAnalysisSandboxLog
3757
+ summary: One page of a sandbox log stream of the analysis run
3758
+ description: |
3759
+ The box's own streams, recorded while it runs and kept after:
3760
+ `agent` (the harness's stdout/stderr), `verifier` (the verifier
3761
+ command's) and `system` (the box's system log — only when the job
3762
+ asked for it, `JobCreate.system_log`). `setup` and `metrics` are
3763
+ named but not recorded today. Lines are `{seq, t, fd: out|err, line}`;
3764
+ `cursor` is an opaque position, `limit` default 1000, max 1000. A
3765
+ stream the platform holds nothing for answers an empty page whose
3766
+ `reason` says why — never an error.
3767
+ parameters:
3768
+ - $ref: '#/components/parameters/AnalysisId'
3769
+ - name: stream
3770
+ in: query
3771
+ required: true
3772
+ schema:
3773
+ $ref: '#/components/schemas/SandboxLogStream'
3774
+ - $ref: '#/components/parameters/FilesystemCursor'
3775
+ - $ref: '#/components/parameters/FilesystemLimit'
3776
+ responses:
3777
+ '200':
3778
+ description: One page of the stream.
3779
+ content:
3780
+ application/json:
3781
+ schema:
3782
+ $ref: '#/components/schemas/SandboxLogLines'
3783
+ '400':
3784
+ $ref: '#/components/responses/BadRequest'
3785
+ '401':
3786
+ $ref: '#/components/responses/Unauthorized'
3787
+ '404':
3788
+ $ref: '#/components/responses/NotFound'
3789
+ '503':
3790
+ description: >
3791
+ `provider_unreachable` — the box's provider did not answer;
3792
+ retry after `Retry-After`.
3793
+ content:
3794
+ application/json:
3795
+ schema:
3796
+ $ref: '#/components/schemas/Error'
3797
+ '429':
3798
+ $ref: '#/components/responses/TooManyRequests'
3799
+ '500':
3800
+ $ref: '#/components/responses/InternalError'
3801
+
3802
+ /api/analyses/{analysisId}/logs/events:
3803
+ get:
3804
+ tags: [analyses]
3805
+ operationId: streamAnalysisSandboxLogs
3806
+ summary: Sandbox log lines as they arrive (SSE)
3807
+ description: |
3808
+ Server-sent events over every stream at once: `event: line`
3809
+ (`{stream, seq, t, fd, line}`, `id: <stream>:<seq>`), `event: state`
3810
+ on open and on every change of the box's state, `event: ping` every
3811
+ 15 s. Without
3812
+ `Last-Event-ID` every stream replays from its beginning and then
3813
+ follows; with `Last-Event-ID: <stream>:<seq>` that stream resumes
3814
+ after the seq the paged GET handed out. Once the box is gone and
3815
+ every recorded line has been sent, the stream ends — the paged GET
3816
+ is the reader after that.
3817
+ parameters:
3818
+ - $ref: '#/components/parameters/AnalysisId'
3819
+ - name: Last-Event-ID
3820
+ in: header
3821
+ description: Resume one stream after `<stream>:<seq>`.
3822
+ schema:
3823
+ type: string
3824
+ responses:
3825
+ '200':
3826
+ description: The event stream.
3827
+ content:
3828
+ text/event-stream:
3829
+ schema:
3830
+ type: string
3831
+ '400':
3832
+ $ref: '#/components/responses/BadRequest'
3833
+ '401':
3834
+ $ref: '#/components/responses/Unauthorized'
3835
+ '404':
3836
+ $ref: '#/components/responses/NotFound'
3837
+ '503':
3838
+ description: >
3839
+ `provider_unreachable` — the box's provider did not answer;
3840
+ retry after `Retry-After`.
3841
+ content:
3842
+ application/json:
3843
+ schema:
3844
+ $ref: '#/components/schemas/Error'
3845
+ '429':
3846
+ $ref: '#/components/responses/TooManyRequests'
3847
+ '500':
3848
+ $ref: '#/components/responses/InternalError'
3849
+
3850
+ /api/analyses/{analysisId}/procs:
3851
+ get:
3852
+ tags: [analyses]
3853
+ operationId: getAnalysisProcs
3854
+ summary: The analysis run's box process list (live only)
3855
+ description: |
3856
+ The processes running in the box right now, as one text listing
3857
+ (`ps` sorted by memory; on an image without `ps`, the same table
3858
+ read from `/proc`, its first line saying so). Live only: once the
3859
+ box is gone the answer is 409 `filesystem_state`.
3860
+ parameters:
3861
+ - $ref: '#/components/parameters/AnalysisId'
3862
+ responses:
3863
+ '200':
3864
+ description: The listing.
3865
+ content:
3866
+ application/json:
3867
+ schema:
3868
+ $ref: '#/components/schemas/SandboxProcs'
3869
+ '401':
3870
+ $ref: '#/components/responses/Unauthorized'
3871
+ '404':
3872
+ $ref: '#/components/responses/NotFound'
3873
+ '409':
3874
+ description: >
3875
+ `filesystem_state` — `source=live` while the box is gone, or
3876
+ `source=capture` before a capture exists (details carry the
3877
+ requested source and the state).
3878
+ content:
3879
+ application/json:
3880
+ schema:
3881
+ $ref: '#/components/schemas/Error'
3882
+ '503':
3883
+ description: >
3884
+ `provider_unreachable` — the box's provider did not answer;
3885
+ retry after `Retry-After`.
3886
+ content:
3887
+ application/json:
3888
+ schema:
3889
+ $ref: '#/components/schemas/Error'
3890
+ '429':
3891
+ $ref: '#/components/responses/TooManyRequests'
3892
+ '500':
3893
+ $ref: '#/components/responses/InternalError'
3894
+
3895
+
2557
3896
  /api/analyses/{analysisId}/download:
2558
3897
  get:
2559
- tags: [analyses]
2560
- operationId: downloadAnalysis
2561
- summary: Download one analysis run as Harbor's wrapper-trial folder
3898
+ tags: [analyses]
3899
+ operationId: downloadAnalysis
3900
+ summary: Download one analysis run as Harbor's wrapper-trial folder
3901
+ description: |
3902
+ One `.tar.gz` holding the analysis run laid out exactly as the
3903
+ trial directory Harbor's `harbor analyze` leaves on disk for it —
3904
+ the WRAPPER trial (their analyze/analyzer.py assemble_analyze_task
3905
+ wraps one analyzed trial in a generated task named
3906
+ `analyze-<trial dir>` and runs it as a Harbor trial; the folder is
3907
+ that trial's, models/trial/paths.py TrialPaths). The archive
3908
+ extracts to one directory named as Harbor names a trial
3909
+ (`<task name>__<7 chars>`, here `analyze-<analyzed trial dir>__…`):
3910
+
3911
+ analyze-<trial>__<7>/
3912
+ ├── config.json TrialConfig, partial: the wrapper
3913
+ │ task's name, the agent (claude-code,
3914
+ │ the model), and under `x_evolve`
3915
+ │ the frozen policy — rubric, prompt,
3916
+ │ reasoning_effort — plus the analyzed
3917
+ │ trial/job/task and the provider
3918
+ ├── lock.json TrialLock, partial: the agent and
3919
+ │ its model (no task digest exists —
3920
+ │ the wrapper task is never assembled
3921
+ │ here; see the x_evolve note below)
3922
+ ├── result.json TrialResult: the wrapper name,
3923
+ │ agent_info, agent_result (tokens,
3924
+ │ measured cost), verifier_result
3925
+ │ (reward 1 = a valid analysis.json,
3926
+ │ 0 = the validator refused it),
3927
+ │ exception_info on an infrastructure
3928
+ │ failure, the timings, and under
3929
+ │ `x_evolve` the run's own facts
3930
+ │ (status, attempts, the typed failure,
3931
+ │ the box, the meter, and the task
3932
+ │ package's fate — `taskContent`
3933
+ │ retained_package|absent, `taskAbsentReason`
3934
+ │ when absent)
3935
+ ├── trial.log the worker's phase log, rendered
3936
+ │ deterministically (the trial tree's
3937
+ │ own renderer)
3938
+ ├── exception.txt when the run failed OUTSIDE the
3939
+ │ validator (an infrastructure phase)
3940
+ ├── agent/
3941
+ │ ├── claude-code.txt the analyzer's stdout stream at
3942
+ │ │ Harbor's own tee name for claude-code
3943
+ │ ├── stderr.log its stderr, when stored
3944
+ │ ├── trace-parsed.jsonl the run's parsed event trace
3945
+ │ ├── .claude/ … the captured home at its real names,
3946
+ │ ├── agent-home.json the capture record beside it
3947
+ │ └── sessions/ … Harbor's own copy of the .claude/
3948
+ │ subtree (claude_code.py:482)
3949
+ ├── verifier/ ONLY when the validator ruled —
3950
+ │ │ completed, or failed `invalid_result`
3951
+ │ ├── test-stdout.txt the validator's printed reasons
3952
+ │ │ (empty on a valid result, as
3953
+ │ │ Harbor's validate.py prints nothing)
3954
+ │ ├── reward.txt `1` or `0` — the exact bytes Harbor's
3955
+ │ │ wrapper test.sh writes
3956
+ │ └── reward.json {"reward": 1|0}
3957
+ └── artifacts/
3958
+ ├── manifest.json always present — what was collected
3959
+ │ (`[]` when the run produced nothing)
3960
+ └── analysis.json the validated deliverable — summary
3961
+ + checks — only on a completed run
3962
+
3963
+ The validator runs on the platform's worker, not inside the box
3964
+ (a recorded deviation: the rule is the platform's one result
3965
+ definition, which the box's validate.py records; the reasons are that
3966
+ definition's issues, one per line as `path: message`); the run's raw
3967
+ deliverable is not retained, so
3968
+ `artifacts/analysis.json` is the validated document in canonical
3969
+ JSON and a refused deliverable leaves no file. The image digest and
3970
+ the harness bundle version resolve per run but are not stored on
3971
+ the row, so `lock.json` cannot state them (capture-side, deferred).
3972
+ Data the platform does not hold is left out, never faked.
3973
+
3974
+ Deterministic: re-downloads are byte-identical (stored rows only,
3975
+ canonical JSON, fixed tar mtimes, zero gzip mtime). Readable by the
3976
+ analyzed job's creator and the owning organization's members;
3977
+ anyone else gets 404 `analysis_not_found`, indistinguishable from
3978
+ an id that does not exist. A run still queued or running refuses
3979
+ 409 `analysis_not_terminal` — the folder is a record of a settled
3980
+ run.
3981
+
3982
+ `evolve analysis download <analysis-id>` extracts it and adds
3983
+ `evolve.json` (the platform record Harbor's layout has no slot for).
3984
+ parameters:
3985
+ - $ref: '#/components/parameters/AnalysisId'
3986
+ responses:
3987
+ '200':
3988
+ description: The archive.
3989
+ content:
3990
+ application/gzip:
3991
+ schema:
3992
+ type: string
3993
+ format: binary
3994
+ '401':
3995
+ $ref: '#/components/responses/Unauthorized'
3996
+ '404':
3997
+ $ref: '#/components/responses/NotFound'
3998
+ '409':
3999
+ description: The analysis is still queued or running (`analysis_not_terminal`).
4000
+ content:
4001
+ application/json:
4002
+ schema:
4003
+ $ref: '#/components/schemas/Error'
4004
+ '429':
4005
+ $ref: '#/components/responses/TooManyRequests'
4006
+ '500':
4007
+ $ref: '#/components/responses/InternalError'
4008
+
4009
+ # ===========================================================================
4010
+ # Checks — task quality checks (Harbor's `harbor check`, hosted)
4011
+ # ===========================================================================
4012
+
4013
+ /api/checks:
4014
+ post:
4015
+ tags: [checks]
4016
+ operationId: createCheck
4017
+ summary: Check task quality against a rubric (Harbor's `harbor check`)
4018
+ description: |
4019
+ Harbor's `harbor check <PATH>` (their cli/analyze.py:84-207
4020
+ check_command; analyze/checker.py run_checks), hosted: PATH — one
4021
+ task directory, or a directory of task directories — arrives as a
4022
+ gzipped tarball (`archive`), or a published dataset is named
4023
+ instead (`dataset`, the hosted form; `CheckRequest.dataset` states
4024
+ the deviation and its reason), and for each task directory it holds
4025
+ the checker agent (claude-code, Harbor's own check agent, in its own
4026
+ sandbox, under the network Harbor's check wrapper task gives it —
4027
+ open by default) reads EVERY file of the task and rules every rubric
4028
+ criterion `pass`, `fail`, `not_applicable` or `unknown` with a
4029
+ rationale and the evidence behind it, writing `check-result.json`
4030
+ (the platform's default check prompt and rubric, rendered through
4031
+ Harbor's own tokens and join; `prompt` and `rubric` replace them).
4032
+ **The response is the Check**
4033
+ (202): the record with one `results` entry per task, each `queued`
4034
+ — poll `GET /api/checks/{checkId}` to watch them settle (the
4035
+ `status` of the check reads `completed` once every task has).
4036
+
4037
+ Which task directories, exactly as Harbor decides it
4038
+ (checker.py:116-141 over the archive's listing): the archive root
4039
+ when it is a task directory (task.toml + environment/ +
4040
+ instruction.md + the test script of its `[environment].os` —
4041
+ tests/test.sh for linux, the default; tests/test.bat for windows;
4042
+ a `[[steps]]` task needs each step's instruction.md and its own or
4043
+ the shared test script — their Task.is_valid_dir), else every
4044
+ top-level directory that is one, sorted; then the
4045
+ `include_task_names` globs (any match keeps), the
4046
+ `exclude_task_names` globs (any match drops), then the first
4047
+ `n_tasks`. An empty selection is refused `no_checkable_tasks`
4048
+ (their "No valid task directories found", :140); nothing is stored.
4049
+ There is no task-count ceiling — Harbor's check has none (no
4050
+ invented number, owner 2026-09-13); the organization's
4051
+ `max_concurrent_analyses` paces the boxes. On the dataset form the
4052
+ same selection runs over the version's task names, and the same
4053
+ refusal answers. The `config`
4054
+ part is the JSON `CheckConfigInput`; absent or
4055
+ `{}` means the defaults (openrouter/deepseek/deepseek-v4.1-flash at its
4056
+ per-model effort, the platform's default check rubric — eleven
4057
+ criteria, `CheckConfigInput.rubric` names them).
4058
+
4059
+ The task bytes are never modified and never land anywhere but the
4060
+ checker's sandbox and the platform's rows: Harbor's own rule ("Do
4061
+ not modify any files under {task_path}"; nothing written into the
4062
+ reviewed directory — their report is the job dir's
4063
+ check_report.json). A checker run that completes without a valid
4064
+ `check-result.json` is automatically re-run at most once (fresh
4065
+ sandbox, same model and rubric; the task's `attempts` says how far
4066
+ the ladder went), the analyze verb's own hosted rule; a run cut by
4067
+ its budget settles `failed` with phase `timeout` at once and is
4068
+ never re-run.
4069
+
4070
+ Decision order: 400 `invalid_multipart` (not multipart, or a
4071
+ malformed body) → 413 `upload_too_large` (the archive over what the
4072
+ store can land or what the server's spool disk has free — both read
4073
+ live at the request, the refusal naming which; no platform number)
4074
+ → 429
4075
+ `too_many_concurrent_check_uploads` (the server's spool bound,
4076
+ ruled at the archive part before its first byte) → 400
4077
+ `invalid_rubric` / `invalid_input` (the `config` part: the rubric
4078
+ grammar; then an off-roster model, a bad `prompt`, an off-lineup
4079
+ `sandbox_provider`, an off-vocabulary `reasoning_effort`, an
4080
+ out-of-range `n_concurrent`, a malformed glob list, an invalid
4081
+ `n_tasks` — in that order, the analyze door's own ladder under the
4082
+ `check.` prefix) → 400 `invalid_input` (both `archive` and `dataset`,
4083
+ or neither) → the dataset form's resolution refusals
4084
+ (`CheckRequest.dataset`) → 400 `invalid_archive` (not a readable gzipped
4085
+ tar, an unsafe entry, or a listing that would hold more heap than
4086
+ the server can still commit — read live, named in the refusal) →
4087
+ 400 `no_checkable_tasks` → 202.
4088
+ requestBody:
4089
+ required: true
4090
+ content:
4091
+ multipart/form-data:
4092
+ schema:
4093
+ $ref: '#/components/schemas/CheckRequest'
4094
+ responses:
4095
+ '202':
4096
+ description: The check, its task checks queued.
4097
+ content:
4098
+ application/json:
4099
+ schema:
4100
+ $ref: '#/components/schemas/Check'
4101
+ '400':
4102
+ $ref: '#/components/responses/BadRequest'
4103
+ '401':
4104
+ $ref: '#/components/responses/Unauthorized'
4105
+ '413':
4106
+ description: The archive is over the physical ceiling read at the request — the store's, or the spool disk's (`upload_too_large`; details.max_bytes and details.source).
4107
+ content:
4108
+ application/json:
4109
+ schema:
4110
+ $ref: '#/components/schemas/Error'
4111
+ '429':
4112
+ description: >
4113
+ Two conditions, distinguished by code: `rate_limited` (the
4114
+ standard throttle — honor `Retry-After`), and
4115
+ `too_many_concurrent_check_uploads` — the server is already
4116
+ spooling its bound of concurrent check archives
4117
+ (`max_concurrent` in the details), refused before the first
4118
+ uploaded byte, with no `Retry-After` because the server cannot
4119
+ honestly compute one; retry when an in-flight upload finishes.
4120
+ content:
4121
+ application/json:
4122
+ schema:
4123
+ $ref: '#/components/schemas/Error'
4124
+ '500':
4125
+ $ref: '#/components/responses/InternalError'
4126
+ get:
4127
+ tags: [checks]
4128
+ operationId: listChecks
4129
+ summary: List task quality checks
2562
4130
  description: |
2563
- One `.tar.gz` holding the analysis run laid out exactly as the
2564
- trial directory Harbor's `harbor analyze` leaves on disk for it
2565
- the WRAPPER trial (their analyze/analyzer.py assemble_analyze_task
2566
- wraps one analyzed trial in a generated task named
2567
- `analyze-<trial dir>` and runs it as a Harbor trial; the folder is
2568
- that trial's, models/trial/paths.py TrialPaths). The archive
2569
- extracts to one directory named as Harbor names a trial
2570
- (`<task name>__<7 chars>`, here `analyze-<analyzed trial dir>__…`):
4131
+ Every check the caller may read, newest first, cursor-paged — each
4132
+ item the full `Check` with its per-task results. Harbor has no
4133
+ check list (their check writes a local report per invocation);
4134
+ hosted, the run is a record of its own and this is its catalog.
4135
+ `scope=my` (the default) lists checks you created; `scope=shared`
4136
+ lists your organizations' checks that teammates created (the
4137
+ analyses list's own law, on the check's own org). Nothing is
4138
+ public, so Harbor's `all` is refused. `status` filters by the
4139
+ check's own ladder (`queued`, `running`, `completed`), repeatable
4140
+ and/or comma-separated; an unknown word is refused
4141
+ `invalid_input` with the ladder in `details.valid_statuses`.
4142
+ `dataset` narrows to the checks of one published dataset (the
4143
+ dataset form's `source.dataset`): `name` spans every version of
4144
+ it, `name@version` is one version — the spelling `CheckRequest.
4145
+ dataset` takes. Archive checks never match. A dataset the caller
4146
+ has no checks of is an ordinary empty page; a malformed value
4147
+ (an empty half around the `@`) is refused `invalid_input` on
4148
+ `dataset`.
4149
+ parameters:
4150
+ - $ref: '#/components/parameters/Limit'
4151
+ - $ref: '#/components/parameters/Cursor'
4152
+ - $ref: '#/components/parameters/ListScope'
4153
+ - name: status
4154
+ in: query
4155
+ schema:
4156
+ type: array
4157
+ items:
4158
+ type: string
4159
+ enum: [queued, running, completed]
4160
+ explode: true
4161
+ - name: dataset
4162
+ in: query
4163
+ description: >
4164
+ Only checks of this published dataset — `name` (every version)
4165
+ or `name@version` (one version), exact on the check's
4166
+ `source.dataset`.
4167
+ schema:
4168
+ type: string
4169
+ responses:
4170
+ '200':
4171
+ description: One page of checks.
4172
+ content:
4173
+ application/json:
4174
+ schema:
4175
+ $ref: '#/components/schemas/CheckPage'
4176
+ '400':
4177
+ $ref: '#/components/responses/BadRequest'
4178
+ '401':
4179
+ $ref: '#/components/responses/Unauthorized'
4180
+ '429':
4181
+ $ref: '#/components/responses/TooManyRequests'
4182
+ '500':
4183
+ $ref: '#/components/responses/InternalError'
2571
4184
 
2572
- analyze-<trial>__<7>/
2573
- ├── config.json TrialConfig, partial: the wrapper
2574
- │ task's name, the agent (claude-code,
2575
- │ the model), and under `x_evolve`
2576
- │ the frozen policy rubric, prompt,
2577
- │ reasoning_effort — plus the analyzed
2578
- │ trial/job/task and the provider
2579
- ├── lock.json TrialLock, partial: the agent and
2580
- │ its model (no task digest exists
2581
- │ the wrapper task is never assembled
2582
- │ here; see the x_evolve note below)
2583
- ├── result.json TrialResult: the wrapper name,
2584
- │ agent_info, agent_result (tokens,
2585
- │ measured cost), verifier_result
2586
- (reward 1 = a valid analysis.json,
2587
- │ 0 = the validator refused it),
2588
- │ exception_info on an infrastructure
2589
- │ failure, the timings, and under
2590
- `x_evolve` the run's own facts
2591
- │ (status, attempts, the typed failure,
2592
- the box, the meter, and the task
2593
- │ package's fate `taskContent`
2594
- │ retained_package|absent, `taskAbsentReason`
2595
- │ when absent)
2596
- ├── trial.log the worker's phase log, rendered
2597
- │ deterministically (the trial tree's
2598
- │ own renderer)
2599
- ├── exception.txt when the run failed OUTSIDE the
2600
- │ validator (an infrastructure phase)
2601
- ├── agent/
2602
- │ ├── claude-code.txt the analyzer's stdout stream at
2603
- │ │ Harbor's own tee name for claude-code
2604
- │ ├── stderr.log its stderr, when stored
2605
- │ ├── trace-parsed.jsonl the run's parsed event trace
2606
- │ ├── .claude/ … the captured home at its real names,
2607
- │ ├── agent-home.json the capture record beside it
2608
- │ └── sessions/ … Harbor's own copy of the .claude/
2609
- │ subtree (claude_code.py:482)
2610
- ├── verifier/ ONLY when the validator ruled —
2611
- │ │ completed, or failed `invalid_result`
2612
- │ ├── test-stdout.txt the validator's printed reasons
2613
- │ │ (empty on a valid result, as
2614
- │ │ Harbor's validate.py prints nothing)
2615
- │ ├── reward.txt `1` or `0` — the exact bytes Harbor's
2616
- │ │ wrapper test.sh writes
2617
- │ └── reward.json {"reward": 1|0}
2618
- └── artifacts/
2619
- ├── manifest.json always present — what was collected
2620
- │ (`[]` when the run produced nothing)
2621
- └── analysis.json the validated deliverable — summary
2622
- + checks — only on a completed run
4185
+ /api/checks/defaults:
4186
+ get:
4187
+ tags: [checks]
4188
+ operationId: getCheckDefaults
4189
+ summary: The defaults a check runs under when its config names nothing
4190
+ description: |
4191
+ The resolved policy an empty `CheckConfigInput` falls back to — the
4192
+ five knobs the create door stamps on every check that names none:
4193
+ `model_name`, `rubric` (the platform's check rubric, the eleven
4194
+ criteria `CheckConfigInput` lists), `prompt` (the built-in check
4195
+ body, as the TEMPLATE a caller's own `prompt` replaces — with its
4196
+ `{task_path}`, `{file_tree}` and `{criteria_guidance}` slots
4197
+ unrendered; the output section Harbor appends is not part of it),
4198
+ `reasoning_effort` (the default model's per-model effort) and
4199
+ `sandbox_provider` (the fleet default). Read them to see what a
4200
+ bare `evolve check` runs under, or to start an edited rubric or
4201
+ prompt from the platform's own. `model_name` and `rubric` sent
4202
+ back resolve as if nothing was sent; `prompt`, `reasoning_effort`
4203
+ and `sandbox_provider` sent back are stored as named, so the check
4204
+ echoes the prompt text and keeps that effort and provider even
4205
+ when the fleet default later changes. Harbor
4206
+ has no such verb: its defaults are files on disk
4207
+ (cli/quality_checker/default-rubric.toml, analyze/prompts/
4208
+ check.txt), which a hosted caller cannot open — the hosted reading
4209
+ of the same files.
4210
+ responses:
4211
+ '200':
4212
+ description: The resolved defaults.
4213
+ content:
4214
+ application/json:
4215
+ schema:
4216
+ $ref: '#/components/schemas/CheckDefaults'
4217
+ '401':
4218
+ $ref: '#/components/responses/Unauthorized'
4219
+ '429':
4220
+ $ref: '#/components/responses/TooManyRequests'
4221
+ '500':
4222
+ $ref: '#/components/responses/InternalError'
2623
4223
 
2624
- The validator runs on the platform's worker, not inside the box
2625
- (a recorded deviation: the rule is the platform's one result
2626
- definition, which the box's validate.py records; the reasons are that
2627
- definition's issues, one per line as `path: message`); the run's raw
2628
- deliverable is not retained, so
2629
- `artifacts/analysis.json` is the validated document in canonical
2630
- JSON and a refused deliverable leaves no file. The image digest and
2631
- the harness bundle version resolve per run but are not stored on
2632
- the row, so `lock.json` cannot state them (capture-side, deferred).
2633
- Data the platform does not hold is left out, never faked.
4224
+ /api/checks/{checkId}:
4225
+ get:
4226
+ tags: [checks]
4227
+ operationId: getCheck
4228
+ summary: Get one task quality check (the report)
4229
+ description: |
4230
+ The check with its per-task results Harbor's CheckReport
4231
+ (`results`, their cli/quality_checker/models.py:38-41) plus the
4232
+ policy it ran under and its source. Readable by the creator and by
4233
+ every member of the owning organization; anyone else gets 404
4234
+ `check_not_found`, indistinguishable from an id that does not exist.
2634
4235
 
2635
- Deterministic: re-downloads are byte-identical (stored rows only,
2636
- canonical JSON, fixed tar mtimes, zero gzip mtime). Readable by the
2637
- analyzed job's creator and the owning organization's members;
2638
- anyone else gets 404 `analysis_not_found`, indistinguishable from
2639
- an id that does not exist. A run still queued or running refuses
2640
- 409 `analysis_not_terminal` — the folder is a record of a settled
2641
- run.
4236
+ Each task's result (`results[].id`) is also a run of its own, read
4237
+ exactly like an analysis run (owner ruling 2026-09-09): the
4238
+ checker's live trace, raw streams, agent home and its result file
4239
+ (`check-result.json`, Harbor's name for the checker's deliverable —
4240
+ checker.py:37) are served by the traces feed's per-run doors under
4241
+ the task check's id — the doors `evolve check trace` and `evolve
4242
+ check download` ride — under the check's own access law (creator or
4243
+ org member). A task check is stoppable there too (the feed's stop
4244
+ verb), one task at a time.
4245
+ parameters:
4246
+ - $ref: '#/components/parameters/CheckId'
4247
+ responses:
4248
+ '200':
4249
+ description: The check.
4250
+ content:
4251
+ application/json:
4252
+ schema:
4253
+ $ref: '#/components/schemas/Check'
4254
+ '401':
4255
+ $ref: '#/components/responses/Unauthorized'
4256
+ '404':
4257
+ $ref: '#/components/responses/NotFound'
4258
+ '429':
4259
+ $ref: '#/components/responses/TooManyRequests'
4260
+ '500':
4261
+ $ref: '#/components/responses/InternalError'
4262
+
4263
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem:
4264
+ get:
4265
+ tags: [checks]
4266
+ operationId: getTaskCheckFilesystem
4267
+ summary: The task check's file system — which source it has
4268
+ description: |
4269
+ The Files surface of one task check (the file system family, one shape
4270
+ under every run owner: trials, analyses, task checks — and the task
4271
+ package as a fourth, read-only owner). `state` says what the
4272
+ listing, read, search, archive and change routes will answer from:
4273
+ `live` — the box is running and the routes read it as it changes;
4274
+ `captured` — the run ended and its file system was kept, the same
4275
+ routes read the kept tree; `capturing` — the run just ended and the
4276
+ capture is still being written; `none` — nothing to read yet (a
4277
+ queued run, a run older than the feature, a capture that failed —
4278
+ `capture.status` says which). `box` names the running box and its
4279
+ role, `watcher` how change events arrive (`native` from the
4280
+ provider, `poll` from the folders the viewer declared open), and
4281
+ `capture` the kept tree's record: when, after which phase, how many
4282
+ entries and changed files, `ready` | `incomplete` (`left_out` names
4283
+ what did not fit the budget) | `failed`.
4284
+ parameters:
4285
+ - $ref: '#/components/parameters/CheckId'
4286
+ - $ref: '#/components/parameters/TaskCheckId'
4287
+ responses:
4288
+ '200':
4289
+ description: The file system's state.
4290
+ content:
4291
+ application/json:
4292
+ schema:
4293
+ $ref: '#/components/schemas/FilesystemStatus'
4294
+ '401':
4295
+ $ref: '#/components/responses/Unauthorized'
4296
+ '404':
4297
+ $ref: '#/components/responses/NotFound'
4298
+ '503':
4299
+ description: >
4300
+ `provider_unreachable` — the box's provider did not answer;
4301
+ retry after `Retry-After`.
4302
+ content:
4303
+ application/json:
4304
+ schema:
4305
+ $ref: '#/components/schemas/Error'
4306
+ '429':
4307
+ $ref: '#/components/responses/TooManyRequests'
4308
+ '500':
4309
+ $ref: '#/components/responses/InternalError'
4310
+
4311
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/files:
4312
+ get:
4313
+ tags: [checks]
4314
+ operationId: listTaskCheckFilesystemFolder
4315
+ summary: List one folder of the task check's file system
4316
+ description: |
4317
+ One page of a folder, sorted by name, paged with `cursor` (the last
4318
+ name served) and `limit` (default 500, max 1000). `source` forces
4319
+ `live` or `capture`; omitted, whichever the run has (`state` on the
4320
+ status route). Every entry carries its type (`dir` | `file` |
4321
+ `symlink` — with `target` — | `other`), size, mtime, mode and owner;
4322
+ `changed` and `phase` say whether the run created or modified it
4323
+ and in which phase (null when not known for the source); on a
4324
+ captured listing `captured: false` marks a file the run never
4325
+ touched, which only the image holds — it lists but does not open.
4326
+ The kernel trees (`/proc`, `/sys`, `/dev`) are never listed.
4327
+ parameters:
4328
+ - $ref: '#/components/parameters/CheckId'
4329
+ - $ref: '#/components/parameters/TaskCheckId'
4330
+ - $ref: '#/components/parameters/FilesystemPath'
4331
+ - $ref: '#/components/parameters/FilesystemSource'
4332
+ - $ref: '#/components/parameters/FilesystemCursor'
4333
+ - $ref: '#/components/parameters/FilesystemLimit'
4334
+ responses:
4335
+ '200':
4336
+ description: One page of the folder.
4337
+ content:
4338
+ application/json:
4339
+ schema:
4340
+ $ref: '#/components/schemas/FilesystemListing'
4341
+ '400':
4342
+ $ref: '#/components/responses/BadRequest'
4343
+ '401':
4344
+ $ref: '#/components/responses/Unauthorized'
4345
+ '404':
4346
+ $ref: '#/components/responses/NotFound'
4347
+ '409':
4348
+ description: >
4349
+ `filesystem_state` — `source=live` while the box is gone, or
4350
+ `source=capture` before a capture exists (details carry the
4351
+ requested source and the state).
4352
+ content:
4353
+ application/json:
4354
+ schema:
4355
+ $ref: '#/components/schemas/Error'
4356
+ '503':
4357
+ description: >
4358
+ `provider_unreachable` — the box's provider did not answer;
4359
+ retry after `Retry-After`.
4360
+ content:
4361
+ application/json:
4362
+ schema:
4363
+ $ref: '#/components/schemas/Error'
4364
+ '429':
4365
+ $ref: '#/components/responses/TooManyRequests'
4366
+ '500':
4367
+ $ref: '#/components/responses/InternalError'
2642
4368
 
2643
- `evolve analysis download <analysis-id>` extracts it and adds
2644
- `evolve.json` (the platform record Harbor's layout has no slot for).
4369
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/files/{path}:
4370
+ get:
4371
+ tags: [checks]
4372
+ operationId: readTaskCheckFilesystemFile
4373
+ summary: Read one file of the task check's file system, raw bytes
4374
+ description: |
4375
+ RAW BYTES of one file — application/octet-stream, byte fidelity, no
4376
+ translation (whether it is text is the reader's call: the first
4377
+ 4 KiB, a NUL byte means binary). A standard single `Range` header
4378
+ (`bytes=a-b`, `bytes=a-`, `bytes=-n`) reads a slice and answers 206
4379
+ with `Content-Range`; `Accept-Ranges: bytes` advertises it; a
4380
+ malformed or multi-range header is the typed 400, an out-of-file
4381
+ range the 416. An unranged read above the deployment's whole-read
4382
+ ceiling is the typed 413 (`invalid_input`, param `Range`, details
4383
+ `size_bytes` / `max_unranged_bytes`) — read it in slices.
4384
+ `X-Source` names the source that answered, `X-Ms` the server time.
4385
+ A symlink reads as its target (one hop). A path the file system
4386
+ does not hold is 404 `not_found`; on a captured read, a file the
4387
+ run never touched is 404 `not_captured` (only the image holds it).
4388
+ A stream that breaks after the headers ends with an aborted
4389
+ connection, never a partial body delivered as success.
2645
4390
  parameters:
2646
- - $ref: '#/components/parameters/AnalysisId'
4391
+ - $ref: '#/components/parameters/CheckId'
4392
+ - $ref: '#/components/parameters/TaskCheckId'
4393
+ - name: path
4394
+ in: path
4395
+ required: true
4396
+ description: The absolute box path, e.g. `app/work/main.py` for `/app/work/main.py` (the leading slash is the route's).
4397
+ schema:
4398
+ type: string
4399
+ - $ref: '#/components/parameters/FilesystemSource'
4400
+ - name: Range
4401
+ in: header
4402
+ description: Optional single byte range (`bytes=a-b` / `bytes=a-` / `bytes=-n`).
4403
+ schema:
4404
+ type: string
2647
4405
  responses:
2648
4406
  '200':
2649
- description: The archive.
4407
+ description: The whole file (`X-Source`, `X-Ms`).
2650
4408
  content:
2651
- application/gzip:
4409
+ application/octet-stream:
2652
4410
  schema:
2653
4411
  type: string
2654
4412
  format: binary
4413
+ '206':
4414
+ description: The requested byte range (`Content-Range` states position and size).
4415
+ content:
4416
+ application/octet-stream:
4417
+ schema:
4418
+ type: string
4419
+ format: binary
4420
+ '400':
4421
+ $ref: '#/components/responses/BadRequest'
2655
4422
  '401':
2656
4423
  $ref: '#/components/responses/Unauthorized'
2657
4424
  '404':
2658
4425
  $ref: '#/components/responses/NotFound'
2659
4426
  '409':
2660
- description: The analysis is still queued or running (`analysis_not_terminal`).
4427
+ description: >
4428
+ `filesystem_state` — `source=live` while the box is gone, or
4429
+ `source=capture` before a capture exists (details carry the
4430
+ requested source and the state).
4431
+ content:
4432
+ application/json:
4433
+ schema:
4434
+ $ref: '#/components/schemas/Error'
4435
+ '413':
4436
+ description: >
4437
+ An unranged read above the whole-read ceiling (`invalid_input`,
4438
+ param `Range`; details `size_bytes` / `max_unranged_bytes`).
4439
+ content:
4440
+ application/json:
4441
+ schema:
4442
+ $ref: '#/components/schemas/Error'
4443
+ '416':
4444
+ description: >
4445
+ Range selects nothing inside the file (`invalid_input`;
4446
+ `Content-Range` carries `bytes */<size>`).
4447
+ content:
4448
+ application/json:
4449
+ schema:
4450
+ $ref: '#/components/schemas/Error'
4451
+ '503':
4452
+ description: >
4453
+ `provider_unreachable` — the box's provider did not answer;
4454
+ retry after `Retry-After`.
2661
4455
  content:
2662
4456
  application/json:
2663
4457
  schema:
@@ -2667,206 +4461,467 @@ paths:
2667
4461
  '500':
2668
4462
  $ref: '#/components/responses/InternalError'
2669
4463
 
2670
- # ===========================================================================
2671
- # Checks — task quality checks (Harbor's `harbor check`, hosted)
2672
- # ===========================================================================
2673
-
2674
- /api/checks:
2675
- post:
4464
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/search:
4465
+ get:
2676
4466
  tags: [checks]
2677
- operationId: createCheck
2678
- summary: Check task quality against a rubric (Harbor's `harbor check`)
4467
+ operationId: searchTaskCheckFilesystem
4468
+ summary: Search the task check's file system for text
2679
4469
  description: |
2680
- Harbor's `harbor check <PATH>` (their cli/analyze.py:84-207
2681
- check_command; analyze/checker.py run_checks), hosted: PATHone
2682
- task directory, or a directory of task directories arrives as a
2683
- gzipped tarball (`archive`), or a published dataset is named
2684
- instead (`dataset`, the hosted form; `CheckRequest.dataset` states
2685
- the deviation and its reason), and for each task directory it holds
2686
- the checker agent (claude-code, Harbor's own check agent, in its own
2687
- sandbox, under the network Harbor's check wrapper task gives it —
2688
- open by default) reads EVERY file of the task and rules every rubric
2689
- criterion `pass`, `fail`, `not_applicable` or `unknown` with a
2690
- rationale and the evidence behind it, writing `check-result.json`
2691
- (the platform's default check prompt and rubric, rendered through
2692
- Harbor's own tokens and join; `prompt` and `rubric` replace them).
2693
- **The response is the Check**
2694
- (202): the record with one `results` entry per task, each `queued`
2695
- poll `GET /api/checks/{checkId}` to watch them settle (the
2696
- `status` of the check reads `completed` once every task has).
2697
-
2698
- Which task directories, exactly as Harbor decides it
2699
- (checker.py:116-141 over the archive's listing): the archive root
2700
- when it is a task directory (task.toml + environment/ +
2701
- instruction.md + the test script of its `[environment].os`
2702
- tests/test.sh for linux, the default; tests/test.bat for windows;
2703
- a `[[steps]]` task needs each step's instruction.md and its own or
2704
- the shared test script — their Task.is_valid_dir), else every
2705
- top-level directory that is one, sorted; then the
2706
- `include_task_names` globs (any match keeps), the
2707
- `exclude_task_names` globs (any match drops), then the first
2708
- `n_tasks`. An empty selection is refused `no_checkable_tasks`
2709
- (their "No valid task directories found", :140); nothing is stored.
2710
- There is no task-count ceiling — Harbor's check has none (no
2711
- invented number, owner 2026-09-13); the organization's
2712
- `max_concurrent_analyses` paces the boxes. On the dataset form the
2713
- same selection runs over the version's task names, and the same
2714
- refusal answers. The `config`
2715
- part is the JSON `CheckConfigInput`; absent or
2716
- `{}` means the defaults (openrouter/deepseek/deepseek-v4.1-flash at its
2717
- per-model effort, the platform's default check rubric — eleven
2718
- criteria, `CheckConfigInput.rubric` names them).
4470
+ Content search: `q` is the text (or, with `regex=true`, the
4471
+ pattern), `path` the folder to search under (default `/` the
4472
+ whole box, `scope: "box"`, which can take seconds; a folder answers
4473
+ `scope: "path"` in well under a second), `limit` the hit cap
4474
+ (default 200, max 1000). `truncated: true` means more hits exist or
4475
+ the search budget ran out narrow the path or the text. Live: the
4476
+ box is searched as it is; captured: the kept text is searched, and
4477
+ `image_files_excluded: true` says files the run never touched were
4478
+ not searchable. Binary files never hit.
4479
+ parameters:
4480
+ - $ref: '#/components/parameters/CheckId'
4481
+ - $ref: '#/components/parameters/TaskCheckId'
4482
+ - name: q
4483
+ in: query
4484
+ required: true
4485
+ description: The text to find; a regular expression with `regex=true`.
4486
+ schema:
4487
+ type: string
4488
+ - $ref: '#/components/parameters/FilesystemPath'
4489
+ - name: regex
4490
+ in: query
4491
+ description: Treat `q` as a regular expression (default false a plain substring).
4492
+ schema:
4493
+ type: boolean
4494
+ default: false
4495
+ - $ref: '#/components/parameters/FilesystemLimit'
4496
+ - $ref: '#/components/parameters/FilesystemSource'
4497
+ responses:
4498
+ '200':
4499
+ description: The hits found.
4500
+ content:
4501
+ application/json:
4502
+ schema:
4503
+ $ref: '#/components/schemas/FilesystemSearchResult'
4504
+ '400':
4505
+ $ref: '#/components/responses/BadRequest'
4506
+ '401':
4507
+ $ref: '#/components/responses/Unauthorized'
4508
+ '404':
4509
+ $ref: '#/components/responses/NotFound'
4510
+ '409':
4511
+ description: >
4512
+ `filesystem_state` — `source=live` while the box is gone, or
4513
+ `source=capture` before a capture exists (details carry the
4514
+ requested source and the state).
4515
+ content:
4516
+ application/json:
4517
+ schema:
4518
+ $ref: '#/components/schemas/Error'
4519
+ '503':
4520
+ description: >
4521
+ `provider_unreachable` — the box's provider did not answer;
4522
+ retry after `Retry-After`.
4523
+ content:
4524
+ application/json:
4525
+ schema:
4526
+ $ref: '#/components/schemas/Error'
4527
+ '429':
4528
+ $ref: '#/components/responses/TooManyRequests'
4529
+ '500':
4530
+ $ref: '#/components/responses/InternalError'
2719
4531
 
2720
- The task bytes are never modified and never land anywhere but the
2721
- checker's sandbox and the platform's rows: Harbor's own rule ("Do
2722
- not modify any files under {task_path}"; nothing written into the
2723
- reviewed directory — their report is the job dir's
2724
- check_report.json). A checker run that completes without a valid
2725
- `check-result.json` is automatically re-run at most once (fresh
2726
- sandbox, same model and rubric; the task's `attempts` says how far
2727
- the ladder went), the analyze verb's own hosted rule; a run cut by
2728
- its budget settles `failed` with phase `timeout` at once and is
2729
- never re-run.
4532
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/events:
4533
+ get:
4534
+ tags: [checks]
4535
+ operationId: streamTaskCheckFilesystemEvents
4536
+ summary: Change events while the task check's box lives (SSE)
4537
+ description: |
4538
+ Server-sent events. `event: state` first (`{state, box}`), then
4539
+ `event: fs` per change (`{seq, t, path, type: create|write|remove|
4540
+ rename, source: watch|poll}`, `id: <seq>`), `event: ping` every
4541
+ 15 s. Resume with `Last-Event-ID: <seq>` (or `?after=`): the events
4542
+ after it replay when the server still holds them; when it does not,
4543
+ a fresh `state` frame is the signal to relist the open folders.
4544
+ On a provider without a native watcher (`watcher: "poll"` on the
4545
+ status route) events come from polling the folders declared with
4546
+ `POST …/filesystem/watch`, so declare them. A `state` frame follows
4547
+ every change of state; the stream ends once the file system settles
4548
+ (`captured` or `none`).
4549
+ parameters:
4550
+ - $ref: '#/components/parameters/CheckId'
4551
+ - $ref: '#/components/parameters/TaskCheckId'
4552
+ - name: Last-Event-ID
4553
+ in: header
4554
+ description: Resume after this event seq.
4555
+ schema:
4556
+ type: string
4557
+ - name: after
4558
+ in: query
4559
+ description: The same as `Last-Event-ID`, for clients that cannot set headers.
4560
+ schema:
4561
+ type: string
4562
+ responses:
4563
+ '200':
4564
+ description: The event stream.
4565
+ content:
4566
+ text/event-stream:
4567
+ schema:
4568
+ type: string
4569
+ '400':
4570
+ $ref: '#/components/responses/BadRequest'
4571
+ '401':
4572
+ $ref: '#/components/responses/Unauthorized'
4573
+ '404':
4574
+ $ref: '#/components/responses/NotFound'
4575
+ '503':
4576
+ description: >
4577
+ `provider_unreachable` — the box's provider did not answer;
4578
+ retry after `Retry-After`.
4579
+ content:
4580
+ application/json:
4581
+ schema:
4582
+ $ref: '#/components/schemas/Error'
4583
+ '429':
4584
+ $ref: '#/components/responses/TooManyRequests'
4585
+ '500':
4586
+ $ref: '#/components/responses/InternalError'
2730
4587
 
2731
- Decision order: 400 `invalid_multipart` (not multipart, or a
2732
- malformed body) → 413 `upload_too_large` (the archive over what the
2733
- store can land or what the server's spool disk has free — both read
2734
- live at the request, the refusal naming which; no platform number)
2735
- 429
2736
- `too_many_concurrent_check_uploads` (the server's spool bound,
2737
- ruled at the archive part before its first byte) 400
2738
- `invalid_rubric` / `invalid_input` (the `config` part: the rubric
2739
- grammar; then an off-roster model, a bad `prompt`, an off-lineup
2740
- `sandbox_provider`, an off-vocabulary `reasoning_effort`, an
2741
- out-of-range `n_concurrent`, a malformed glob list, an invalid
2742
- `n_tasks` in that order, the analyze door's own ladder under the
2743
- `check.` prefix) → 400 `invalid_input` (both `archive` and `dataset`,
2744
- or neither) → the dataset form's resolution refusals
2745
- (`CheckRequest.dataset`) 400 `invalid_archive` (not a readable gzipped
2746
- tar, an unsafe entry, or a listing that would hold more heap than
2747
- the server can still commit — read live, named in the refusal) →
2748
- 400 `no_checkable_tasks` → 202.
4588
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/watch:
4589
+ post:
4590
+ tags: [checks]
4591
+ operationId: watchTaskCheckFilesystem
4592
+ summary: Declare the folders the viewer has open
4593
+ description: |
4594
+ The folders whose changes the event stream must report on a
4595
+ provider without a native watcher (`watcher: "poll"`): idempotent
4596
+ the body's `paths` REPLACES the set; an empty set stops polling. At
4597
+ most 8 folders at once (a bigger set is the typed 400 naming the
4598
+ bound). On a provider with a native watcher the set is accepted
4599
+ and unused. Refused with `filesystem_state` when the box is gone.
4600
+ parameters:
4601
+ - $ref: '#/components/parameters/CheckId'
4602
+ - $ref: '#/components/parameters/TaskCheckId'
2749
4603
  requestBody:
2750
4604
  required: true
2751
4605
  content:
2752
- multipart/form-data:
4606
+ application/json:
2753
4607
  schema:
2754
- $ref: '#/components/schemas/CheckRequest'
4608
+ $ref: '#/components/schemas/FilesystemWatchRequest'
2755
4609
  responses:
2756
- '202':
2757
- description: The check, its task checks queued.
4610
+ '200':
4611
+ description: The set in force.
4612
+ content:
4613
+ application/json:
4614
+ schema:
4615
+ $ref: '#/components/schemas/FilesystemWatchResult'
4616
+ '400':
4617
+ $ref: '#/components/responses/BadRequest'
4618
+ '401':
4619
+ $ref: '#/components/responses/Unauthorized'
4620
+ '404':
4621
+ $ref: '#/components/responses/NotFound'
4622
+ '409':
4623
+ description: >
4624
+ `filesystem_state` — `source=live` while the box is gone, or
4625
+ `source=capture` before a capture exists (details carry the
4626
+ requested source and the state).
4627
+ content:
4628
+ application/json:
4629
+ schema:
4630
+ $ref: '#/components/schemas/Error'
4631
+ '503':
4632
+ description: >
4633
+ `provider_unreachable` — the box's provider did not answer;
4634
+ retry after `Retry-After`.
4635
+ content:
4636
+ application/json:
4637
+ schema:
4638
+ $ref: '#/components/schemas/Error'
4639
+ '429':
4640
+ $ref: '#/components/responses/TooManyRequests'
4641
+ '500':
4642
+ $ref: '#/components/responses/InternalError'
4643
+
4644
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/changes:
4645
+ get:
4646
+ tags: [checks]
4647
+ operationId: listTaskCheckFilesystemChanges
4648
+ summary: The files the task check changed
4649
+ description: |
4650
+ The flat list of what the run created, modified or removed since its
4651
+ box started, with `phase` (`setup` | `agent` | `verifier`); `total`
4652
+ and `changed_bytes` are the whole list's, `items` one page (`cursor`
4653
+ = an opaque position, `limit` default 500, max 1000); `phase=`
4654
+ narrows to one phase. Captured: stored with the kept tree. Live: the
4655
+ box start listing against a fresh one, seconds per call; a box whose
4656
+ start listing was not stored is the typed 422.
4657
+ parameters:
4658
+ - $ref: '#/components/parameters/CheckId'
4659
+ - $ref: '#/components/parameters/TaskCheckId'
4660
+ - $ref: '#/components/parameters/FilesystemSource'
4661
+ - name: phase
4662
+ in: query
4663
+ description: Only changes of one phase (default all).
4664
+ schema:
4665
+ type: string
4666
+ enum: [setup, agent, verifier, all]
4667
+ default: all
4668
+ - $ref: '#/components/parameters/FilesystemCursor'
4669
+ - $ref: '#/components/parameters/FilesystemLimit'
4670
+ responses:
4671
+ '200':
4672
+ description: One page of the changed-files list.
4673
+ content:
4674
+ application/json:
4675
+ schema:
4676
+ $ref: '#/components/schemas/FilesystemChanges'
4677
+ '400':
4678
+ $ref: '#/components/responses/BadRequest'
4679
+ '401':
4680
+ $ref: '#/components/responses/Unauthorized'
4681
+ '404':
4682
+ $ref: '#/components/responses/NotFound'
4683
+ '409':
4684
+ description: >
4685
+ `filesystem_state` — `source=live` while the box is gone, or
4686
+ `source=capture` before a capture exists (details carry the
4687
+ requested source and the state).
2758
4688
  content:
2759
4689
  application/json:
2760
4690
  schema:
2761
- $ref: '#/components/schemas/Check'
4691
+ $ref: '#/components/schemas/Error'
4692
+ '422':
4693
+ description: >
4694
+ `feature_unsupported` — the shape or the provider has no live view
4695
+ of what was asked (details.cause names it).
4696
+ content:
4697
+ application/json:
4698
+ schema:
4699
+ $ref: '#/components/schemas/Error'
4700
+ '503':
4701
+ description: >
4702
+ `provider_unreachable` — the box's provider did not answer;
4703
+ retry after `Retry-After`.
4704
+ content:
4705
+ application/json:
4706
+ schema:
4707
+ $ref: '#/components/schemas/Error'
4708
+ '429':
4709
+ $ref: '#/components/responses/TooManyRequests'
4710
+ '500':
4711
+ $ref: '#/components/responses/InternalError'
4712
+
4713
+ /api/checks/{checkId}/tasks/{taskCheckId}/filesystem/archive:
4714
+ get:
4715
+ tags: [checks]
4716
+ operationId: downloadTaskCheckFilesystemArchive
4717
+ summary: Download one subtree of the task check's file system as a .tar.gz
4718
+ description: |
4719
+ A gzipped tarball of the subtree under `path` (default `/`, the
4720
+ whole tree), streamed. Captured: only the kept bytes ride; the
4721
+ files the run never touched are listed in `MANIFEST.tsv` at the
4722
+ archive root with `captured=false`. Live: read out of the running
4723
+ box under the capture budget — a subtree too large to leave the box
4724
+ inside it is refused up front, 422 `feature_unsupported` with the
4725
+ size in `details.cause`; a read-out that still stalls past the
4726
+ budget aborts the connection, never a truncated archive delivered
4727
+ as success. `X-Source` names the source. A folder the file system
4728
+ does not hold is 404 `not_found`.
4729
+ parameters:
4730
+ - $ref: '#/components/parameters/CheckId'
4731
+ - $ref: '#/components/parameters/TaskCheckId'
4732
+ - $ref: '#/components/parameters/FilesystemPath'
4733
+ - $ref: '#/components/parameters/FilesystemSource'
4734
+ responses:
4735
+ '200':
4736
+ description: The archive (`Content-Disposition` names it `<id>_filesystem.tar.gz`).
4737
+ content:
4738
+ application/gzip:
4739
+ schema:
4740
+ type: string
4741
+ format: binary
2762
4742
  '400':
2763
4743
  $ref: '#/components/responses/BadRequest'
2764
4744
  '401':
2765
4745
  $ref: '#/components/responses/Unauthorized'
2766
- '413':
2767
- description: The archive is over the physical ceiling read at the request — the store's, or the spool disk's (`upload_too_large`; details.max_bytes and details.source).
4746
+ '404':
4747
+ $ref: '#/components/responses/NotFound'
4748
+ '409':
4749
+ description: >
4750
+ `filesystem_state` — `source=live` while the box is gone, or
4751
+ `source=capture` before a capture exists (details carry the
4752
+ requested source and the state).
2768
4753
  content:
2769
4754
  application/json:
2770
4755
  schema:
2771
4756
  $ref: '#/components/schemas/Error'
2772
- '429':
4757
+ '422':
2773
4758
  description: >
2774
- Two conditions, distinguished by code: `rate_limited` (the
2775
- standard throttle honor `Retry-After`), and
2776
- `too_many_concurrent_check_uploads` the server is already
2777
- spooling its bound of concurrent check archives
2778
- (`max_concurrent` in the details), refused before the first
2779
- uploaded byte, with no `Retry-After` because the server cannot
2780
- honestly compute one; retry when an in-flight upload finishes.
4759
+ `feature_unsupported` the provider has no live view of what was
4760
+ asked, or the live subtree cannot leave the box inside the
4761
+ capture budget (details.cause names it, with the size).
4762
+ content:
4763
+ application/json:
4764
+ schema:
4765
+ $ref: '#/components/schemas/Error'
4766
+ '503':
4767
+ description: >
4768
+ `provider_unreachable` — the box's provider did not answer;
4769
+ retry after `Retry-After`.
2781
4770
  content:
2782
4771
  application/json:
2783
4772
  schema:
2784
4773
  $ref: '#/components/schemas/Error'
4774
+ '429':
4775
+ $ref: '#/components/responses/TooManyRequests'
2785
4776
  '500':
2786
4777
  $ref: '#/components/responses/InternalError'
4778
+
4779
+ /api/checks/{checkId}/tasks/{taskCheckId}/logs:
2787
4780
  get:
2788
4781
  tags: [checks]
2789
- operationId: listChecks
2790
- summary: List task quality checks
4782
+ operationId: getTaskCheckSandboxLog
4783
+ summary: One page of a sandbox log stream of the task check
2791
4784
  description: |
2792
- Every check the caller may read, newest first, cursor-paged each
2793
- item the full `Check` with its per-task results. Harbor has no
2794
- check list (their check writes a local report per invocation);
2795
- hosted, the run is a record of its own and this is its catalog.
2796
- `scope=my` (the default) lists checks you created; `scope=shared`
2797
- lists your organizations' checks that teammates created (the
2798
- analyses list's own law, on the check's own org). Nothing is
2799
- public, so Harbor's `all` is refused. `status` filters by the
2800
- check's own ladder (`queued`, `running`, `completed`), repeatable
2801
- and/or comma-separated; an unknown word is refused
2802
- `invalid_input` with the ladder in `details.valid_statuses`.
4785
+ The box's own streams, recorded while it runs and kept after:
4786
+ `agent` (the harness's stdout/stderr), `verifier` (the verifier
4787
+ command's) and `system` (the box's system log only when the job
4788
+ asked for it, `JobCreate.system_log`). `setup` and `metrics` are
4789
+ named but not recorded today. Lines are `{seq, t, fd: out|err, line}`;
4790
+ `cursor` is an opaque position, `limit` default 1000, max 1000. A
4791
+ stream the platform holds nothing for answers an empty page whose
4792
+ `reason` says why never an error.
2803
4793
  parameters:
2804
- - $ref: '#/components/parameters/Limit'
2805
- - $ref: '#/components/parameters/Cursor'
2806
- - $ref: '#/components/parameters/ListScope'
2807
- - name: status
4794
+ - $ref: '#/components/parameters/CheckId'
4795
+ - $ref: '#/components/parameters/TaskCheckId'
4796
+ - name: stream
2808
4797
  in: query
4798
+ required: true
2809
4799
  schema:
2810
- type: array
2811
- items:
2812
- type: string
2813
- enum: [queued, running, completed]
2814
- explode: true
4800
+ $ref: '#/components/schemas/SandboxLogStream'
4801
+ - $ref: '#/components/parameters/FilesystemCursor'
4802
+ - $ref: '#/components/parameters/FilesystemLimit'
2815
4803
  responses:
2816
4804
  '200':
2817
- description: One page of checks.
4805
+ description: One page of the stream.
2818
4806
  content:
2819
4807
  application/json:
2820
4808
  schema:
2821
- $ref: '#/components/schemas/CheckPage'
4809
+ $ref: '#/components/schemas/SandboxLogLines'
2822
4810
  '400':
2823
4811
  $ref: '#/components/responses/BadRequest'
2824
4812
  '401':
2825
4813
  $ref: '#/components/responses/Unauthorized'
4814
+ '404':
4815
+ $ref: '#/components/responses/NotFound'
4816
+ '503':
4817
+ description: >
4818
+ `provider_unreachable` — the box's provider did not answer;
4819
+ retry after `Retry-After`.
4820
+ content:
4821
+ application/json:
4822
+ schema:
4823
+ $ref: '#/components/schemas/Error'
2826
4824
  '429':
2827
4825
  $ref: '#/components/responses/TooManyRequests'
2828
4826
  '500':
2829
4827
  $ref: '#/components/responses/InternalError'
2830
4828
 
2831
- /api/checks/{checkId}:
4829
+ /api/checks/{checkId}/tasks/{taskCheckId}/logs/events:
2832
4830
  get:
2833
4831
  tags: [checks]
2834
- operationId: getCheck
2835
- summary: Get one task quality check (the report)
4832
+ operationId: streamTaskCheckSandboxLogs
4833
+ summary: Sandbox log lines as they arrive (SSE)
2836
4834
  description: |
2837
- The check with its per-task results Harbor's CheckReport
2838
- (`results`, their cli/quality_checker/models.py:38-41) plus the
2839
- policy it ran under and its source. Readable by the creator and by
2840
- every member of the owning organization; anyone else gets 404
2841
- `check_not_found`, indistinguishable from an id that does not exist.
4835
+ Server-sent events over every stream at once: `event: line`
4836
+ (`{stream, seq, t, fd, line}`, `id: <stream>:<seq>`), `event: state`
4837
+ on open and on every change of the box's state, `event: ping` every
4838
+ 15 s. Without
4839
+ `Last-Event-ID` every stream replays from its beginning and then
4840
+ follows; with `Last-Event-ID: <stream>:<seq>` that stream resumes
4841
+ after the seq the paged GET handed out. Once the box is gone and
4842
+ every recorded line has been sent, the stream ends — the paged GET
4843
+ is the reader after that.
4844
+ parameters:
4845
+ - $ref: '#/components/parameters/CheckId'
4846
+ - $ref: '#/components/parameters/TaskCheckId'
4847
+ - name: Last-Event-ID
4848
+ in: header
4849
+ description: Resume one stream after `<stream>:<seq>`.
4850
+ schema:
4851
+ type: string
4852
+ responses:
4853
+ '200':
4854
+ description: The event stream.
4855
+ content:
4856
+ text/event-stream:
4857
+ schema:
4858
+ type: string
4859
+ '400':
4860
+ $ref: '#/components/responses/BadRequest'
4861
+ '401':
4862
+ $ref: '#/components/responses/Unauthorized'
4863
+ '404':
4864
+ $ref: '#/components/responses/NotFound'
4865
+ '503':
4866
+ description: >
4867
+ `provider_unreachable` — the box's provider did not answer;
4868
+ retry after `Retry-After`.
4869
+ content:
4870
+ application/json:
4871
+ schema:
4872
+ $ref: '#/components/schemas/Error'
4873
+ '429':
4874
+ $ref: '#/components/responses/TooManyRequests'
4875
+ '500':
4876
+ $ref: '#/components/responses/InternalError'
2842
4877
 
2843
- Each task's result (`results[].id`) is also a run of its own, read
2844
- exactly like an analysis run (owner ruling 2026-09-09): the
2845
- checker's live trace, raw streams, agent home and its result file
2846
- (`check-result.json`, Harbor's name for the checker's deliverable —
2847
- checker.py:37) are served by the traces feed's per-run doors under
2848
- the task check's id — the doors `evolve check trace` and `evolve
2849
- check download` ride under the check's own access law (creator or
2850
- org member). A task check is stoppable there too (the feed's stop
2851
- verb), one task at a time.
4878
+ /api/checks/{checkId}/tasks/{taskCheckId}/procs:
4879
+ get:
4880
+ tags: [checks]
4881
+ operationId: getTaskCheckProcs
4882
+ summary: The task check's box process list (live only)
4883
+ description: |
4884
+ The processes running in the box right now, as one text listing
4885
+ (`ps` sorted by memory; on an image without `ps`, the same table
4886
+ read from `/proc`, its first line saying so). Live only: once the
4887
+ box is gone the answer is 409 `filesystem_state`.
2852
4888
  parameters:
2853
4889
  - $ref: '#/components/parameters/CheckId'
4890
+ - $ref: '#/components/parameters/TaskCheckId'
2854
4891
  responses:
2855
4892
  '200':
2856
- description: The check.
4893
+ description: The listing.
2857
4894
  content:
2858
4895
  application/json:
2859
4896
  schema:
2860
- $ref: '#/components/schemas/Check'
4897
+ $ref: '#/components/schemas/SandboxProcs'
2861
4898
  '401':
2862
4899
  $ref: '#/components/responses/Unauthorized'
2863
4900
  '404':
2864
4901
  $ref: '#/components/responses/NotFound'
4902
+ '409':
4903
+ description: >
4904
+ `filesystem_state` — `source=live` while the box is gone, or
4905
+ `source=capture` before a capture exists (details carry the
4906
+ requested source and the state).
4907
+ content:
4908
+ application/json:
4909
+ schema:
4910
+ $ref: '#/components/schemas/Error'
4911
+ '503':
4912
+ description: >
4913
+ `provider_unreachable` — the box's provider did not answer;
4914
+ retry after `Retry-After`.
4915
+ content:
4916
+ application/json:
4917
+ schema:
4918
+ $ref: '#/components/schemas/Error'
2865
4919
  '429':
2866
4920
  $ref: '#/components/responses/TooManyRequests'
2867
4921
  '500':
2868
4922
  $ref: '#/components/responses/InternalError'
2869
4923
 
4924
+
2870
4925
  /api/checks/{checkId}/download:
2871
4926
  get:
2872
4927
  tags: [checks]
@@ -3410,25 +5465,200 @@ paths:
3410
5465
  required: true
3411
5466
  schema:
3412
5467
  type: string
3413
- - name: task_name
5468
+ - name: task_name
5469
+ in: path
5470
+ required: true
5471
+ schema:
5472
+ type: string
5473
+ responses:
5474
+ '200':
5475
+ description: The task's build outcome.
5476
+ content:
5477
+ application/json:
5478
+ schema:
5479
+ $ref: '#/components/schemas/TaskBuild'
5480
+ '401':
5481
+ $ref: '#/components/responses/Unauthorized'
5482
+ '404':
5483
+ description: >
5484
+ `dataset_not_found`, `dataset_version_not_found`, or
5485
+ `task_not_found` (no build outcome recorded under that name in
5486
+ this version).
5487
+ content:
5488
+ application/json:
5489
+ schema:
5490
+ $ref: '#/components/schemas/Error'
5491
+ '429':
5492
+ $ref: '#/components/responses/TooManyRequests'
5493
+ '500':
5494
+ $ref: '#/components/responses/InternalError'
5495
+
5496
+ /api/datasets/{name}/versions/{version}/tasks/{task_name}/filesystem:
5497
+ get:
5498
+ tags: [datasets]
5499
+ operationId: getTaskPackageFilesystem
5500
+ summary: The task package as a file system (status)
5501
+ description: |
5502
+ The task directory's files as the fourth, read-only owner of the
5503
+ file system family: served from the retained task package — the
5504
+ exact bytes the import stored, the ones every trial ran against.
5505
+ `state` is always `none` (no box, no capture), `source` is
5506
+ `package`; `package_retained` says whether the version keeps its
5507
+ package — one imported before packages were retained answers the
5508
+ listing and read with 409 `task_package_not_retained`. No events,
5509
+ no logs, no search beyond the path filter.
5510
+ parameters:
5511
+ - $ref: '#/components/parameters/DatasetName'
5512
+ - name: version
5513
+ in: path
5514
+ required: true
5515
+ schema:
5516
+ type: string
5517
+ - name: task_name
5518
+ in: path
5519
+ required: true
5520
+ schema:
5521
+ type: string
5522
+ responses:
5523
+ '200':
5524
+ description: The package owner's state.
5525
+ content:
5526
+ application/json:
5527
+ schema:
5528
+ $ref: '#/components/schemas/TaskPackageFilesystemStatus'
5529
+ '401':
5530
+ $ref: '#/components/responses/Unauthorized'
5531
+ '404':
5532
+ $ref: '#/components/responses/NotFound'
5533
+ '429':
5534
+ $ref: '#/components/responses/TooManyRequests'
5535
+ '500':
5536
+ $ref: '#/components/responses/InternalError'
5537
+
5538
+ /api/datasets/{name}/versions/{version}/tasks/{task_name}/filesystem/files:
5539
+ get:
5540
+ tags: [datasets]
5541
+ operationId: listTaskPackageFolder
5542
+ summary: List one folder of the task package
5543
+ description: |
5544
+ The same page as a run's folder listing (`FilesystemListing`), over
5545
+ the task directory (`/` is the directory holding instruction.md,
5546
+ task.toml, environment/, tests/); `source` may only be `package`.
5547
+ Every file is held (`captured: true`); `changed` and `phase` are
5548
+ null. A version that keeps no package is 409
5549
+ `task_package_not_retained`.
5550
+ parameters:
5551
+ - $ref: '#/components/parameters/DatasetName'
5552
+ - name: version
5553
+ in: path
5554
+ required: true
5555
+ schema:
5556
+ type: string
5557
+ - name: task_name
5558
+ in: path
5559
+ required: true
5560
+ schema:
5561
+ type: string
5562
+ - $ref: '#/components/parameters/FilesystemPath'
5563
+ - $ref: '#/components/parameters/FilesystemCursor'
5564
+ - $ref: '#/components/parameters/FilesystemLimit'
5565
+ responses:
5566
+ '200':
5567
+ description: One page of the folder.
5568
+ content:
5569
+ application/json:
5570
+ schema:
5571
+ $ref: '#/components/schemas/FilesystemListing'
5572
+ '400':
5573
+ $ref: '#/components/responses/BadRequest'
5574
+ '401':
5575
+ $ref: '#/components/responses/Unauthorized'
5576
+ '404':
5577
+ $ref: '#/components/responses/NotFound'
5578
+ '409':
5579
+ description: '`task_package_not_retained` — the version keeps no package.'
5580
+ content:
5581
+ application/json:
5582
+ schema:
5583
+ $ref: '#/components/schemas/Error'
5584
+ '429':
5585
+ $ref: '#/components/responses/TooManyRequests'
5586
+ '500':
5587
+ $ref: '#/components/responses/InternalError'
5588
+
5589
+ /api/datasets/{name}/versions/{version}/tasks/{task_name}/filesystem/files/{path}:
5590
+ get:
5591
+ tags: [datasets]
5592
+ operationId: readTaskPackageFile
5593
+ summary: Read one file of the task package, raw bytes
5594
+ description: |
5595
+ The run file read (`readTrialFilesystemFile`) over the task
5596
+ directory: raw bytes, a single `Range` honoured (206), the 413 above
5597
+ the whole-read ceiling, 404 `not_found` for a path the package does
5598
+ not hold, `X-Source: package`. A version that keeps no package is
5599
+ 409 `task_package_not_retained`.
5600
+ parameters:
5601
+ - $ref: '#/components/parameters/DatasetName'
5602
+ - name: version
5603
+ in: path
5604
+ required: true
5605
+ schema:
5606
+ type: string
5607
+ - name: task_name
5608
+ in: path
5609
+ required: true
5610
+ schema:
5611
+ type: string
5612
+ - name: path
3414
5613
  in: path
3415
5614
  required: true
5615
+ description: The path inside the task directory, e.g. `tests/test.sh`.
5616
+ schema:
5617
+ type: string
5618
+ - name: Range
5619
+ in: header
5620
+ description: Optional single byte range (`bytes=a-b` / `bytes=a-` / `bytes=-n`).
3416
5621
  schema:
3417
5622
  type: string
3418
5623
  responses:
3419
5624
  '200':
3420
- description: The task's build outcome.
5625
+ description: The whole file.
3421
5626
  content:
3422
- application/json:
5627
+ application/octet-stream:
3423
5628
  schema:
3424
- $ref: '#/components/schemas/TaskBuild'
5629
+ type: string
5630
+ format: binary
5631
+ '206':
5632
+ description: The requested byte range.
5633
+ content:
5634
+ application/octet-stream:
5635
+ schema:
5636
+ type: string
5637
+ format: binary
5638
+ '400':
5639
+ $ref: '#/components/responses/BadRequest'
3425
5640
  '401':
3426
5641
  $ref: '#/components/responses/Unauthorized'
3427
5642
  '404':
5643
+ $ref: '#/components/responses/NotFound'
5644
+ '409':
5645
+ description: '`task_package_not_retained` — the version keeps no package.'
5646
+ content:
5647
+ application/json:
5648
+ schema:
5649
+ $ref: '#/components/schemas/Error'
5650
+ '413':
3428
5651
  description: >
3429
- `dataset_not_found`, `dataset_version_not_found`, or
3430
- `task_not_found` (no build outcome recorded under that name in
3431
- this version).
5652
+ An unranged read above the whole-read ceiling (`invalid_input`,
5653
+ param `Range`; details `size_bytes` / `max_unranged_bytes`).
5654
+ content:
5655
+ application/json:
5656
+ schema:
5657
+ $ref: '#/components/schemas/Error'
5658
+ '416':
5659
+ description: >
5660
+ Range selects nothing inside the file (`invalid_input`;
5661
+ `Content-Range` carries `bytes */<size>`).
3432
5662
  content:
3433
5663
  application/json:
3434
5664
  schema:
@@ -5053,6 +7283,11 @@ paths:
5053
7283
  parameter: a session has one owner and no organization, so `my` is
5054
7284
  the only visibility there is. The dashboard's own `admin=true` view
5055
7285
  is a session-cookie door — an API key that sends it is refused 403.
7286
+ Each item (the same document `getSession` serves) carries the
7287
+ session's identity — `agent`, `model`, `provider`, `sandboxId` — and
7288
+ `reasoningEffort`, the effort the session was started with as the
7289
+ SDK resolved it; null for a harness without one and for sessions
7290
+ ingested before the field.
5056
7291
  parameters:
5057
7292
  - name: paginated
5058
7293
  in: query
@@ -5178,7 +7413,13 @@ paths:
5178
7413
  summary: Ingest session observability events
5179
7414
  description: >
5180
7415
  The SDK's observability push: session lifecycle and transcript events,
5181
- batched. Returns `{ success: true }` on acceptance.
7416
+ batched. Returns `{ success: true }` on acceptance. The body carries
7417
+ the session's identity (`tag`, `provider`, `agent`, `model`,
7418
+ `sandboxId`, `timestamp`) beside the batch, and may carry
7419
+ `reasoningEffort` — the effort the session was started with, as the
7420
+ SDK resolved it: 1–32 characters of letters, digits, `_` or `-`,
7421
+ else 400 `invalid_input` naming the field. The first ingest of a
7422
+ session records it; a later ingest never overwrites a recorded value.
5182
7423
  requestBody:
5183
7424
  required: true
5184
7425
  content:
@@ -6098,6 +8339,45 @@ components:
6098
8339
  dataset route.
6099
8340
  schema:
6100
8341
  type: string
8342
+ TaskCheckId:
8343
+ name: taskCheckId
8344
+ in: path
8345
+ required: true
8346
+ description: One task check's id (`results[].id` on the check); its file system is its own, under the check that owns it.
8347
+ schema:
8348
+ type: string
8349
+ format: uuid
8350
+ FilesystemSource:
8351
+ name: source
8352
+ in: query
8353
+ description: >
8354
+ Force the source: `live` (the running box) or `capture` (the kept
8355
+ tree). Omitted, whichever the run has. A source that is not there
8356
+ is 409 `filesystem_state`.
8357
+ schema:
8358
+ type: string
8359
+ enum: [live, capture]
8360
+ FilesystemPath:
8361
+ name: path
8362
+ in: query
8363
+ description: An absolute box path (default `/`).
8364
+ schema:
8365
+ type: string
8366
+ default: /
8367
+ FilesystemCursor:
8368
+ name: cursor
8369
+ in: query
8370
+ description: Resume after this cursor (the previous page's `next_cursor`).
8371
+ schema:
8372
+ type: string
8373
+ FilesystemLimit:
8374
+ name: limit
8375
+ in: query
8376
+ description: Page size; the route's default, max 1000.
8377
+ schema:
8378
+ type: integer
8379
+ minimum: 1
8380
+ maximum: 1000
6101
8381
  AgentName:
6102
8382
  name: name
6103
8383
  in: path
@@ -6539,6 +8819,13 @@ components:
6539
8819
  - org_member_not_found
6540
8820
  - invite_not_found
6541
8821
  - invite_invalid
8822
+ # The file system + sandbox logs family (each code's meaning: its routes' responses).
8823
+ - not_found
8824
+ - not_captured
8825
+ - filesystem_state
8826
+ - feature_unsupported
8827
+ - provider_unreachable
8828
+ - task_package_not_retained
6542
8829
  # Fallback
6543
8830
  - internal_error
6544
8831
 
@@ -7428,6 +9715,15 @@ components:
7428
9715
  $ref: '#/components/schemas/RetryConfigInput'
7429
9716
  analyze:
7430
9717
  $ref: '#/components/schemas/AnalyzeConfigInput'
9718
+ system_log:
9719
+ type: boolean
9720
+ default: false
9721
+ description: >
9722
+ Record the box's own SYSTEM log stream (the system journal or
9723
+ log files) beside the agent, verifier, setup and metrics
9724
+ streams every run keeps — read back at
9725
+ `GET /api/trials/{trialId}/logs?stream=system`. Off by default:
9726
+ it is one extra read-only reader inside a box under evaluation.
7431
9727
  timeout_multiplier:
7432
9728
  type: number
7433
9729
  exclusiveMinimum: 0
@@ -8441,6 +10737,7 @@ components:
8441
10737
  - agent_setup_timeout_multiplier
8442
10738
  - environment_build_timeout_multiplier
8443
10739
  - sandbox_provider
10740
+ - system_log
8444
10741
  - counts
8445
10742
  - build_exclusions
8446
10743
  - n_total_trials
@@ -8504,6 +10801,13 @@ components:
8504
10801
  IT ran under). Derived jobs (resume, retry) inherit it verbatim
8505
10802
  — a re-run gets analyzed exactly like the trials it reproduces.
8506
10803
  Always null on a regrade job.
10804
+ system_log:
10805
+ type: boolean
10806
+ description: >
10807
+ Whether the run records the box's own SYSTEM log stream
10808
+ (`JobCreate.system_log`), read back at `GET …/logs?stream=system`.
10809
+ Derived jobs inherit it; always false on a regrade job and on
10810
+ every job created before the switch existed.
8507
10811
  timeout_multiplier:
8508
10812
  type: number
8509
10813
  description: >
@@ -10206,6 +12510,14 @@ components:
10206
12510
  `--ek` (local-runner knobs with no hosted meaning), `-q` (a client
10207
12511
  rendering option, not a wire field).
10208
12512
  properties:
12513
+ name:
12514
+ type: string
12515
+ minLength: 1
12516
+ maxLength: 120
12517
+ description: |
12518
+ The caller's name for the check — Harbor's `--job-name` on
12519
+ `harbor check` (cli/analyze.py:138). Omitted, the accept
12520
+ timestamp in Harbor's own shape, `YYYY-MM-DD__HH-MM-SS` (UTC).
10209
12521
  model_name:
10210
12522
  type: string
10211
12523
  default: openrouter/deepseek/deepseek-v4.1-flash
@@ -10391,6 +12703,7 @@ components:
10391
12703
  of the measured task costs, null when none was measured.
10392
12704
  required:
10393
12705
  - id
12706
+ - name
10394
12707
  - status
10395
12708
  - source
10396
12709
  - model_name
@@ -10410,6 +12723,9 @@ components:
10410
12723
  id:
10411
12724
  type: string
10412
12725
  format: uuid
12726
+ name:
12727
+ type: string
12728
+ description: The caller's name, or the accept timestamp in Harbor's `YYYY-MM-DD__HH-MM-SS` shape.
10413
12729
  status:
10414
12730
  type: string
10415
12731
  enum: [queued, running, completed]
@@ -10459,6 +12775,33 @@ components:
10459
12775
  format: date-time
10460
12776
  description: When the last task settled; null until every task has.
10461
12777
 
12778
+ CheckDefaults:
12779
+ type: object
12780
+ description: >
12781
+ The policy an empty `CheckConfigInput` resolves to (GET
12782
+ /api/checks/defaults) — each key the same value `Check` echoes for
12783
+ a check created with no config, except `prompt`, which `Check`
12784
+ serves as null (the built-in body) and this serves as that body's
12785
+ template text.
12786
+ required: [model_name, rubric, prompt, reasoning_effort, sandbox_provider]
12787
+ properties:
12788
+ model_name:
12789
+ type: string
12790
+ rubric:
12791
+ $ref: '#/components/schemas/Rubric'
12792
+ prompt:
12793
+ type: string
12794
+ description: >
12795
+ The built-in check prompt template, unrendered — pass it as
12796
+ `CheckConfigInput.prompt` to run the default body explicitly,
12797
+ or edit it from here.
12798
+ reasoning_effort:
12799
+ type: string
12800
+ description: The effort the default model runs at when the config names none.
12801
+ sandbox_provider:
12802
+ allOf:
12803
+ - $ref: '#/components/schemas/SandboxProvider'
12804
+
10462
12805
  CheckPage:
10463
12806
  type: object
10464
12807
  description: Cursor page of task quality checks (envelope keys frozen verbatim).
@@ -10781,6 +13124,324 @@ components:
10781
13124
  hasMore:
10782
13125
  type: boolean
10783
13126
 
13127
+ # The file system + sandbox logs family: one set of shapes under every run owner and the task package.
13128
+
13129
+ FilesystemState:
13130
+ type: string
13131
+ description: >
13132
+ Which source the file system routes answer from: `live` (the box
13133
+ runs), `captured` (the kept tree), `capturing` (the capture is being
13134
+ written), `none` (nothing to read).
13135
+ enum: [live, captured, none, capturing]
13136
+
13137
+ FilesystemBox:
13138
+ type: object
13139
+ description: The running box a live file system reads.
13140
+ required: [provider, id, role, since]
13141
+ properties:
13142
+ provider:
13143
+ $ref: '#/components/schemas/SandboxProvider'
13144
+ id:
13145
+ type: string
13146
+ role:
13147
+ type: string
13148
+ enum: [agent, verifier, analyzer, checker]
13149
+ since:
13150
+ type: string
13151
+ format: date-time
13152
+
13153
+ FilesystemCapture:
13154
+ type: object
13155
+ description: The kept tree's record.
13156
+ required: [id, at, phase, entries, changed_files, changed_bytes, status, left_out, failure_reason]
13157
+ properties:
13158
+ id:
13159
+ type: string
13160
+ at:
13161
+ type: [string, 'null']
13162
+ format: date-time
13163
+ description: When the capture settled; null on one abandoned mid-way.
13164
+ phase:
13165
+ type: string
13166
+ enum: [after_verifier, after_seal, after_run]
13167
+ description: >
13168
+ The moment it was taken at: after a shared verifier ran in the box,
13169
+ after the seal (a separate verifier runs elsewhere), or after a run
13170
+ that failed.
13171
+ entries:
13172
+ type: integer
13173
+ changed_files:
13174
+ type: integer
13175
+ changed_bytes:
13176
+ type: integer
13177
+ status:
13178
+ type: string
13179
+ enum: [ready, incomplete, failed]
13180
+ left_out:
13181
+ type: array
13182
+ items:
13183
+ type: string
13184
+ description: Every path the run touched whose bytes are not stored (`incomplete`), in the capture's order.
13185
+ failure_reason:
13186
+ type: [string, 'null']
13187
+ description: Why a `failed` capture stored nothing; null otherwise.
13188
+
13189
+ FilesystemStatus:
13190
+ type: object
13191
+ required: [state, box, watcher, root, work_dir, capture]
13192
+ properties:
13193
+ state:
13194
+ $ref: '#/components/schemas/FilesystemState'
13195
+ box:
13196
+ oneOf:
13197
+ - $ref: '#/components/schemas/FilesystemBox'
13198
+ - type: 'null'
13199
+ watcher:
13200
+ type: [string, 'null']
13201
+ enum: [native, poll, null]
13202
+ description: How live change events arrive; null when not live.
13203
+ root:
13204
+ type: string
13205
+ description: Always `/`.
13206
+ work_dir:
13207
+ type: string
13208
+ description: Where the run works (Harbor's `/app`).
13209
+ capture:
13210
+ oneOf:
13211
+ - $ref: '#/components/schemas/FilesystemCapture'
13212
+ - type: 'null'
13213
+
13214
+ TaskPackageFilesystemStatus:
13215
+ type: object
13216
+ description: The task package owner's status — the run shape with `state` always `none`, plus the package facts.
13217
+ required: [state, box, watcher, root, work_dir, capture, source, package_retained]
13218
+ properties:
13219
+ state:
13220
+ type: string
13221
+ enum: [none]
13222
+ box:
13223
+ type: 'null'
13224
+ watcher:
13225
+ type: 'null'
13226
+ root:
13227
+ type: string
13228
+ work_dir:
13229
+ type: string
13230
+ capture:
13231
+ type: 'null'
13232
+ source:
13233
+ type: string
13234
+ enum: [package]
13235
+ package_retained:
13236
+ type: boolean
13237
+
13238
+ FilesystemEntry:
13239
+ type: object
13240
+ description: One entry of a folder listing.
13241
+ required: [name, type, size, mtime, mode, owner, changed, phase, captured]
13242
+ properties:
13243
+ name:
13244
+ type: string
13245
+ type:
13246
+ type: string
13247
+ enum: [dir, file, symlink, other]
13248
+ size:
13249
+ type: integer
13250
+ mtime:
13251
+ type: string
13252
+ format: date-time
13253
+ mode:
13254
+ type: string
13255
+ description: Octal, four digits, e.g. `0644`; the same words from both sources.
13256
+ owner:
13257
+ type: string
13258
+ description: The owning user as the box names it, e.g. `root`; the same words from both sources.
13259
+ changed:
13260
+ type: [string, 'null']
13261
+ enum: [created, modified, null]
13262
+ description: What the run did to it since the box started; null when not known for the source.
13263
+ phase:
13264
+ type: [string, 'null']
13265
+ enum: [setup, agent, verifier, null]
13266
+ description: Which part of the run changed it — the platform's own setup, the agent, or the verifier.
13267
+ captured:
13268
+ type: [boolean, 'null']
13269
+ description: >
13270
+ Captured source: false = the run never touched it, only the
13271
+ image holds it (lists, does not open). Live: null. Package: true.
13272
+ left_out:
13273
+ type: string
13274
+ description: Captured source, a file the run touched whose bytes are not stored — why, in the capture's own words.
13275
+ target:
13276
+ type: string
13277
+ description: A symlink's target.
13278
+
13279
+ FilesystemListing:
13280
+ type: object
13281
+ required: [path, source, entries, next_cursor, ms]
13282
+ properties:
13283
+ path:
13284
+ type: string
13285
+ source:
13286
+ type: string
13287
+ enum: [live, capture, package]
13288
+ entries:
13289
+ type: array
13290
+ items:
13291
+ $ref: '#/components/schemas/FilesystemEntry'
13292
+ next_cursor:
13293
+ type: [string, 'null']
13294
+ ms:
13295
+ type: integer
13296
+ description: Server time spent, milliseconds.
13297
+
13298
+ FilesystemSearchHit:
13299
+ type: object
13300
+ required: [path, line, snippet]
13301
+ properties:
13302
+ path:
13303
+ type: string
13304
+ line:
13305
+ type: integer
13306
+ snippet:
13307
+ type: string
13308
+
13309
+ FilesystemSearchResult:
13310
+ type: object
13311
+ required: [hits, truncated, scope, source, ms]
13312
+ properties:
13313
+ hits:
13314
+ type: array
13315
+ items:
13316
+ $ref: '#/components/schemas/FilesystemSearchHit'
13317
+ truncated:
13318
+ type: boolean
13319
+ description: More hits exist, or the search budget ran out.
13320
+ scope:
13321
+ type: string
13322
+ enum: [path, box]
13323
+ source:
13324
+ type: string
13325
+ enum: [live, capture]
13326
+ image_files_excluded:
13327
+ type: boolean
13328
+ description: Captured source only — files the run never touched were not searchable.
13329
+ ms:
13330
+ type: integer
13331
+
13332
+ FilesystemChange:
13333
+ type: object
13334
+ required: [path, type, changed, phase, size, mtime]
13335
+ properties:
13336
+ path:
13337
+ type: string
13338
+ type:
13339
+ type: string
13340
+ enum: [dir, file, symlink, other]
13341
+ changed:
13342
+ type: string
13343
+ enum: [created, modified, removed]
13344
+ phase:
13345
+ type: string
13346
+ enum: [setup, agent, verifier]
13347
+ size:
13348
+ type: integer
13349
+ mtime:
13350
+ type: string
13351
+ format: date-time
13352
+ left_out:
13353
+ type: string
13354
+ description: Captured source, a file whose bytes are not stored — why, in the capture's own words.
13355
+
13356
+ FilesystemChanges:
13357
+ type: object
13358
+ required: [source, total, changed_bytes, items, next_cursor]
13359
+ properties:
13360
+ source:
13361
+ type: string
13362
+ enum: [live, capture]
13363
+ total:
13364
+ type: integer
13365
+ changed_bytes:
13366
+ type: integer
13367
+ items:
13368
+ type: array
13369
+ items:
13370
+ $ref: '#/components/schemas/FilesystemChange'
13371
+ next_cursor:
13372
+ type: [string, 'null']
13373
+
13374
+ FilesystemWatchRequest:
13375
+ type: object
13376
+ required: [paths]
13377
+ properties:
13378
+ paths:
13379
+ type: array
13380
+ maxItems: 8
13381
+ items:
13382
+ type: string
13383
+ description: The folders the viewer has open, absolute box paths; replaces the set.
13384
+
13385
+ FilesystemWatchResult:
13386
+ type: object
13387
+ required: [watcher, paths]
13388
+ properties:
13389
+ watcher:
13390
+ type: string
13391
+ enum: [native, poll]
13392
+ paths:
13393
+ type: array
13394
+ items:
13395
+ type: string
13396
+
13397
+ SandboxLogStream:
13398
+ type: string
13399
+ description: The named sandbox streams a run records.
13400
+ enum: [agent, verifier, setup, system, metrics]
13401
+
13402
+ SandboxLogLine:
13403
+ type: object
13404
+ required: [seq, t, fd, line]
13405
+ properties:
13406
+ seq:
13407
+ type: integer
13408
+ t:
13409
+ type: [string, 'null']
13410
+ format: date-time
13411
+ description: When the line was recorded; null when the record holds no time for it.
13412
+ fd:
13413
+ type: string
13414
+ enum: [out, err]
13415
+ line:
13416
+ type: string
13417
+
13418
+ SandboxLogLines:
13419
+ type: object
13420
+ description: One page of a stream.
13421
+ required: [stream, lines, next_cursor]
13422
+ properties:
13423
+ stream:
13424
+ $ref: '#/components/schemas/SandboxLogStream'
13425
+ lines:
13426
+ type: array
13427
+ items:
13428
+ $ref: '#/components/schemas/SandboxLogLine'
13429
+ next_cursor:
13430
+ type: [string, 'null']
13431
+ reason:
13432
+ type: string
13433
+ description: Why the page is empty when the platform holds nothing for this stream (never an error).
13434
+
13435
+ SandboxProcs:
13436
+ type: object
13437
+ required: [text, ms]
13438
+ properties:
13439
+ text:
13440
+ type: string
13441
+ description: The process listing as text.
13442
+ ms:
13443
+ type: integer
13444
+
10784
13445
  # -------------------------------------------------------------------------
10785
13446
  # Job archive (the downloadJob tar) — the Evolve extension keys inside the
10786
13447
  # otherwise Harbor-vocabulary record files. Not HTTP bodies: these schemas