Files
Reading a package, verifying it, uploading what an evaluation produced, and attaching it.
Reading
GET /api/v1/runner/files/{id}The package and the submission's files, by the ids the claimed job carried.
A Runner is authorized against the job it holds, not against being a Runner
Without that, any approved Runner could download every test package in the installation.
A file belonging to another problem answers 404, not 403. A file id is opaque, and a 403 would confirm the bytes exist.
That is a rule about file ids and only about them. The job endpoints answer
the other way round — a 404 for an id nobody issued, a 403 for a job another
Runner holds — because a Runner has to be able to tell a reclaimed job from a
Server that has forgotten it. Claiming says why.
sha256, and where it is checked
The integrity field is called sha256 — never hash — and it is a lowercase
hexadecimal SHA-256 of the bytes.
It is computed and checked three times over the life of one file, by three different parties, and each check exists because the previous one is a claim rather than evidence:
| Who | When |
|---|---|
| the Client | before uploading |
| the Server | over the bytes it actually received, refusing a mismatch |
| the Runner | over the bytes it actually received, before evaluating |
A Runner verifies packageSha256 before evaluating. A mismatch is an
infrastructure failure, not a verdict: the submission
was never judged, and a truncated download is not somebody's program being wrong.
Hash while streaming rather than by re-reading the file afterwards. The bytes are in hand once, and a second pass hashes whatever is on disk now rather than what came off the wire.
Uploading what an evaluation produced
POST /api/v1/runner/filesMultipart: the bytes as file, with a sha256 text field. The Server recomputes
the checksum and refuses to store on a mismatch — 422, checksum_mismatch —
exactly as it does for anybody else. It answers the stored file's id, name, media
type, size, checksum and creation time.
Attaching
Uploading stores bytes. Attaching says what they are of.
POST /api/v1/runner/jobs/{jobId}/files { "leaseToken": "…", "fileId": "…", "name": "log" }
POST /api/v1/runner/files/attach { "fileId": "…", "name": "runner.log" }A fileId must name a file this Runner uploaded through
POST /api/v1/runner/files and nothing points at yet — not one another Runner
uploaded, not one a person did, and not one already attached. One upload, one
reference: bytes wanted on the attempt and on the Runner are uploaded twice.
The two calls above say so — a 422 file.missing for a file that is none of
those, and a 409 attempt.file.duplicate for a name the attempt already
carries. A
report may also name its attachments inline, which the accepted contract does not
describe, and that path skips a bad entry in silence: an implementation
reusing an id from somewhere else loses its log with nothing said.
The first attaches to the attempt this Runner holds, and needs the lease
token. The second attaches to the Runner itself — a start-up log, a lscpu
dump — and replaces the file under that name rather than adding another, so a
chatty Runner costs a fixed amount.
name is a role, not a file name. Two are conventional on an attempt:
| Name | |
|---|---|
log | the compiler and sandbox output |
details | the per-test table |
Who may read either is the activity's attachment visibility, one row per name. An unlisted name is managers-only, which is the safe direction: a problem type inventing a third attachment does not accidentally publish it.
Why the per-test table is an attachment
Because a problem may have two thousand tests per attempt. A contest's submissions came to hundreds of megabytes in a database column before it moved out of one.
That is also why extra is bounded at 2 kB: the small
document rides a feed many people read, and the large one is a file fetched by
whoever is entitled to it.
Upload, attach, then report — the order cannot be worked around
The Server requires the job to be Running to accept an attachment, and
reporting ends that.
Report first and the attachment is refused, which means the log explaining a failure is the one thing that goes missing.
What the contract does not carry
The package format. This contract carries the bytes without reading them.
standard-io@1 has its own package format specification; another problem type
may define its own, and neither is part of the Server–Runner contract.