Open Meet Exchange

Version 1.0. A minor version only adds; only a major breaks. Comments to [email protected].

OMX Conformance Tests

Each test has a check you run and an observable pass condition, so this list doubles as a test plan. Run the group for each stage your product runs, and skip the others:

Group Run it if your product
A. Registration Serves a registration document
B. Registration that names destinations Also puts a publish array in that document
C. Publisher Accepts results documents
D. Meet Operations Reads registration documents and hands off results documents

OMX-C107, in group A, applies to every stage. A test marked must is required for conformance, one marked should is a strong recommendation, and one marked may applies only if you build the optional feature it tests. A test’s id never changes, and an id the list skips is never reused.

Three tools do much of the work for you:

  • The strict schemas, the registration check registration-document-v1 and the results check results-document-v1, fail a value this version does not define, a date-time without seconds, the wrong omx.kind, and a field your stage does not write. No schema fails a field the format does not define (OMX-R129), so a misspelt key passes them: check your field names against the field reference.
  • omx-check.mjs checks what no schema can, and names the test each problem fails. It reads the document’s omx.kind; --kind registration or --kind results checks a document as that kind whatever it says.
  • Each test that needs a crafted document names a fixture. The fixture index lists them all with the outcome a conforming product produces (accept, reject, refuse-version, refuse-kind, refuse-results, older, equal, or operator), and the test page runs the Publisher tests against your endpoint. accept is what a reader does; the checker, which judges a document as a writer would send it, may still report problems in an accept fixture that a reader is meant to tolerate, such as null values or a later version’s values.

Fixture order. Each fixture’s updated_at is later than every example’s and than the fixture before it in the index, so the Publisher fixtures applied to one meet in the index’s order each apply. older and equal fixtures are sent right after c30-first, to a meet holding nothing later. A partial fixture is sent after the full document its test names.

You run every test yourself; none needs sign-off from 2Timer. If a test is ambiguous, that is a bug in the format: report it to [email protected].


A. Registration

Required of anyone serving a registration document, whether or not you want results back.

OMX-C1 — The document validates (must)

  • Check: validate your document against registration-document-v1 with any draft-07 validator, with no add-ons or options, and run node omx-check.mjs on it.
  • Pass: zero errors from both, including for a meet with relays, scratches, and at least one unattached athlete. The lenient reader schema accepts values a later minor version may define, so passing it alone does not pass this test.

OMX-C107 — Every document says its kind (must)

  • Check: fetch your document and read omx.kind. If you also read documents, offer your product the fixtures c107-missing-kind.omx.json and c107-unknown-kind.omx.json.
  • Pass: your document says "kind": "registration", and a results document you write says "results". Meet Operations refuses a document with no kind, or a kind it does not know, before creating anything, and names what it found. A Publisher answers both 422 with errors naming /omx/kind, and answers a registration document 422 too: it reads results documents only. No product works out a document’s kind from its fields (OMX-R135).
  • Fixtures: c107-missing-kind, c107-unknown-kind

OMX-C2 — Ids are stable across pulls (must)

  • Check: fetch the document, change something unrelated in your database, fetch again. Diff the id of every team, athlete, entry, event, and division.
  • Pass: no id changed. Regenerated ids break the results join and are the single most common implementation error.

OMX-C3 — Ids are unique within their list (must)

  • Check: for each of teams, athletes, entries, events, divisions, groups, waves, and publish, count distinct ids against list length. Count distinct team codes against the number of teams.
  • Pass: equal. omx-check.mjs checks this.
  • Fixture: c3-duplicate-ids.omx.json

OMX-C4 — References resolve (must)

  • Check: for every team_id, athlete_id, event_id, division_id, group_id, wave_id, and relay leg, look up the id in its list. In a results document, add multi_event_id, each entry’s round in its event’s rounds, group_places[].group_id, every split’s marker_id in its event’s markers, and every team_scores team, scope, and counted entry. A partial results document’s team scores may name teams and entries it does not carry.
  • Pass: every one is found. The schema cannot check this, so a validator run alone does not prove it; omx-check.mjs does.
  • Fixture: c4-unresolved-reference.omx.json

OMX-C108 — Entries can be joined (must)

  • Check: fetch a document with individual entries and relays, including two relays from one team in one event.
  • Pass: athlete + event names one entry: no athlete is entered twice in one event. Every relay entry carries relay.letter, and team + letter + event names one relay entry. A Publisher joins a result back to your entry through these, because no entry carries an external_ids (OMX-R136). A results document keeps the same rule within each round, and every relay in it carries its letter.
  • Fixture: c108-entered-twice.omx.json

OMX-C5 — HTTPS only (must)

  • Check: confirm the document URL and every publish[].url begins https://.
  • Pass: all do. Meet Operations refuses plain HTTP.

OMX-C6 — The URL is a revocable capability (must)

  • Check: revoke the link in your own UI, then fetch it again.
  • Pass: the fetch answers 410. Then confirm the meet director can mint a new one without losing the meet.

