# Submission spec

Every competition declares one `submissionSpec` in its detail response. It is the **only** source of truth for how to construct a submission for that competition — fields, validation, eligibility, window, size cap.

```json
{
  "kind": "model-reference",
  "schema": {
    "type": "object",
    "required": ["modelName", "commitHash"],
    "properties": {
      "modelName": {
        "type": "string",
        "pattern": "^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$"
      },
      "commitHash": {
        "type": "string",
        "pattern": "^[0-9a-fA-F]{40}$"
      }
    },
    "additionalProperties": false
  },
  "metaSchema": {
    "type": "object",
    "properties": {
      "description": {
        "type": "string",
        "maxLength": 5000
      }
    },
    "additionalProperties": false
  },
  "permission": {
    "teamRole": "owner"
  },
  "window": {
    "opensAt": "2026-04-01T00:00:00Z",
    "closesAt": "2026-09-30T23:59:59Z"
  },
  "limits": {
    "maxBytes": null,
    "maxEntries": 3
  }
}
```

## Fields

### `kind`

Stable enum identifying the submission shape:

- [`cheatsheet`](./submission-kinds.md#cheatsheet) — a text body conditioning a model.
- [`igp24-polynomial`](./submission-kinds.md#igp24-polynomial) — an array of degree 24 integer polynomial coefficient strings.
- [`model-reference`](./submission-kinds.md#model-reference) — a pointer to a model artifact (Hugging Face ID + commit).
- [`solver-participation`](./submission-kinds.md#solver-participation) — Lean 4 solver source for a Lean-judged competition.

A competition has exactly one `kind` for its lifetime; changing it would be a breaking change.

### `schema`

JSON Schema (draft 2020-12) for `payload`. Plug it into a client-side validator (e.g. `ajv` / `jsonschema`) to catch errors before sending.

```json
{
  "type": "object",
  "required": ["modelName", "commitHash"],
  "properties": {
    "modelName": { "type": "string", "pattern": "^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$" },
    "commitHash": { "type": "string", "pattern": "^[0-9a-f]{40}$" }
  },
  "additionalProperties": false
}
```

### `metaSchema`

JSON Schema for the optional top-level `meta` object. `meta` carries submission metadata and lineage; it is **not** part of the judged payload.

```json
{
  "type": "object",
  "properties": {
    "description": { "type": "string", "maxLength": 5000 },
    "contributorNetworkItemId": { "type": "string" }
  },
  "additionalProperties": false
}
```

`contributorNetworkItemId` records the Contributor Network item this submission references or builds on. It is available only for kinds whose `metaSchema` includes it, currently `cheatsheet` and `solver-participation`. It is not accepted for `model-reference` or `igp24-polynomial`.

`description.maxLength` is kind-specific. Read the competition detail response before submitting; for example, IGP24 uses a 500-character description limit.

### `permission`

```json
{ "permission": { "teamRole": "owner" } }
```

- `teamRole: "owner"` — only the team owner may submit. Returned as `TEAM_OWNER_REQUIRED` (403) for non-owners.
- `teamRole: "activeMember"` — any active member of the competition team may submit.
- `permission` absent — any enrolled member may submit.

This is the sole source for submission permissions; `kind` does not carry permission rules.

### `window`

```json
{ "window": { "opensAt": "2026-04-01T00:00:00Z", "closesAt": "2026-09-30T23:59:59Z" } }
```

Requests before `opensAt` return `SUBMISSION_WINDOW_NOT_OPEN` (403); after `closesAt`, `SUBMISSION_WINDOW_CLOSED` (403).

### `limits`

```json
{
  "limits": {
    "maxBytes": 1000000,
    "maxEntries": 1,
    "maxPolynomials": 1000,
    "maxRequestBytes": 1310720
  }
}
```

`maxBytes: null` means no explicit cap. Unless a submission kind defines a more specific unit, the cap covers the entire `payload` serialized as UTF-8 JSON.

`maxEntries` is a positive, kind-specific capacity value. For submission kinds that use numbered slots, such as `model-reference`, it defines the maximum number of current entries and the valid `slot` range from `1` through `maxEntries`. Modular Arithmetic Challenge currently returns `maxEntries: 3`. It does not limit the number of items returned by `/submissions/mine` for track-based or history-based submission kinds; use the kind-specific track rules or collection pagination metadata instead.

For `igp24-polynomial`, `maxBytes` uses the same canonical limit as the raw `submission.txt` body: 1,000,000 bytes. Measure the UTF-8 byte length after joining the complete `payload.polynomials` strings, including trailing comments, with newline separators. Blank lines and full-line comments are not array items.

`maxPolynomials` is optional. It is present for `igp24-polynomial` and limits `payload.polynomials.length`.

`maxRequestBytes` is also present for `igp24-polynomial`. It is a separate 1,310,720-byte guard on the normalized JSON request envelope, including `payload`, `meta`, and JSON quoting or escaping overhead. IGP24 requests must satisfy both `maxBytes` and `maxRequestBytes`.

### `catalog`

Optional. Present when a kind expects clients to choose from a finite, public set of IDs. Used by `solver-participation`:

```json
{
  "catalog": {
    "tracks": [
      { "id": "solo", "label": "Solo" },
      { "id": "marathon", "label": "Marathon" }
    ],
    "models": [
      { "id": "google-gemma-4-31b-it", "name": "Google: Gemma 4 31B" },
      { "id": "openai-gpt-oss-120b", "name": "OpenAI: GPT OSS 120B" }
    ]
  }
}
```

Send `catalog.tracks[].id` as `payload.track` and `catalog.models[].id` as `payload.modelId`. The same IDs are also reflected in `schema` as `enum` values.

#### Catalog fields

| Field      | Type                              | Description                                      |
| :--------- | :-------------------------------- | :----------------------------------------------- |
| `tracks[]` | [CatalogTrack](#catalog-track-fields)[] | Track IDs accepted by `payload.track`.          |
| `models[]` | [CatalogModel](#catalog-model-fields)[] | Model IDs accepted by `payload.modelId`.        |

#### Catalog track fields

| Field   | Type   | Description                            |
| :------ | :----- | :------------------------------------- |
| `id`    | string | Track ID, such as `solo` or `marathon`. |
| `label` | string | Human-readable track label.            |

#### Catalog model fields

| Field  | Type   | Description                  |
| :----- | :----- | :--------------------------- |
| `id`   | string | Model ID accepted by payload. |
| `name` | string | Human-readable model name.    |

## Versioning

Non-breaking changes to a `submissionSpec`:

- Adding optional fields to `schema` or `metaSchema`.
- Adding metadata fields (`name`, `description`) to `catalog` entries.

Breaking changes (only on a new competition season):

- Removing fields, adding required fields, tightening patterns.
- Removing a `catalog` ID that was valid for an active submission window.
- Changing `kind`.

New `kind` values may appear at any time for new competitions.
