# CodeAPI Puzzle DSL v1

This document defines the deterministic rule language used by
`codeapi-puzzle/v1` black-box transform levels. The JSON representation is
validated by [`puzzle.schema.json`](./puzzle.schema.json).

## Execution model

A level accepts one JSON array of integers. Its private oracle is a `pipe` whose
`steps` run from first to last:

```json
{
  "op": "pipe",
  "steps": [
    { "op": "filter_mod", "modulus": 2, "remainder": 0 },
    { "op": "sort", "direction": "desc" },
    { "op": "take", "count": 3 }
  ]
}
```

Each step receives the complete output of the previous step. Every primitive
except `rle` consumes and returns an integer array. `rle` consumes an integer
array and returns a tuple array, so it MUST be the final step. A pipe without
`rle` MUST declare `io.output.kind` as `integer-array`; a pipe ending in `rle`
MUST declare it as `tuple-array`.

Integers use mathematical integer arithmetic, not machine overflow or
floating-point rounding. Every input, parameter, intermediate value, and output
MUST remain in the JSON safe-integer range
`[-9007199254740991, 9007199254740991]`. A level or query that would leave this
range is invalid; an implementation MUST reject it rather than wrap, clamp, or
round it.

All primitives are deterministic. They preserve input order unless their
definition explicitly changes it. Empty arrays are valid when permitted by
`io.input.minItems`; every primitive has the empty-array behavior stated below.

## v1 structural limits

The v1 submission contract uses the following hard limits. These are protocol
limits, not optional deployment recommendations:

| Field | Allowed value |
| --- | --- |
| `id` | 3–64 characters of lowercase kebab-case matching `^[a-z0-9]+(?:-[a-z0-9]+)*$` |
| `publicExamples.length` | 2 through 12 |
| `io.input.minItems` | 0 through 32 |
| `io.input.maxItems` | 0 through 32 |
| `limits.maxQueries` | 1 through 50 |
| `limits.hiddenTests` | 1 through 20 |
| `limits.timeoutSeconds` | 10 through 3600 |
| `private.oracle.steps.length` | 1 through 12 |

`io.input.minItems` MUST still be less than or equal to
`io.input.maxItems`.

`limits.timeoutSeconds` is the wall-clock lease for one solving session, from
session issuance through final submission. It is not an instruction to let one
DSL evaluation consume that long: a deployment MUST apply a much smaller,
platform-owned execution deadline to each oracle call.

## Primitive semantics

### `reverse`

```json
{ "op": "reverse" }
```

Returns the elements in reverse order. The empty array remains empty.

### `sort`

```json
{ "op": "sort", "direction": "asc" }
```

Sorts integers numerically. `asc` orders smallest to largest and `desc` orders
largest to smallest. Duplicate values are retained. The empty array remains
empty.

### `rotate`

```json
{ "op": "rotate", "direction": "left", "amount": 2 }
```

Let the input length be `n` and `k = amount mod n`. A left rotation moves the
first `k` elements to the end. A right rotation moves the last `k` elements to
the front. `amount` is an integer from `0` through `16` and may exceed `n`. An
empty input always returns an empty output; no modulo operation is performed
when `n = 0`. An amount of zero or any multiple of `n` leaves a non-empty input
unchanged.

Examples:

```text
rotate left  2: [1, 2, 3, 4] -> [3, 4, 1, 2]
rotate right 1: [1, 2, 3, 4] -> [4, 1, 2, 3]
```

### `affine`

```json
{ "op": "affine", "multiply": 3, "add": -1 }
```

Maps every element `x` to `multiply * x + add`. Input order and length are
preserved. `multiply` MUST be an integer from `-10` through `10`, and `add`
MUST be an integer from `-50` through `50`. The empty array remains empty.

### `power`

```json
{ "op": "power", "exponent": 2 }
```

Maps every element `x` to `x ^ exponent`. The exponent is exactly `2` or `3`.
Negative bases therefore become non-negative when squared and remain negative
when cubed. Input order and length are preserved. The empty array remains
empty.

### `filter_mod`