OMX-C7 — The URL secret is strong (must)

  • Check: read the code that mints the document URL secret.
  • Pass: it draws at least 128 bits from a cryptographically secure generator (22 or more base62 characters, not counting any prefix), and your logs redact it.

OMX-C8 — Conditional requests work (should)

  • Check: fetch, keep the ETag, fetch again with If-None-Match.
  • Pass: 304 with no body. Then change one entry and repeat.
  • Pass: 200 with a new ETag. Meet Operations may fetch your link more than once; a 304 costs nothing.

OMX-C9 — Browsers can fetch the document (must)

  • Check: from a web page on another origin, send OPTIONS and then a GET with If-None-Match to the document URL.
  • Pass: both succeed. Responses carry Access-Control-Allow-Origin: * and expose ETag, the preflight allows If-None-Match, and nothing relies on cookies.

OMX-C100 — The document URL answers without a redirect (must)

  • Check: from a web page on another origin, fetch the document URL exactly as you give it out: the form in your copy-link button, emails, and help pages.
  • Pass: the URL itself answers 200; nothing answers 301, 302, 307, or 308. A browser cannot follow a cross-origin redirect that lacks CORS headers, and Meet Operations sees that as a URL it cannot reach.

OMX-C10 — A scratch is a status, not an omission (must)

  • Check: scratch an athlete in your product and fetch the document.
  • Pass: the entry is still present with status: "scr". An entry that vanished instead tells Meet Operations nothing: whether it treats the athlete as scratched is its own guess.

OMX-C11 — Marks are in the unit you measured (must)

  • Check: inspect seeds for a track event, a field event measured in inches, and a multi-event.
  • Pass: ms, mm, and pt values are integers; in has at most two decimal places; no imperial mark was converted to metric before sending; and each mark’s unit is one its event’s code ranks by: ms for a race, mm or in for a field event, pt for a multi-event.

OMX-C12 — Age or birth date, never both (must)

  • Check: fetch a document for a meet with age groups or an age-spanned division.
  • Pass: no athlete carries both age and birth_date. One whose age your product computes for this meet carries age (OMX-R128).

OMX-C13 — Registration writes only what Registration writes (must)

  • Check: fetch your document and look for fields Meet Operations writes.
  • Pass: no entry carries round, heat, lane, result, net_result, a place, group_places, qualified, flags, wind_mps, reaction_time_ms, points, multi_points, splits, field_series, or official_note, and none carries a bib: the bib is the athlete’s. No event carries rounds or markers, the meet carries no origin or track, omx carries no update or updated_at, and there are no team_scores. Meet Operations that meets one of these in a registration document still reads the meet: it ignores the field and tells the timer.
  • Fixture: c13-registration-writes-results.omx.json: Meet Operations reads it and reports the stray fields; omx-check.mjs reports them as errors.

OMX-C14 — Every event has a code for the meet’s sport (must)

  • Check: fetch a document with every event your product offers.
  • Pass: every event’s code is one the spec defines for the meet’s sport, spelled exactly: SP and 3000S are track codes, not run or xc codes. A race’s code is its advertised distance, not what the course measured: the Riverbend “5K”, which measures 4.92 km, is 5000. Each distance has one code: a half marathon is HMAR and a marathon MAR, never 21097 or 13.1MILE. A multi-event (DEC, HEP, PENT) may be for any gender. A registration document lists no component events. An event your product holds that has no code for the sport is not in the document.

OMX-C15 — Multi-event entries go on the multi-event (must)

  • Check: enter one athlete in a decathlon and fetch the document.
  • Pass: the athlete has one entry, on the DEC event, with any seed in pt. No entry points at a component event, and the document lists no component events: Meet Operations builds them.

OMX-C88 — Only values this version defines (must)

  • Check: fetch a document with every sport your product offers and every value you write in an open list.
  • Pass: each is a value the format defines, spelled as the format spells it. A value of your own fails, because a later version may define it to mean something else (OMX-R129). The strict schema reports it, and omx-check.mjs also reports an undefined sport or event code.
  • Fixture: c88-bare-values.omx.json

OMX-C103 — Registration writes only its statuses, and no origin (must)

  • Check: fetch a document with an entered, a scratched, and a wait-listed athlete, and one you hold as unconfirmed or checked in.
  • Pass: every entry’s status is e, scr, or wait, or absent, which means e (OMX-R186); an unconfirmed entry is left out or sent as e, since Entered means an entrant, confirmed or not; and meet carries no origin. Checked In and every later status are set at the meet. There is no Pending: p is not a status in this version (OMX-R151), and the reader schema refuses it.

OMX-C106 — Your system id, and never your own id in external_ids (must)

  • Check: fetch a document whose athletes, teams, events, or meet carry ids on other systems.
  • Pass: omx.generator.id is your system id, a name you picked, and it is the same in every document you write. No record lists that id in its external_ids: your own id for a record is its id (OMX-R130). Ids on other systems sit under theirs, such as an athlete’s Athletic.net id under anet. No entry carries external_ids at all (OMX-R136). omx-check.mjs checks both, for every kind.

B. Registration that names destinations

Add these when your document carries a publish array. Skip the whole group if it does not; Registration with no destinations is a complete implementation.

