Skip to main content

Wire contract reference

The wire between the Roboticks SDK and the Roboticks platform is JUnit XML extended with roboticks.* properties, converted server-side to a canonical JSON document. This page is the authoritative reference. For the tutorial introduction, see Testing → Wire contract.
github.com/roboticks-io/roboticks-sdk
└── schemas/
    ├── junit_with_confirms.xsd       # XML schema for the upload format
    ├── test_result.schema.json       # JSON schema for the parsed shape
    └── requirement.schema.json       # JSON schema for requirement uploads

Schema version

The wire contract is versioned as a single integer. Currently 2.
from roboticks._version import SCHEMA_VERSION
print(SCHEMA_VERSION)  # 2

Version policy

IncrementCause
Major (1 → 2)Any change that a schema-1 reader cannot parse. New required properties; renamed properties; changed value types.
Add property (no bump)New optional roboticks.* property. Older readers ignore unknown keys.
Remove property (no bump for one release)Property is deprecated but still emitted; readers tolerant. Next major removes the emission.

v2 changes vs v1

  • Added (required when emitted): roboticks.nodeid on every <testcase> — the pytest nodeid, used by the platform to deterministically route per-test-case artifacts into the S3 prefix test-runs/{run_id}/test-cases/{sha256(nodeid)[:16]}/.
  • Added (optional, repeatable): roboticks.attach.{kind} — one property per file registered via roboticks.attach_artifact(). The property name carries the kind (mcap, logs, attachments, or any custom single-segment label) and the value is the local file path; the runner uploader walks these and posts each file to the per-test-case prefix.
  • Behavior: schema-1 uploads still parse — the platform falls back to a run-level prefix when roboticks.nodeid is absent. Schema-2 readers tolerate the absence of roboticks.attach.* (it just means the test did not call attach_artifact).

Suite-level properties

PropertyValueRequiredSince
roboticks_schema_versionintegeryes1
roboticks.sdk.versionsemver stringyes1
roboticks.sdk.language"python" or "cpp"yes1
roboticks.python.versionsemver stringconditional (Python only)1
roboticks.cpp.versionsemver stringconditional (C++ only)1

Test-case properties

PropertyValue formatRequiredSinceDecorator / source
roboticks.nodeidpytest nodeid string (pkg/test_x.py::TestCls::test_y[param])yes (schema ≥ 2)2plugin (stamped on every <testcase>)
roboticks.confirmscomma list of requirement IDsconditional1@confirms
roboticks.tagscomma list of stringsoptional1@tags
roboticks.deadline_msintegeroptional1@deadline
roboticks.requires_simengine or engine:gpuoptional1@requires_sim
roboticks.fault_injectionJSON arrayoptional1(inferred from fault_injection usage)
roboticks.mcap.pathstring (relative path)optional1(set by mcap_capture)
roboticks.mcap.uploaded"true" or "false"optional1(set by runner after upload)
roboticks.coverage.lines.coveredintegeroptional1(set by coverage post-processor)
roboticks.coverage.lines.totalintegeroptional1(set by coverage post-processor)
roboticks.attach.{kind}string (local file path)optional, repeatable2attach_artifact(path, kind=...)

roboticks.attach.{kind} — per-test artifact attachments

Schema 2 adds a property family for files a test registers via attach_artifact(). The property name carries the kind, the value carries the local path:
<property name="roboticks.attach.mcap" value="/tmp/run_42/test_estop.mcap"/>
<property name="roboticks.attach.attachments" value="/tmp/run_42/before.png"/>
<property name="roboticks.attach.attachments" value="/tmp/run_42/after.png"/>
<property name="roboticks.attach.logs" value="/tmp/run_42/decision_log.jsonl"/>
Reserved kinds and their conventional sub-folders (the runner uploader follows these when it lays out S3):
KindConventional use
mcapMCAP bag files
logsPlain-text or JSONL log dumps
attachmentsScreenshots, generated reports, anything else
(custom)Any single URL-safe path segment, e.g. traces, coredumps
The runner walks <testcase> for every property whose name starts with roboticks.attach. and posts the file to the platform’s /test-runs/{run_id}/mcap-upload-urls endpoint with {nodeid, kind} in the body. The backend lands it at:
test-runs/{run_id}/test-cases/{sha256(nodeid)[:16]}/{kind}/{filename}
so the workspace UI groups artifacts under the test that produced them.

JUnit XSD excerpt

<!-- schemas/junit_with_confirms.xsd -->
<xs:complexType name="testcaseType">
  <xs:sequence>
    <xs:element name="properties" minOccurs="0">
      <xs:complexType>
        <xs:sequence>
          <xs:element name="property" maxOccurs="unbounded">
            <xs:complexType>
              <xs:attribute name="name" type="xs:string" use="required"/>
              <xs:attribute name="value" type="xs:string" use="required"/>
            </xs:complexType>
          </xs:element>
        </xs:sequence>
      </xs:complexType>
    </xs:element>
    <xs:choice minOccurs="0">
      <xs:element name="failure" type="xs:string"/>
      <xs:element name="error" type="xs:string"/>
      <xs:element name="skipped" type="xs:string"/>
    </xs:choice>
  </xs:sequence>
  <xs:attribute name="name" type="xs:string" use="required"/>
  <xs:attribute name="classname" type="xs:string" use="required"/>
  <xs:attribute name="time" type="xs:decimal" use="required"/>