```json
{ "op": "filter_mod", "modulus": 3, "remainder": 2 }
```

Keeps, in original order, exactly the elements `x` for which
`mod(x, modulus) = remainder`. `modulus` MUST be an integer from `2` through
`12`, and `remainder` MUST satisfy `0 <= remainder < modulus`.

Modulo is the non-negative mathematical modulo:

```text
mod(x, m) = x - m * floor(x / m), for m > 0
```

Consequently, negative inputs have non-negative remainders:

```text
mod(-1, 3) = 2
mod(-4, 3) = 2
mod(-6, 3) = 0
```

This definition takes precedence over a host language's `%` behavior. The empty
array remains empty.

### `dedupe`

```json
{ "op": "dedupe" }
```

Keeps the first occurrence of each integer and removes later occurrences. It
does not sort.

```text
[3, 1, 3, 2, 1] -> [3, 1, 2]
```

The empty array remains empty.

### `take`

```json
{ "op": "take", "count": 3 }
```

Returns the first `min(count, n)` elements, where `n` is the input length.
`count` is an integer from `0` through `16`. `count = 0` returns an empty
array.

### `drop`

```json
{ "op": "drop", "count": 3 }
```

Removes the first `min(count, n)` elements, where `n` is the input length, and
returns the rest. `count` is an integer from `0` through `16`. `count = 0`
leaves the input unchanged.

### `adjacent`

```json
{ "op": "adjacent", "operator": "delta" }
```

Applies an operator to every overlapping adjacent pair `(current, next)`. For
input length `n`, the output length is `max(n - 1, 0)`.

| Operator | Output for each pair |
| --- | --- |
| `sum` | `current + next` |
| `delta` | `next - current` |
| `max` | the greater of `current` and `next` |
| `min` | the lesser of `current` and `next` |

In particular, `delta` is always **next minus current**:

```text
[3, 8, 2] -> [5, -6]
```

Inputs with zero or one element return an empty array.

### `chunk_reduce`

```json
{
  "op": "chunk_reduce",
  "size": 3,
  "operator": "sum",
  "partial": "include"
}
```

Splits the input from left to right into consecutive, non-overlapping chunks of
`size` elements. `size` MUST be an integer from `1` through `8`. Each retained
chunk produces one integer:

| Operator | Chunk result |
| --- | --- |
| `sum` | sum of all elements |
| `product` | product of all elements |
| `max` | greatest element |
| `min` | least element |

If the final chunk contains fewer than `size` elements, `partial: "include"`
reduces and returns it, while `partial: "drop"` discards it. Only the final
chunk can be partial.

```text
size 3, sum, include: [1, 2, 3, 4, 5] -> [6, 9]
size 3, sum, drop:    [1, 2, 3, 4, 5] -> [6]
```

An empty input returns an empty output. If `size` is greater than a non-empty
input length, `include` returns one reduced value and `drop` returns an empty
array.

### `rle`

```json
{ "op": "rle" }
```

Returns the run-length encoding of consecutive equal values. Each output tuple
is exactly `[value, count]`, where `count` is a positive integer. Separate runs
of the same value remain separate.

```text
[4, 4, 2, 4, 4, 4] -> [[4, 2], [2, 1], [4, 3]]
[]                    -> []
```

`rle` changes the data type to `tuple-array` and MUST be the last pipe step. No
v1 primitive accepts tuple-array input.

## Level-wide validation

Schema validation is necessary but not sufficient. Before accepting a level,
the platform MUST also enforce these relational rules:

1. `io.input.minItems <= io.input.maxItems`.
2. `io.input.minimum <= io.input.maximum`.
3. Every public input and every `private.hiddenInputs` entry satisfies the
   declared input length and value bounds.
4. Every public output equals the oracle result for its corresponding input.
5. `limits.hiddenTests` equals `private.hiddenInputs.length`.
6. For every `filter_mod`, `remainder < modulus`.
7. The pipe's final data type matches `io.output.kind`, and `rle`, if present,
   appears exactly once as the final step.
8. A conservative interval analysis proves that every input in the complete
   declared length/value domain keeps every arithmetic intermediate and output
   within the safe-integer range. Sampling alone is not sufficient.

