Skip to main content

Wire contract

The wire between the SDK and the Roboticks platform is JUnit XML extended with roboticks.* properties. Nothing else is required — no custom upload protocol, no proprietary metadata. If your test runner emits JUnit, it can speak Roboticks.
This page is a tutorial. For the authoritative schema (XSD + JSON Schema), see the SDK wire contract reference.

What the SDK emits

For a single Python test (schema version 2):
<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="tests" tests="1" failures="0" errors="0" skipped="0" time="0.082">
    <properties>
      <property name="roboticks_schema_version" value="2"/>
      <property name="roboticks.sdk.version" value="0.2.0a0"/>
      <property name="roboticks.python.version" value="3.13.1"/>
    </properties>
    <testcase name="test_estop_halts_motion"
              classname="tests.test_estop"
              time="0.082">
      <properties>
        <property name="roboticks.nodeid" value="tests/test_estop.py::test_estop_halts_motion"/>
        <property name="roboticks.confirms" value="REQ-001,REQ-014"/>
        <property name="roboticks.tags" value="safety,smoke"/>
        <property name="roboticks.deadline_ms" value="100"/>
        <property name="roboticks.attach.mcap" value="/tmp/run/test_estop.mcap"/>
        <property name="roboticks.attach.attachments" value="/tmp/run/before.png"/>
      </properties>
    </testcase>
  </testsuite>
</testsuites>
Four observations:
  1. Schema version sits on the <testsuite>. Per-testcase properties carry the semantic payload; suite-level properties carry the metadata about the emitter.
  2. roboticks.confirms is comma-separated. Standard JUnit properties are stringly-typed; a single comma-list is more portable than nested elements.
  3. roboticks.nodeid (added in v2) is what the platform uses to lay out per-test-case S3 folders. The hash slug it derives — sha256(nodeid)[:16] — becomes the sub-directory under test-runs/{run_id}/test-cases/.
  4. roboticks.attach.{kind} (added in v2) is repeatable — one property per file registered with attach_artifact(). The runner uploader walks every roboticks.attach.* property and posts each file to the per-test-case S3 prefix.

The properties

PropertyScopeFormatMeaning
roboticks_schema_versionsuiteintegerSchema version this XML conforms to. Currently 2.
roboticks.sdk.versionsuitesemverSDK version that produced the XML.
roboticks.python.versionsuitesemverInterpreter version (Python tests only).
roboticks.nodeidtestcasepytest nodeidStable identity for the test. Drives the per-test-case S3 prefix. (v2+)
roboticks.confirmstestcasecomma listRequirement IDs this test confirms.
roboticks.tagstestcasecomma listFree-form tags from @tags(...).
roboticks.deadline_mstestcaseintegerDeadline in ms from @deadline(...).
roboticks.requires_simtestcaseengine or engine:gpuSim requirement from @requires_sim(...).
roboticks.fault_injectiontestcaseJSONFault primitives invoked, with topic + params.
roboticks.mcap.pathtestcasestringRelative path to MCAP file if mcap_capture was used.
roboticks.attach.{kind}testcasestring (local path)Repeatable; one per file registered via attach_artifact(). Kind goes into the S3 sub-folder. (v2+)
The full XSD is shipped at schemas/junit_with_confirms.xsd in the SDK repo.

Per-test-case S3 layout (v2)

When the runner uploads artifacts, schema-2 metadata lets the platform fan files out into per-test-case sub-folders inside the run’s S3 prefix:
test-runs/{run_id}/
├── junit/{junit.xml}                            # one per upload
├── mcaps/...                                    # run-level fallback (v1 + un-keyed)
└── test-cases/{sha256(nodeid)[:16]}/
    ├── mcap/test_estop.mcap
    ├── logs/decision_log.jsonl
    └── attachments/before.png
                       after.png
The 16-hex slug is computed deterministically on both the SDK and the platform from roboticks.nodeid; no round-trip is required. The workspace UI groups files under whichever test produced them, and rbtk test files --nodeid / rbtk test cases let you scope a download to a single test.

The test-result JSON

For systems that prefer JSON to XML (the platform’s internal store, the LLM triage prompt context, the matrix API), the platform converts JUnit-with-confirms into a JSON shape defined at schemas/test_result.schema.json:
{
  "schema_version": 2,
  "test_id": "tests.test_estop::test_estop_halts_motion",
  "nodeid": "tests/test_estop.py::test_estop_halts_motion",
  "nodeid_slug": "a4f3b9c218e07d54",
  "result": "passed",
  "duration_ms": 82,
  "confirms": ["REQ-001", "REQ-014"],
  "tags": ["safety", "smoke"],
  "deadline_ms": 100,
  "requires_sim": null,
  "artifacts": {
    "mcap": null,
    "stdout": "s3://...",
    "stderr": "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"}
    ]
  }
}
The conversion is lossless. JSON is the canonical shape for everything after ingestion.

Schema versioning

Schema versions are integers. Every increment is a breaking change to either the XML or the JSON shape. The platform supports the current version and one back.
VersionStatusNotes
2currentAdds roboticks.nodeid (required) + roboticks.attach.{kind} (optional, repeatable). Enables per-test-case S3 layout.
1supported (legacy)Initial GA. Still accepted; per-test-case artifacts fall back to the run-level prefix.
0rejectedThe pre-GA testing builds; never produced by a released SDK

The handshake

When the platform parses an uploaded JUnit XML, the first thing it does is read roboticks_schema_version from the suite properties: The forward-compat behaviour matters: a newer SDK MAY upload a newer schema; the platform tolerates unknown roboticks.* properties rather than failing. You can upgrade the SDK ahead of a platform release without breaking your pipeline. Conversely, the platform never downgrades. A schema-1 platform will not produce schema-0 JSON.

What if I’m using a non-Roboticks test framework

If you emit stock JUnit (without roboticks_schema_version), the platform still ingests the file. You get:
  • Test pass/fail in the Check Run.
  • No requirement linking (the matrix shows the test under “Unlinked”).
  • No deadline, no tags, no MCAP correlation.
To get the full traceability story, you need some mechanism to attach @confirms to each test. Even a <property name="roboticks.confirms" value="REQ-001"/> written by hand into the JUnit XML works. The SDK is the convenient path, not the only path.

Next

SDK wire contract reference

Authoritative schema with XSD and JSON Schema files.

Pytest plugin internals

How the SDK actually writes those properties.

CI recipes

How to get the XML to the platform.

Matrix UI

Where the parsed confirms show up.