Submitting to the ClusterMAX challenge

There are two different things you can submit here, and they are evaluated differently. Do not mix them in one submission.

  1. A prospective plan+binding+outcomes packet -- you ran real GPU-cloud jobs as an ordinary customer, froze a design before running them (before a medal tier even existed for these providers, if applicable), separately recorded which medal each provider actually got once the rating published, and now have the terminal outcomes. This is what scripts/challenge.py's evaluate() scores.
  2. A retrospective incidents correction -- you have a more complete or more accurate status-page incident history for a provider than what's in retrospective/incidents/. This feeds scripts/retrospective.py's R1 study, not the prospective challenge.

Minimum you need before you start

Read this before you invest time building a packet -- these are hard floors, not suggestions, and the scorer HOLDs a packet that falls short of any of them.

Templates and a converter. submissions/templates/{plan,binding,outcomes}.template.json are annotated, minimal, one-trial examples of each file (field-by-field explanations in the sibling submissions/templates/FIELDS.md). If you already have job logs as a CSV, scripts/csv_to_packet.py converts a jobs CSV plus a small plan-header JSON straight into a correctly hashed plan.json + outcomes.json pair -- see "Converting a CSV" below.

What workload_sha256 and validator_sha256 mean

Both are required on every trial row (in plan.json, and again -- unchanged -- on the matching row in outcomes.json). They are not free-form labels; they are exact hashes of files, the same way plan_sha256/binding_sha256 are.

Example. If you ran the same inference benchmark config (bench.yaml, sha256 9f86d0...) against every provider, checked each job's outputs with the same validator script (validate.py, sha256 e3b0c4...), every single trial row in both plan.json and outcomes.json carries "workload_sha256": "9f86d0..." and "validator_sha256": "e3b0c4..." -- identical across all providers and all rows, because it was the same workload and the same validator. If you ran two different benchmark configs against different subsets of providers, rows from each subset carry that subset's own workload hash; the scorer does not require every row to share one hash, only that the recorded hash is the real hash of the file that actually governed that row.

Converting a CSV

If your job logs are already in a spreadsheet or a database export, write them out as a CSV with these columns (scripts/csv_to_packet.py --help shows the same list):

trial_id, provider_id, service_id, region, session_id, predicted_at,
p_baseline, started_at, finished_at, status, accepted, attempted, p95_ms,
total_cost_usd, credits_redeemed_usd, receipt_ref, receipt_sha256

Everything that's constant across the whole cohort (or constant per workload) -- study_id, rating_name, disclosure text, the transform, the baseline/with-rating predictor definitions, workload_sha256, validator_sha256, the shared gates, and the binding (medals, sources, bound_at) -- goes in a separate small header JSON, documented with a worked example in submissions/templates/FIELDS.md. Then:

python scripts/csv_to_packet.py --csv jobs.csv --header plan-header.json \
  --plan-out plan.json --binding-out binding.json --outcomes-out outcomes.json

This writes all three files LF-only with correct, freshly computed plan_sha256 / binding_sha256 cross-references -- run python scripts/submit_check.py <output dir> afterward the same as for a hand-authored packet; the converter does no validation of its own beyond basic type coercion.

1. Plan+binding+outcomes packets (the prospective test)

For claims about ClusterMAX 3.0, identify the managed-cluster service, configuration, region, ordinary customer permissions and standard support in SUBMITTER.md. Bare-metal-only or token-endpoint evidence belongs to a different claim. Maintainer review must verify this service match; the generic scorer cannot infer it from a provider name. The original five-tier transform still holds Participation Ribbon inputs.

Requirements

None of this requires our involvement to prepare -- freeze your own plan, run your own jobs, submit your own outcomes.

How to submit

Pick one:

Before either path, you can self-check locally:

python scripts/submit_check.py submissions/<id>

This calls scripts/challenge.py's evaluate() -- the same evaluator used in CI -- and prints its status/reason, plus the SHA-256 of every file you submitted. A HOLD status means something in the packet failed validation; the reason field says what. Fix it and re-run before submitting.

2. Retrospective incident-history corrections

If you maintain, or have deeper access to, a provider's status-page history than what's in retrospective/incidents/<slug>.json -- a longer confirmed history, a corrected severity mapping, incidents the automated collector missed -- you can submit a correction the same way: a pull request touching retrospective/incidents/<slug>.json (and, if you fetched anything new, retrospective/incidents/raw/<slug>/ with the raw evidence and its SHA-256).

Requirements:

retrospective/PLAN.md and retrospective/plan.json are frozen and out of scope for correction requests -- the repository records their commitment before collection (git time is self-reported, not an independent registration timestamp), and scripts/retrospective.py refuses to run if PLAN.md's hash no longer matches plan.json.

What happens to a HOLD

HOLD is not a rejection of your GPU provider, your rating, or you -- it means the submitted packet itself didn't pass structural or provenance checks (a missing field, an untimestamped prediction, a training/test leak, a receipt without a hash, a missing or placeholder disclosure, a binding that doesn't reference the exact plan bytes, a binding bound before the plan froze or after a job already started, a missing or incomplete rubric citation, a source or rubric retrieved_utc after bound_at, a medal tier outside the frozen anchors, a provider missing from or extra in the binding, or credits redeemed on a job exceeding that job's own list charge). Since v1.4 this also includes: a transform that doesn't canonically hash to a registered entry in design/registry.json, transform anchors that aren't strictly decreasing, gates that differ between trials sharing a workload_sha256, an empty training_providers list on either predictor, or -- for a binding against a rating version that is itself registered -- a binding.source.sha256 that isn't one of that rating's registered medal-table digests, a bound provider name that isn't spelled exactly as in the registered transcription, or a bound tier that differs from the registered transcription's tier for that provider. Every HOLD comes with a reason string. Fix the packet and resubmit; there's no penalty for a fixed resubmission.