`validation.mode: "exact"` means order, length, nesting, and every integer must
match. `normalization: "canonical-json"` means answers are parsed as JSON,
validated for the declared output kind, and compared structurally. Whitespace
and insignificant JSON textual formatting do not affect equality; strings,
floats, object-shaped tuples, and extra values are not coerced.

`private.hiddenInputs` contains author-supplied regression fixtures. They make a
complete submission package locally testable, but a ranked service MUST NOT use
them as the player's final test set. `limits.hiddenTests` still declares the
number of session-specific cases required in a ranked final submission.

`private.generation.strategy: "hmac-seeded-v1"` selects server-derived,
deterministic hidden inputs. The reference algorithm is:

1. Use a server-owned secret of at least 32 bytes and a unique, non-empty
   `sessionId` of at most 256 characters.
2. Define `context` as the JSON array
   `["codeapi-game","hmac-seeded-v1",spec,id,version,sessionId]`.
3. To sample one integer from the inclusive range `[minimum, maximum]`, set
   `span = maximum - minimum + 1` and
   `limit = 2^64 - (2^64 mod span)`.
4. Starting with `attempt = 0`, compute HMAC-SHA-256 over the UTF-8 JSON
   serialization of `[context,purpose,attempt]`. Interpret the first eight
   digest bytes as an unsigned big-endian 64-bit integer `candidate`. Reject it
   when `candidate >= limit`; otherwise return
   `minimum + (candidate mod span)`.
5. For case index `i`, sample its length with purpose `case:i:length` from
   `[io.input.minItems, io.input.maxItems]`. For item index `j`, sample its
   value with purpose `case:i:value:j` from
   `[io.input.minimum, io.input.maximum]`.

The JSON serialization in step 4 is the compact form with no whitespace;
strings use standard JSON escaping and integers use base-10 without leading
zeros. This is the behavior of `JSON.stringify` for the arrays above.

The rejection step avoids modulo bias. A ranked attempt generates exactly
`limits.hiddenTests` cases. Local validation may request more cases with the
same algorithm and a non-production validation key. The HMAC secret MUST never
be stored in a level, log, replay, or public API response. The v1 DSL exposes no
randomness to the oracle itself: for any particular input, the oracle result is
always identical.

Reference vector: for `seed-001-reverse-affine-add3@1`, a 32-byte secret filled
with `0x5a`, and `sessionId = "attempt-001"`, the five generated inputs are:

```json
[
  [10, -6, -11, -7, -15, 12, 19, 14, -17, 17],
  [7, -1, -8, -8, -17, 9, 17, -10],
  [2, 16],
  [0],
  [-5, -18, 1]
]
```

## Safety and publication boundary

This DSL is data, not executable code. A conforming runtime:

- MUST reject unknown fields, unknown operations, invalid types, and
  out-of-range arithmetic;
- MUST NOT evaluate submitted JavaScript, Python, shell commands, templates,
  WebAssembly, containers, or arbitrary plugins;
- MUST give oracle evaluation no filesystem, network, process, environment,
  clock, or random-number access;
- MUST enforce the v1 structural and primitive limits above, plus a
  deployment-level cap on output length and total work;
- SHOULD evaluate the allow-listed primitives in an isolated worker even though
  they are declarative; and
- MUST keep `private.oracle`, `private.hiddenInputs`, HMAC material, and
  unrevealed replays out of public puzzle responses.

`puzzle.schema.json` describes a complete author submission package.
`puzzle-public.schema.json` describes the solver-facing projection, and the
reference `createPublicLevel` implementation builds it with an explicit
allow-list. A public GitHub repository or puzzle endpoint containing the
submission's `private` object reveals the answer. Production storage must
therefore keep the complete package server-side and return only the public
projection. Seed/tutorial fixtures may intentionally publish their complete
rules, but cannot be ranked.

When `replay.revealRuleAfterSeason` is `true`, the platform may reveal the
oracle only after the season is closed and immutable. When it is `false`, that
flag grants no permission to reveal private rule data.