OMX-C18 — Destination ids are stable and unique (must)

  • Check: fetch twice and diff publish[].id; count distinct ids against array length.
  • Pass: unchanged and unique. Meet Operations reports each destination’s status against its id, and a timer who fetches your link again should see the same destinations, not new ones.

OMX-C19 — Every destination has a label (should)

  • Pass: label is present and reads as a name a meet director would recognise. Without it the timer is asked to approve a bare URL.

OMX-C20 — Credentials are separate (must)

  • Check: compare each publish[].token against the document URL secret and against each other.
  • Pass: all distinct. Holding the read link must not grant the right to write results, and one compromised destination must not implicate another.

OMX-C21 — deliver and content were chosen, not defaulted by accident (must)

  • Check: for each destination, state which deliver (final, progressive, or live) and which content (results, and start_lists or not) it asks for, and why.
  • Pass: you can answer. The default is final with results only, so a destination that wanted the meet as it unfolds, or heat sheets, and said nothing will appear broken: it sees nothing until every round is official, and never a heat sheet.

C. Publisher

Required of anyone that accepts results documents.

OMX-C22 — Accepts a document (must)

  • Check: POST a valid full results document with the right bearer token.
  • Pass: 200 or 204.

OMX-C23 — Browsers can hand off (must)

  • Check: from a web page on another origin, send OPTIONS and then a POST with Authorization, Content-Type: application/json, and Content-Encoding: gzip to your endpoint.
  • Pass: both succeed. The preflight allows Authorization, Content-Type, Content-Encoding, and Content-Digest; responses carry Access-Control-Allow-Origin: * and expose Retry-After.

OMX-C101 — The push URL answers without a redirect (must)

  • Check: send OPTIONS and a POST to the url your destination names, exactly as written, from a web page on another origin.
  • Pass: the URL itself answers both; neither is redirected. A browser never redirects a preflight, and drops Authorization when a redirect leaves the origin.

OMX-C24 — Rejects a bad credential (must)

  • Check: POST with a wrong token, then with one you have retired: expired by your own rule, or revoked by the meet director.
  • Pass: 401, then 410. You decide how long a token works, and keep it working long enough for corrections after the meet; once it stops, you answer 410 (OMX-R164). Anything else leaves the timer without a usable message.

OMX-C125 — Answers the probe correctly, if it answers (may)

  • Check: send GET to your destination url with the right bearer token, then with a wrong one, from a web page on another origin.
  • Pass: either you do not answer probes (404 or 405), or the right token gets 200 with { "accepts": [...], "generator": { "id", "name", "version"? } }, where accepts lists the versions you read and generator names you as your documents do, or 503 with Retry-After while you are not ready; and the wrong token gets 401. The preflight allows GET, the response carries Access-Control-Allow-Origin: *, and nothing you hold changes.

OMX-C25 — Tokens you issue are strong (must)

  • Check: read the code that mints destination tokens.
  • Pass: at least 128 bits from a cryptographically secure generator, compared in constant time, and redacted from logs.

OMX-C26 — Refuses a version it does not implement (must)

  • Check: POST a document with omx.version: "9.0".
  • Pass: 422, with a message naming the version offered and the versions you accept, not a schema error: read the version before validating. Nothing you hold changes.
  • Fixture: c26-unknown-version.omx.json

OMX-C99 — Says which versions it reads (should)

  • Check: POST a document with omx.version: "9.0".
  • Pass: the 422 body carries accepts, listing every version you read, such as ["1.0"], so Meet Operations knows what you read. If your destination is named in a publish item or a destination card, accepts there lists the same versions.
  • Fixture: c26-unknown-version.omx.json

OMX-C27 — Explains an invalid document (should)

  • Check: POST a document with an entry whose result.value is not a number.
  • Pass: 400 or 422, with errors naming the path, so the timer can pass it on. Nothing you hold changes.
  • Fixture: c27-invalid-mark.omx.json

OMX-C28 — A repeat handoff is a no-op (must)

  • Check: send the same document twice. Snapshot your stored state after each.
  • Pass: identical. No duplicated entries, no doubled counts.

OMX-C29 — A full document supersedes everything before it (must)

  • Check: send a full document, then a corrected full one with a later updated_at, the same omx.generator.id, and the same meet.id.
  • Pass: what you hold matches the second document exactly, with nothing left over from the first: a round, entry, event, team, or athlete the second lacks is gone. You key a meet on omx.generator.id plus meet.id, whichever credential it arrives on; a document with the same meet.id under another generator.id is another product’s meet (OMX-C91).

OMX-C30 — An older document is refused (must)

  • Check: send a document with omx.updated_at of 2026-10-03T14:41:52.418-04:00, then one with 2026-10-03T14:40:00.000-04:00. Then send 2026-10-03T18:41:52.418Z, the first instant written with a different offset.
  • Pass: the second answers 409 and what you hold still matches the first. The third is treated as equal: 200, no change. Comparing the strings as text fails this test. The rule is the same whatever each document’s update.
  • Fixtures: c30-first.omx.json, c30-older.omx.json, and c30-same-instant.omx.json, sent in that order

