AlgoJudge documentation0.2
Domain model

File and FileReference

Immutable bytes with a checksum, and the references that say what they are for and who may read them.

Two rows again, and the split earns its keep: File is bytes, FileReference is what those bytes are for.

The same bytes may be referenced twice — carrying a figure forward into a new problem version is a second reference, not a second upload. A scope column on the file would force a copy just to change who may read it.

File

FieldMeaning
Namethe name as uploaded, e.g. main.cpp. For a person to read
MimeType
SizeBytescounted by the Server as it stored them
Sha256lowercase hexadecimal SHA-256 of the stored bytes
StorageIdwhich configured store holds these bytes
UploadedByUserId
UploadedByRunnerIdset instead when a Runner uploaded it, which carries a token rather than a session. At most one of the two

Bytes are immutable. There is no replace: a corrected file is a new upload with a new id. That is what lets a pinned problem version mean something, and what licenses a permanent cache header.

SizeBytes is never asked of the backend at read time and never taken from a caller: it is what makes a Range request answerable without a round trip, so it has to be the number this Server itself saw.

StorageId names a store, not a kind of backend. A deployment may run several of the same kind — two buckets, two volumes — and the day one is retired, which files are still in there has to be a question the database can answer. A column saying s3 could not.

A file that no reference points at is readable by its uploader alone, which is what makes the two-step publish safe: between the upload and the version being published, the bytes exist and nobody else can see them. An unreferenced file is collected after twenty-four hours.

sha256

The field is `sha256`, never `hash`

The Client computes it, the Server recomputes it and refuses a mismatch with 422 checksum_mismatch, and a Runner verifies it before evaluating.

Since the bytes left the database it is not only a check but a coordinate: the store derives the path from it, so a row whose checksum was edited would point at nothing.

A checksum that arrives with the bytes is a claim, not evidence. What the recomputation buys is a truncated upload rejected as corrupt rather than judged as a wrong answer.

FileReference

FieldMeaning
FileIdthe bytes
OwnerKindwhat this reference hangs off
ScopeParticipant, Manager or Runner — who may read the file through this reference
Namethe name within the ownercontent.md, source, log, details, package.zip
LanguageBCP-47 subtag, where an owner keeps one document per language
ValidFrom, SupersededAtrevisions of an instance or activity document

Scope is the most security-relevant column in the schema — manager scope is where model solutions live — and it is checked when access is granted, never applied as a filter some later endpoint forgets. GET /files/{id} is allowed when at least one reference to it is readable by the caller.

The word means different audiences for different owners. Under a submission, Participant means its author: the word that publishes a statement to a whole activity publishes a compiler log to exactly one reader.

One owner does not consult it. A printout's reference is written with manager scope, and the rule that reads it asks instead for printout:manage in the printout's own activity — there is no second audience under a printout to tell apart, and the person who asked for it already had the text. The scope is a label there rather than a guard, which is worth knowing before building on it.

Name is what an activity's attachment rules key on, never the uploaded file name.

Owner kinds

ProblemVersion, ActivityDocument, InstanceDocument, InstanceLogo, Runner, Submission, Attempt, InstanceTheme, InstanceFont, Printout.

Exactly one owner column is set, and a check constraint holds it to agreeing with OwnerKind. The discriminator sits beside typed foreign keys rather than instead of them: a bare (kind, id) pair cannot be a foreign key, and this is the one table where a dangling row means handing somebody a model solution. The cost is stated plainly — a new owner kind is a migration, not a new enum value.

Well-known names

NameOwnerWhat it is
content.mdproblem versionthe statement. The Server has no statement concept — this is a name the Client understands
sourcesubmissionwhat was sent
logattemptwhat one run said
detailsattemptthe per-test document
sourceprintoutthe page somebody asked to have printed

A superseded document reference is marked, never deleted: deleting it would leave the file it names with no reference at all, and an unreferenced file goes in twenty-four hours.

A printout is the one owner whose bytes are meant to go

Every other owner keeps its file for as long as it exists. A printout is a page somebody wanted on paper, and once it is on paper the copy on the server is somebody's source code sitting where it has no further purpose.

So resolving a print request removes the reference and deletes the file, in that order, in the same call. It is not marked superseded: superseding keeps a reference row, an unreferenced file is what the collector looks for, and a reference that still exists is not one. Marking it would have read exactly like a disposal and kept the bytes indefinitely.

What deletion means here

The Server stops serving the source and removes it from the store. On a PostgreSQL store that is a DELETE: the row's old version survives in the table until autovacuum reclaims it, in the write-ahead log, and in every backup taken before the request was resolved. Nothing here erases anything from a backup.

The record of the request stays — who asked, when, for what, who printed it, and the checksum of what was printed. Only the bytes go. GET on the printout afterwards answers 200 with no source rather than 404, because the row is the audit trail.

The collector is the backstop for a delete that fails after the row is already stamped, not the mechanism. Its window is longer than it looks: the cutoff is measured from upload rather than from the moment a file lost its last reference, and the sweep runs once a day, so a file it is responsible for can last between one and two days.

On this page