Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.roboticks.io/llms.txt

Use this file to discover all available pages before exploring further.

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.