OMX-C31 — A missed handoff heals itself (must)

  • Check: drop a handoff entirely, then accept the next one.
  • Pass: you are fully correct. Nothing has to detect the gap or replay a log: a partial document carries every round changed since the last document you accepted, and a full one carries the whole meet. If you lost what you accepted, you answer 412 and the next document is full (OMX-C121).

OMX-C32 — Creates a meet it has never seen (must)

  • Check: with no prior knowledge and no Registration anywhere, accept a full document.
  • Pass: you can create the meet (name, sport, date, time zone when given, venue, schedule, events, teams, athletes, entries) without asking anyone for anything.

OMX-C33 — Reads meet.origin (must)

  • Check: accept a meet whose origin.id is the document’s own omx.generator.id: the timer built it.
  • Pass: you do not wait for, warn about, or error on missing Registration ids. Such a meet was built without Registration and never will have one.

OMX-C34 — Joins through athletes and events (must)

  • Check: accept a document whose athletes, teams, and events carry your id in external_ids, under your system id, or an id on another system you know, such as anet.
  • Pass: each athlete, team, and event is attached to your record by that id, never by name. An entry carries no external_ids: you find it through its athlete and its event, a relay through its team, letter, and event, and a multi-event component through its athlete and the component event, whose multi_event_id leads to the multi-event.

OMX-C35 — Keeps entries with no Registration id (must)

  • Check: accept a document built from a registration document (its origin.id is Registration’s) containing a walk-up athlete the timer created, with an entry.
  • Pass: the entry appears, with its athlete’s own name, team, and bib. Dropping it is a failure: a dropped entry means incomplete results and a wrong team score, published as if correct.

OMX-C36 — Survives an athlete acquiring a Registration id (must)

  • Check: accept an athlete with no Registration id, then accept a later document where that same athlete id carries one in external_ids.
  • Pass: one athlete, updated. Two means your storage is keyed on the external id and will duplicate every walk-up the timer later merges against a roster.

OMX-C37 — Compares on result.value, not display (must)

  • Check: ingest two results of 10.605 and 10.614, both displaying as “10.61”.
  • Pass: you can tell them apart and order them correctly. A tie exists only where two entries in a round share a place, so comparing display strings invents ties that are not there.

OMX-C38 — Handles a non-placer that carries a result (must)

  • Check: ingest an entry with status: "dq" and a populated result.
  • Pass: the time or mark is retained and displayed, and no place is assigned. Whether an entry places is decided by status alone, never by whether result is populated.

OMX-C40 — Invents no status codes (must)

  • Check: ingest an entry with status: "nr" in a race and one in a shot put, and a dnf on a decathlon’s own entry.
  • Pass: the nr entries are stored as No Result with no value, and rendered as NT and NM. The decathlete is shown as having abandoned, with no place in the standings. If you wrote NT, NH, or NM into a status field, or rendered NR on a results sheet, that is a failure: NT, NM, and NH are how nr prints, not statuses.

OMX-C41 — Never recomputes (must)

  • Check: accept a result set whose places or team scores you would compute differently, such as a tie or an XC score with displacers.
  • Pass: you display the published values unchanged. Two entries in one round with the same place are tied; you print them as tied without a separate flag.

OMX-C42 — Ignores what it does not know (must)

  • Check: accept a document containing an unknown optional field, a camelCase key, a $schema key, and a field prefixed with another product’s name (examplereg:season_id).
  • Pass: accepted. Never fail an ingest over an unrecognised field.
  • Fixture: c42-unknown-fields.omx.json

OMX-C43 — Treats null as absent (must)

  • Check: accept a document where an entry has "lane": null and a finisher has "result": null.
  • Pass: accepted. The entry has no lane, and the finisher is a finisher with no time.
  • Fixture: c43-nulls.omx.json

OMX-C44 — Publishes a multi-event at both levels (should)

  • Check: accept a results document with a scored heptathlon.
  • Pass: you can show the standings from the multi-event entries, each component’s results from its own event, and one athlete’s whole competition by grouping their component entries: the same athlete, in the events whose multi_event_id names the heptathlon. A component entry’s multi_points is what it earned; its points, if any, are team points.

OMX-C45 — Keeps personal data private (must)

  • Check: accept a document where an athlete has a birth_date, an email, a phone, and notes, as a non-conforming writer might send.
  • Pass: nothing you publish shows any birth date, email, phone number, or note. A conforming results document carries none of them: it lists only the athletes its entries name, with the age Meet Operations placed them by.

OMX-C46 — Rate limiting says when to come back (should)

  • Check: if you rate-limit, exceed the limit.
  • Pass: 429 with a Retry-After header.

OMX-C82 — Accepts a large meet (must)

  • Check: POST a valid document that is 64 MB uncompressed, once as is and once gzipped. The test page builds one.
  • Pass: 200 or 204 both times. A 413 for a document below 64 MB uncompressed is a failure (OMX-R182).