</xs:complexType>
The full XSD is shipped at schemas/junit_with_confirms.xsd in the SDK repo. Validate locally:
xmllint --schema schemas/junit_with_confirms.xsd test_results/junit.xml --noout

Canonical test-result JSON

The platform converts JUnit-with-confirms into this JSON shape:
{
  "schema_version": 2,
  "test_id": "tests.test_estop::test_estop_halts_motion",
  "nodeid": "tests/test_estop.py::test_estop_halts_motion",
  "nodeid_slug": "a4f3b9c218e07d54",
  "suite": "tests.test_estop",
  "name": "test_estop_halts_motion",
  "result": "passed",
  "duration_ms": 82,
  "confirms": ["REQ-001", "REQ-014"],
  "tags": ["safety", "smoke"],
  "deadline_ms": 100,
  "requires_sim": null,
  "fault_injection": [],
  "artifacts": {
    "mcap": null,
    "stdout_url": "s3://...",
    "stderr_url": "s3://...",
    "attachments": [
      {"kind": "mcap",        "key": "test-runs/.../test-cases/a4f3b9c2.../mcap/test_estop.mcap"},
      {"kind": "attachments", "key": "test-runs/.../test-cases/a4f3b9c2.../attachments/before.png"}
    ]
  },
  "coverage": {
    "lines_covered": 17,
    "lines_total": 19
  },
  "sdk": {
    "language": "python",
    "version": "0.2.0a0"
  }
}
nodeid_slug is sha256(nodeid).hexdigest()[:16] — derived deterministically on both the SDK and the platform so the per-test-case S3 prefix is stable across re-runs without round-tripping the slug through the wire. JSON Schema (excerpt):
{
  "$id": "https://schemas.roboticks.io/test_result.schema.json",
  "type": "object",
  "required": ["schema_version", "test_id", "nodeid", "result", "duration_ms", "confirms"],
  "properties": {
    "schema_version": {"type": "integer", "const": 2},
    "test_id": {"type": "string"},
    "nodeid": {"type": "string"},
    "nodeid_slug": {"type": "string", "pattern": "^[0-9a-f]{16}$"},
    "result": {"enum": ["passed", "failed", "error", "skipped"]},
    "duration_ms": {"type": "integer", "minimum": 0},
    "confirms": {"type": "array", "items": {"type": "string"}},
    "deadline_ms": {"type": ["integer", "null"], "minimum": 1},
    "requires_sim": {"type": ["string", "null"]},
    "artifacts": {
      "type": "object",
      "properties": {
        "attachments": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["kind", "key"],
            "properties": {
              "kind": {"type": "string"},
              "key": {"type": "string"}
            }
          }
        }
      }
    }
  }
}
Full schema: schemas/test_result.schema.json.

Forward / backward compatibility

ScenarioPlatform behaviour
Upload has no roboticks_schema_versionTreat as stock JUnit. No requirement linking. Tests still show pass/fail.
Upload has roboticks_schema_version = 1, platform supports 2Parse via the v1 legacy adapter. Per-test-case artifacts land under the run-level test-runs/{run_id}/mcaps/ prefix (no nodeid → no per-case sub-folder).
Upload has roboticks_schema_version = 2, platform supports 2Parse normally. Per-test-case artifacts land under test-runs/{run_id}/test-cases/{slug}/.
Upload has roboticks_schema_version = 3, platform supports 2Warn on the Check Run; drop unknown properties; parse the v2 subset.
Upload has roboticks_schema_version = 0Reject with a clear error pointing at the SDK upgrade path.
The platform supports the current and one-back schema versions (v2 and v1 today).

Disagreement handshake

When the SDK and the platform disagree on schema version, the platform writes a roboticks.platform.warnings element into the run’s metadata:
{
  "platform_schema_version": 2,
  "sdk_schema_version": 3,
  "unknown_properties_dropped": ["roboticks.new_thing"],
  "deprecated_properties_seen": []
}
This element is surfaced in the Check Run as a one-line warning and in the run-detail UI as a banner.

What the SDK guarantees

  • A given SDK release pins to exactly one schema version.
  • Within a major SDK series (e.g. all 0.x.y), the schema version doesn’t decrease.
  • The XSD and JSON Schema files in the SDK repo at a tag are the authoritative artefacts for that release.

What the platform guarantees

  • Backwards-compat for at least the current and one-back schema versions.
  • Forward-tolerance: unknown roboticks.* properties are dropped, not rejected.
  • The conversion from XML to canonical JSON is documented and stable per schema version.

Tools

# Validate a JUnit XML against the XSD
roboticks-validate-junit test_results/junit.xml

# Validate a JSON result against the JSON Schema
roboticks-validate-json result.json

# Inspect schema version of a test result file
roboticks-inspect test_results/junit.xml
# > schema_version: 2
# > sdk: roboticks 0.2.0a0 (python 3.13.1)
# > tests: 12 (12 pass, 0 fail)
# > confirms: 18 requirement IDs across 12 tests
# > attachments: 4 files across 3 testcases (mcap=2, attachments=2)

Next

Wire contract tutorial

The version-handshake story in narrative form.

Pytest plugin

Reference for the writer.

C++ reference

Reference for the C++ writer.

Schemas in the SDK repo

Source of truth.