OMX-C109 — Accepts a gzip body (must)

  • Check: POST a valid document with Content-Encoding: gzip and the gzipped document as the body, then the same document uncompressed.
  • Pass: both are accepted and give the same stored state. Meet Operations may compress any handoff, or not (OMX-R140).

OMX-C83 — Gives the timer a destination card (should)

  • Check: set up a destination in your product the way a meet director would.
  • Pass: you show a destination card with a copy button, or offer it as a file, and it validates against destination-card-v1, with the deliver and content you want. Without one, a timer has to copy a URL and a token by hand.

OMX-C84 — Takes the event’s result from the final (must)

  • Check: accept a document in which an event’s prelims are official with places and its final, marked final: true, is upcoming with no entries.
  • Pass: you show no result for the event yet, and you do not present the prelim places as the event’s places. When the final is official, its places are the event’s. You never decide the final from a round’s number or label.
  • Fixture: c84-final-not-run.omx.json

OMX-C110 — Shows a mark in a unit it does not know (must)

  • Check: accept a document with a result whose unit is one this version does not define, with a display.
  • Pass: accepted. The result is shown by its display, and is never compared, sorted, or converted. The places that came with it are shown as given (OMX-R141).
  • Fixture: c110-unknown-unit.omx.json

OMX-C90 — Reads values from a later version safely (must)

  • Check: accept a document carrying a round with status: "provisional" and timing: "laser".
  • Pass: accepted. The round is shown as not official and the timing as unknown. An unknown value in an open list never fails an import, and never makes a result look official.
  • Fixture: c90-later-values.omx.json

OMX-C91 — Keeps two Meet Operations products apart (must, if you are also Registration)

  • Check: accept the cross country results example, then a document for the same registration meet (the same Registration id in meet.external_ids) under a different omx.generator.id and meet.id, from another Meet Operations product.
  • Pass: you hold both, and neither replaced the other; your operator is told the meet has a second Meet Operations product. Each is then replaced only by its own later documents.
  • Fixture: c91-second-timing-system.omx.json, sent after xc-invitational-returned.json

OMX-C92 — Prints team scorers from the standing (should)

  • Check: accept a cross country results document whose standings list their entries.
  • Pass: a team’s line shows its scorers and its displacers as the standing lists them, each with its entry’s own points: “45 (3-7-9-12-14) [18-22]”, with no recomputation. A displacer’s points is its scoring position.

OMX-C111 — Applies a partial document (must)

  • Check: send c30-first.omx.json, a full document, then c111-partial.omx.json, a later partial one carrying one event’s round.
  • Pass: that round is replaced whole: its status, its heats, and every entry in it, an entry it no longer lists gone. The team score blocks it carries are replaced by their id. The meet, divisions, groups, waves, events, teams, and athletes it carries are added or updated by id (OMX-R180). Every other round, entry, event, team, and athlete is exactly as c30-first left it: a partial document deletes nothing outside its rounds.

OMX-C112 — Refuses an unknown update (must)

  • Check: POST a results document whose omx.update is a value this version does not define.
  • Pass: 422 with errors naming /omx/update, and nothing you hold changes. Reading it as full would delete every round it does not carry.
  • Fixture: c112-unknown-update.omx.json

OMX-C113 — Shows a cancelled round (must)

  • Check: accept a document in which one event’s round is cancelled, with no entries.
  • Pass: the event is shown as cancelled, not as “results to come”, and no result is shown for it.
  • Fixture: c113-cancelled-round.omx.json

OMX-C114 — Shows athletes still competing (should)

  • Check: send road-5k-returned.json, a full document, then accept a later partial document whose round is started, with finishers so far carrying their overall_place and runners still on the course as s, some with splits.
  • Pass: the finishers are shown with their places, marked as able to move while the round runs; the s runners are shown as still competing, with any splits and no place; and the round is not shown as official.
  • Fixture: c114-race-in-progress.omx.json

OMX-C120 — Shows an event whose code it does not know (must)

  • Check: accept a results document with an event whose code this version does not define, such as a later minor version’s 600Y, with placed entries, an nr entry, and a team score block.
  • Pass: the event is shown by its name, with places as given, marks by display, the nr entry printed as NR, and its team scores as sent. Nothing in it is dropped or recomputed (OMX-R179).
  • Fixture: c120-unknown-code.omx.json

OMX-C121 — Asks for the whole meet (must)

  • Check: with nothing held for a meet, accept a partial document for it; then accept a full one; then a partial again.
  • Pass: the first partial gets 412, and nothing is stored from it; the full gets 200 or 204; the second partial is applied. A Publisher that restores a backup or refused a full with 413 answers 412 to every partial until a full arrives (OMX-R180).
  • Fixture: c111-partial.omx.json and c30-first.omx.json

D. Meet Operations

Required of timing or meet-management software that reads registration documents and hands off results documents.

OMX-C50 — Builds a meet from a pasted URL (must)

  • Check: paste a registration document URL into an empty install.
  • Pass: venue, schedule, divisions, events, teams, athletes, and entries exist with no wizard and nothing typed twice. Fetching the same link again later returns the same kind of complete document; how you merge it is your own decision.

OMX-C105 — Leaves out an event whose code it does not know (must)

  • Check: read a registration document with an event of code SWIM, which this version does not define.
  • Pass: the rest of the meet is read, and that event and its entries are not: the timer is told which event was left out and why. This is Meet Operations’ rule; a Publisher shows such an event (OMX-C120). The event is never turned into one your product knows from its name. An event code is an open list, so a later minor version’s event never fails a whole meet (OMX-R132). A writer’s checker still reports the code, because a writer sends only codes this version defines.
  • Fixture: c105-undefined-code.omx.json

OMX-C104 — Keeps the ids other systems gave a record (must)

  • Check: read a registration document whose meet carries external_ids with milesplit and anet, and whose athletes carry anet ids and an id under a system your product does not know, such as "exampleresults": "77812". Then hand the meet off.
  • Pass: the results document carries every one of those ids, unchanged, on the same records, beside Registration’s own id under Registration’s system id, whether or not your product knows the system (OMX-R142). A Publisher keyed on any of them can match its records. The timer may edit or remove one.

OMX-C102 — Refuses a results document (must)

  • Check: offer a document whose omx.kind is "results", with results and without. Then offer a registration document.
  • Pass: both results documents are refused before anything is created or changed, and the timer is told that a meet is moved between computers with your product’s own backup. The registration document reads as usual. Meet Operations takes only Registration’s handoff; results only ever go forward, to a Publisher. The kind is read from omx.kind, never worked out from the document’s fields (OMX-R135).
  • Fixtures: c102-results-document.omx.json, refused; the cross country registration example reads as usual

OMX-C51 — Reads a document from a file (should)

  • Check: open a document saved as meet.omx.json, and the same document saved as meet.json.
  • Pass: both build the same meet as fetching it from a URL would.

OMX-C52 — Refuses plain HTTP (must)

  • Pass: an http:// document or destination URL is rejected with a clear message.

OMX-C53 — Refuses an unknown major, reads a later minor (must)

  • Check: offer a document with omx.version: "9.0", one carrying a draft number such as "0.9", and one carrying a later minor of the major you implement, such as "1.7".
  • Pass: the first two refused, naming the version offered and the versions you implement. Importing what you half-understand is worse than failing. The later minor is read: a minor only adds, so you ignore what you do not know (OMX-R175).
  • Fixtures: c53-unknown-draft.omx.json, c53-later-minor.omx.json

OMX-C123 — One malformed value never fails a whole meet (must)

  • Check: read a registration document whose athlete carries country: "JPN", whose team code is 20 characters, and whose destination’s token is 12 characters.
  • Pass: the meet is read, and the timer is warned about each value. The writer’s own checks still fail the document: the strict schema keeps every format (OMX-R181).
  • Fixture: c123-malformed-values.omx.json

OMX-C55 — Keeps the meet when its link is revoked (must)

  • Check: set up from a document URL, then have Registration revoke it and fetch the link again.
  • Pass: the 410 is shown to the timer as a link that no longer works, and the meet, its results, and its destinations are all still there.

OMX-C61 — A multi-event is built, never guessed (must)

  • Check: read a document with a HEP event with gender M in a tf-outdoor meet, which the spec’s default table does not list.
  • Pass: you build its component events by your own rules for that meet, or, if you have none, you report the event to the timer and leave it out. You never guess its events from its name. The default table is where you start, not a limit on what Registration may send (OMX-R139).
  • Fixture: c61-multi-off-table.omx.json

OMX-C63 — Exhibition athletes never score (must)

  • Check: score an event in which an exhibition: true athlete finishes between two scorers.
  • Pass: the exhibition athlete holds a place and any award, scores nothing, and the next scorer is not pushed down a scoring slot.

OMX-C124 — Writes team scores (must)

  • Check: score a cross country race with one complete team and one too small to score, and a track meet with an event’s score and the meet’s total; hand off twice.
  • Pass: every team_scores block has an id, unique in the document and the same in both handoffs. A sprinter’s points is one value, which the event’s block and the meet’s total both use. The incomplete team is listed with complete: false and no place, points, or entries; its runners carry no points, and the other team’s scoring positions skip them (OMX-R178). omx-check.mjs checks the incomplete team.

OMX-C64 — Every handoff stands on its own (must)

  • Check: capture what you send to a destination at three points in a meet.
  • Pass: each full document is a whole meet document. Each partial document carries each of its rounds whole, with every athlete and team its entries name, so it makes sense applied to the last document the Publisher accepted, whichever that was. If any of them needs a document the Publisher may have missed, that is a failure.

OMX-C65 — A handoff carries no secrets or private data (must)

  • Check: set up from a document naming two destinations, with an athlete carrying a birth date, an email, a phone, notes, and a chip, a meet and an event carrying notes, and an extra field of Registration’s own on an athlete. Hand off.
  • Pass: no handoff contains a publish array, a token, any athlete email, phone, notes, birth_date, or chip, the meet’s or an event’s notes, or Registration’s extra field. The athlete carries the age you placed them by, and the document lists only the athletes and teams its entries name: a scratched athlete with no other entry is not in it.

OMX-C66 — meet.origin is honest (must)

  • Check: hand off a meet built from a registration document, and one the timer built from nothing.
  • Pass: omx.generator.id is your system id. The first meet’s origin is Registration’s { id, name } and the second’s is your own. The first carries your own ids in id and Registration’s id unchanged in external_ids, under Registration’s system id, on the meet and every event, team, and athlete that came from Registration, and on nothing else (OMX-R134, OMX-R136): no entry carries external_ids. Neither lists your own system id in external_ids (OMX-C106).

OMX-C67 — Publishes stored values (must)

  • Check: hand off a race whose event displays hundredths, with two runners whose stored times differ only in thousandths.
  • Pass: result.value is the stored thousandths value, display is the hundredths string, and the two hold different places. A tie is two entries with the same place, decided at stored value; there is no tied flag and no precision.

OMX-C68 — Does not re-encode statuses (must)

  • Check: hand off a DQ that has a recorded time, a DNS, a thrower with three fouls, a finisher placed by the finish order with no time, and a decathlete who abandoned after seven events.
  • Pass: dq with result populated and no place; dns and nr with no result and no place; the finisher with its overall_place, no status, and no result key; every entry with exactly one of status and overall_place (OMX-R177); the decathlete’s own entry dnf with no place, and their seven component entries standing. dnf appears only in a race or on a multi-event’s own entry, and fs only in a timed track event. No invented status codes anywhere in the document.

OMX-C69 — Omits the unknown, never writes null (must)

  • Check: hand off a meet with an unlaned race, a finisher placed with no time, and a field event.
  • Pass: no key in the document has the value null. The unlaned entries have no lane key, and the finisher carries its overall_place, no status, and no result key (OMX-R177).

OMX-C70 — Gun and net times stay separate (must)

  • Check: hand off a road race with a start mat, placed overall by gun time and in age groups by net time.
  • Pass: every finisher’s result is the gun time and net_result the net time, and the round carries place_by with overall: "gun" and groups: "net". Gender places follow the same clock as overall places.

OMX-C71 — Carries what it knows about how the result came to be (should)

  • Check: hand off a prelims-and-finals track event, a hand-timed race, a horizontal jump, and a high jump.
  • Pass: each prelim entry that advanced carries qualified as Q, q, qD, qR, or qJ, the letter its result prints; every round carries timing; each race heat’s wind is on the heat as wind_mps, and a field entry’s wind_mps is the wind of the mark that counted; each horizontal jump attempt in field_series carries outcome from o, x, -, r, its mark when valid, and its own wind_mps where one was read; each high jump attempt carries the bar height as mark whether it was cleared or missed. A product that does not record one of these omits it rather than guessing.

OMX-C72 — One entry per round (must)

  • Check: hand off a track event with a prelim and a final.
  • Pass: an athlete who ran both appears as two entries with two ids, round: 1 and round: 2, and neither carries external_ids: a Publisher finds both through the athlete and the event. The prelim entry carries qualified; the final carries the place. No entry is ever overwritten by a later round.

OMX-C73 — Multi-events come back at both levels (must)

  • Check: read a document that lists a heptathlon with no component events, score it, and hand it off in a full document.
  • Pass: the document lists every component event actually contested, each with multi_event_id, built by your rules (the spec’s table is the default). Each athlete’s multi-event entry carries the points total as result in pt and overall_place; each of their component entries carries result and multi_points. The component events, which you built, carry no Registration id.

OMX-C74 — Every handoff is ordered (must)

  • Check: hand off a meet twice with no change in between. Change one result and hand off. Delete one entry and hand off. Set the computer’s clock back an hour, change a result, and hand off.
  • Pass: every document carries omx.updated_at with milliseconds. The first two are equal and every later one is strictly later than the one before, including after the clock change.

OMX-C75 — Offers a cadence, preselecting the preference (must)

  • Check: add destinations whose deliver is final, progressive, and live, and one whose content includes start_lists.
  • Pass: the timer is offered when handoffs happen, with each destination’s deliver and content preselected and overridable. A final destination receives full documents only. The format sets no cadence beyond that; you do.

OMX-C76 — Destinations are independent (must)

  • Check: make one destination return 401 for a whole meet.
  • Pass: the other keeps receiving documents, and the timer is shown per-destination status naming the failing one.

OMX-C77 — A timer can add a destination (must)

  • Check: with no incoming document at all, add a destination by hand.
  • Pass: it receives handoffs. Without this, Publisher-only products are unreachable.

OMX-C79 — Retries only what can succeed (must)

  • Check: point a destination at a test endpoint that answers 503, then 429 with Retry-After: 30, then 400, 401, 404, 409, 410, 413, and 422 in turn. While a retry is pending, change a result.
  • Pass: the 503 is retried with backoff and the 429 no sooner than 30 seconds. The others are not retried and are shown to the timer against that destination, with any errors the body carried. The retry that finally goes out is built when it is sent, so it carries the changed result (OMX-C116).

OMX-C85 — Accepts a destination card (must)

  • Check: paste a destination card into a meet, then open the same card as a file.
  • Pass: each adds the destination as one the timer added by hand, approved by the act of adding it, with the card’s deliver and content preselected, and the meet’s handoffs reach it. Pasting the same card again changes that destination rather than adding a second. A card whose url is http:// is refused.
  • Fixture: c85-destination-card.omx-destination.json

OMX-C86 — Marks the final (must)

  • Check: hand off a prelims-and-finals event before the final runs, and again after it.
  • Pass: the final round carries final: true both times, upcoming before it runs, and no other round does; a single-round event’s round carries it too. Before the final is run, the prelim entries’ overall_place is their place across the prelims.

OMX-C87 — Every handoff passes the strict schema (must)

  • Check: capture a full and a partial handoff and validate each against results-document-v1, then run node omx-check.mjs on it. Note each request’s Content-Encoding.
  • Pass: zero errors from both. Each body is the document itself, sent uncompressed or with Content-Encoding: gzip, and nothing else.

OMX-C94 — Adaptive events stay separate (must)

  • Check: read a document with a women’s shot put and a women’s shot put carrying adaptive: { "mode": "seated" }.
  • Pass: two events. If your product cannot hold an adaptive event, the operator is told and the seated event is not imported; it is never merged into the standard shot put.

OMX-C96 — Splits say where they were taken (must)

  • Check: hand off a road race with two split mats, and a relay with leg splits.
  • Pass: the race’s event lists its markers, and every split names a marker_id from that list; each relay leg split names its leg. Every split carries one number, time_ms, elapsed from the same start as the entry’s result: a relay leg’s split is the running clock when that leg finished. A runner who missed a mat has no split there, and their other splits still name their markers.

OMX-C97 — Writes gender as stated (must)

  • Check: hand off a road race open to everyone, a mixed relay, an athlete who did not state a gender, a non-binary athlete Registration entered in a boys’ race, and a co-ed school team scored separately for boys and girls.
  • Pass: the race and the relay are both C. The athlete with no gender carries no gender and no gender_place, and nothing guessed one. The non-binary athlete keeps the entry Registration gave them and is placed within their own gender. The boys’ and girls’ scores are separate team_scores blocks, each naming its scope.gender; standings that count every gender together name C, and no block leaves scope.gender out.

OMX-C115 — A partial document carries whole rounds (must)

  • Check: while a meet runs, capture a partial handoff that carries one event’s round, and one that carries a decathlon component’s round.
  • Pass: each round is whole: its status, timing, heats, and every entry in it. Each carries the round’s event with only that round, every athlete, team, division, group, and wave its entries name, the event-scope team scores of its events, and every division- and meet-scope team score whole, each block with the same id it had in the last document. The component’s document also carries the decathlon and the decathlon’s round, because the standing changes with every component. No partial document is used to remove anything.

OMX-C116 — One send at a time, each built when it is sent (must)

  • Check: for one destination, change three rounds in quick succession while the endpoint is slow, make the endpoint fail one send, then change a fourth round. Separately, hand off from a second copy of the meet (a laptop swapped mid-meet), and remove an athlete.
  • Pass: at most one document is in flight to a destination at a time. Every send, a retry included, is built at the moment it goes out and carries every round changed since the Publisher last accepted a document, so the failed send’s rounds arrive in the next. The first document any copy of the meet sends to a destination is full, and so is every document after anything is removed: an event, round, team, athlete, division, group, wave, or team score block.

OMX-C122 — Sends the whole meet when asked (must)

  • Check: have a progressive destination answer the next partial with 412, then answer 200. Separately, have it answer a status the response table does not list, such as 418, and then 599.
  • Pass: after the 412 the next document, sent at once, is full, and later ones are partial again. The 418 is not retried and the timer is told, as for 400; the 599 is retried with backoff, as for 5xx (OMX-R180).

OMX-C117 — Entry statuses fit their round (must)

  • Check: hand off a meet with an upcoming round, a started one, a finished one (unofficial, under_review, or official), and a cancelled one.
  • Pass: an upcoming round holds only e entries, and only for a destination that asked for start lists. A started round holds e, s, placed entries (their places so far), and dnf, dns, dq, fs, nr. A finished round holds placed entries and dnf, dns, dq, fs, nr only. No entry carries both a status and an overall_place, and none carries neither. A cancelled round holds no entries. No results entry is ci, scr, or wait: a scratched or wait-listed athlete has no results entry. omx-check.mjs checks this.

OMX-C118 — Heat sheets only for a destination that asks (must)

  • Check: add two destinations, one whose content includes start_lists and one without, then seed a round that has not run.
  • Pass: the first receives the round with an e entry for each athlete, carrying heat, lane, seed, and wave_id where it has them. The second receives the round, upcoming, with no entries.

OMX-C119 — Writes a cancelled round (must)

  • Check: cancel an event after its heats are drawn, and hand off.
  • Pass: the event’s round is cancelled, with no entries, and the event stays listed: in the next full document and in a partial one carrying that round.

These tests are dedicated to the public domain under CC0 1.0: copy them into your own test suite.