Skip to main content

Robotics Runtime Contracts

CI License: MIT

Canonical, machine-verifiable contracts for portable robotics executions.

This package defines the boundary shared by product repositories, runtime infrastructure, and acceptance tooling. It validates requested scenarios, observed runtimes, evidence, qualification inputs, and verdicts. It does not launch ROS 2, choose a simulator, collect telemetry, or contain product logic.

Architecture

flowchart LR
    product["Product repository"] --> infra["Runtime infrastructure"]
    infra --> execution["ROS 2 execution"]
    execution --> harness["Acceptance harness"]
    harness --> evidence["Evidence and verdict"]
    contracts["Runtime contracts"] -. validates .-> product
    contracts -. validates .-> infra
    contracts -. validates .-> harness

The contracts are independent of a particular robot or product. They describe ROS 2 execution, security and timing concepts, including implementation-specific constraints. Provider identities are recorded as data or namespaced extensions; they do not select a schema.

Install

Python 3.12 through 3.14 is supported. Install a wheel from a tagged GitHub Release, or create a development environment:

uv sync --locked --all-groups

Release assets include build-provenance attestations. See SUPPLY_CHAIN.md.

CLI

Validate JSON or YAML using its declared schema_version:

robotics-contracts validate scenario.yaml

Resolve reviewed RFC 7396 overlays and retain their origin trace:

robotics-contracts scenario resolve base.yaml \
  --overlay camera.yaml \
  --overlay limits.yaml \
  --output resolved.yaml \
  --trace-output resolution-trace.json

Qualification subjects use the following naming pattern. Add the evidence index, retained raw artifacts, and other bindings required by the scenario to make the set complete; see qualification validation.

robotics-contracts validate-qualification \
  --artifact scenario:scenario.json=scenario.json \
  --artifact acceptance_run:acceptance-run.json=run.json \
  --artifact runtime_manifest:runtime-manifests/main.json=runtime.json \
  --artifact domain_result:results/main.json=result.json \
  --artifact acceptance_aggregate:acceptance-aggregate.json=aggregate.json

Use --quiet for gates and --format json for stable machine-readable diagnostics. describe reports a schema identifier and digest, diff emits an RFC 7396 merge patch, and permit init --subject-digest ... creates an unsigned physical-execution permit for an external signing workflow.

Python API

from robotics_runtime_contracts import (
    schema_for_role,
    schema_registry,
    validate_document,
    validate_role,
)

validate_document(document)
validate_role(document, "acceptance_scenario")
print(schema_for_role("runtime_manifest"))
registry = schema_registry()

Validation is offline, does not mutate inputs, and rejects non-finite numbers. Individual document validation reports its first failure; structural and semantic errors normally include a JSON path. The public qualification module accumulates independent document and link failures in QualificationReport, with explicit blocked checks when prerequisites are invalid. Link errors can have no JSON path. The package exports worst_status() for consumers to share status-folding rules; this does not mean every consumer already uses it.

Contract Set

Release 0.16 publishes one catalogued v1 contract set. These identifiers are not compatible with every historical v1 document. The machine-readable source of truth is catalog.v1.json.

Area Public roles
Execution scenario, run, observation, result, aggregate, campaign
Runtime runtime manifest, model manifest, dataset manifest, robot description
Evidence evidence index, recording summary, artifact receipt
Qualification profile, conformance result, bundle, policy
Physical safety execution permit, verification, trust policy
Cross-domain transport channel, observation, clock relation, causal chain, qualification result

Every public document uses JSON Schema Draft 2020-12, declares a schema_version ending in .v1, rejects unknown root fields, and has an ID in the urn:robotics-runtime-contracts:v1:* namespace. Internal schema resources exist only to remove duplication and are not document roles.

The working catalog adds execution_trust_policy and robot_description for the next release; these roles were not published in 0.16. See product artifact roles for their field and path conventions and consumer examples for complete documents with real artifact byte digests.

Extensions

Every public role supports extension_schemas declarations and namespaced extensions without changing the common contract. The qualification bundle places both fields inside its in-toto predicate; other roles use the root. Extension schemas are interpreted as Draft 2020-12. Extension keys use reverse-domain namespaces such as org.example.sorting; schema bytes are supplied by the caller and are never fetched from the network.

For compatibility, seven existing v1 roles retain unpinned extensions when extension_schemas is absent: acceptance result, dataset manifest, evidence index, execution permit, execution verification, model artifact manifest, and runtime manifest. Adding the field opts into strict validation, including when the declaration list is empty. An unpinned legacy payload has no schema-integrity guarantee. All other roles require a declaration for each payload namespace.

The declaration digest identifies the exact supplied schema bytes. The URI must match the schema's $id, and registry keys must be those URIs. Duplicate or unmatched namespaces fail validation. A qualification statement's validated extensions are domain claims; its core subjects, digests and classifications still must exactly match the locally validated artifacts.

validate_document(
    scenario,
    extension_schemas={
        "https://schemas.example.org/sorting.v1.schema.json": schema_bytes,
    },
)

Promote an extension into the common catalog only after it has reusable semantics and evidence from more than one domain.

Merge patches and semantic diff

Scenario overlays use the native, typed implementation of RFC 7396 section 2. Object members merge recursively, null removes a member, and arrays replace as a whole. Inputs and outputs do not share mutable containers. Overlays apply in the given order, followed by contract validation.

semantic_diff compares parsed values recursively without Python's scalar coercions: booleans differ from numbers, and integer/float representations such as 1 and 1.0 also produce a patch. Object key order and source formatting are ignored. A round-trip check rejects targets that require introducing an object member with value null, using diff.unrepresentable; an unchanged existing null and nulls inside replaced arrays are representable. This distinction is part of the diff API; RFC 7396 specifies patch application, not diff generation. The implementation is checked against the RFC's 15 Appendix A vectors and generated round-trip cases. It has no third-party merge-patch dependency.

JSON and YAML input

Documents are UTF-8 mappings with string keys and finite JSON values. Files ending in .json are parsed strictly as JSON, without a YAML fallback; .yaml/.yml select YAML. For unnamed input, standard input, and other suffixes, a leading { or [ selects strict JSON and other input selects YAML. Pass a YAML source_name to loads_mapping when using YAML flow syntax such as {key: value}. Duplicate keys are rejected in both formats, including nested objects and escaped spellings of the same JSON key. Digest-pinned extension schemas use the same strict JSON parser.

The YAML loader uses the YAML 1.2 core scalar rules: dates and timestamps stay strings; yes, no, on, off, 1:30, 0b10 and 1_000 stay strings; only the core true/false spellings become booleans. 010 is decimal 10; 0o10 is octal 8 and 0x10 is hexadecimal 16. 1.10 and 1e2 are numbers; quote them when they represent textual versions or IDs. Non-finite numbers, non-string keys, non-core tags, aliases (including recursive and merge aliases), and multiple YAML documents are rejected. << has no merge semantics. YAML output quotes strings using the same scalar rules and emits no aliases, so reading a written document preserves its JSON values.

Each contract document or extension schema is limited to 8 MiB of UTF-8 input, 64 node levels (root at level 1), and 100,000 nodes, counting mapping keys and values. File and stdin reads stop at the byte limit plus one sentinel byte. These bounds apply to document loaders, not retained raw evidence files. Limit failures use input.limit_exceeded, duplicates use input.duplicate_key with the member path, and aliases use input.yaml_alias. Other malformed input uses input.parse_failed; non-finite values use input.non_finite_number.

Deterministic JSON and artifact hashes

dumps_canonical(document) -> bytes implements this project's deterministic JSON profile. It is not RFC 8785/JCS. Existing contract integers, including nanoseconds beyond 2**53, remain exact JSON number tokens. No schemas or wire types change to satisfy JCS's binary64 domain. This is an explicit compatibility choice, not a fallback from a strict JCS implementation.

The profile accepts built-in dict, list, str, int, float, bool and None values. Object keys must be strings. It emits UTF-8 without a BOM or trailing newline, with compact ,/: separators. Object keys sort recursively by Unicode code point, which differs from JCS's UTF-16 ordering; array order is preserved. Unicode is not normalized, non-ASCII characters remain UTF-8, and JSON control/quote/backslash escaping uses the native Python JSON encoder. Surrogate code points in Python strings are rejected. A valid JSON surrogate escape pair parsed into a Unicode scalar is supported.

Integers have no 53-bit or 64-bit cap and are never converted to float or string values. Booleans stay distinct from integers. Finite floats use native Python JSON spelling: 1.0, 1e-06, and -0.0 remain those spellings, including the negative-zero sign. This preserves the supplied float; it cannot recover decimal precision already lost before the call. Tuples, sets, bytes, Decimal, custom objects and subclasses of the accepted built-ins are not coerced.

The input tree uses the same 64-level and 100,000-node limits as the loaders, counting keys and values with the root at level 1. Encoded output is limited to 8 MiB, including escaping and UTF-8 expansion. Individual oversized strings and integers are rejected before assembling output. The interpreter's integer decimal-conversion limit also applies and is never changed process-wide. Shared containers are expanded within the node budget; cycles fail the depth limit. Errors inherit ContractError: input.invalid_type, input.invalid_unicode, input.non_finite_number, or input.limit_exceeded. Paths identify the invalid value, the object containing a non-string or oversized key, or $ for the total output-byte limit. Diagnostic paths also have an 8 MiB UTF-8 budget: when a member or index would exceed it, errors in that subtree use the nearest enclosing path that fits. Key sizes are checked before constructing their paths, and diagnostic escaping uses bounded chunks. This does not narrow the accepted keys or change serialized bytes. Serialization does not mutate the input.

from robotics_runtime_contracts import dumps_canonical, file_sha256

encoded = dumps_canonical({"ns": 1785067200123456789, "ready": True})
assert encoded == b'{"ns":1785067200123456789,"ready":true}'
digest = file_sha256("existing-evidence.bin")

file_sha256(path) -> str returns the lowercase SHA-256 hex digest of the original file bytes, read in 1 MiB chunks. It expands home paths, propagates normal file I/O errors and does not apply the document-size limit to artifacts. It never parses or serializes the file, even for JSON, and works on binary data. There is no document_digest API. Existing receipts and signatures continue to refer to original bytes. Re-serializing a separate copy can change its hash; replacing an existing artifact requires rebuilding its dependent hash/signature chain. Hash new files only after their exact bytes have been written.

Errors and resource paths

Expected validation and document-operation failures inherit from the public ContractError, which remains a ValueError. Existing specialized exception classes and their constructor signatures remain available. Every contract error has an error_id and a json_path (None when no document location applies). The CLI keeps its JSON diagnostic envelope {"error": {"error_id": "...", "message": "...", "path": "..."}}; path is omitted when unavailable. Schema diagnostics use jsonschema's best_match, including nested anyOf/oneOf errors, so the chosen message can change. See jsonschema's selection rules.

CLI exit codes are 0 for success, 1 for invalid input, I/O failures or internal errors, and 2 for invalid arguments (including conflicting paths and malformed artifact/extension options). I/O errors use input.io_error; unexpected exceptions use internal.error without a traceback. Python I/O APIs retain their normal OSError behavior. Library programming errors are not converted to validation failures. --help and process interrupts retain normal behavior. All CLI file inputs and outputs expand ~ before accessing the filesystem.

Error families include schema.validation_failed, schema.unknown, schema.role_unknown, schema.role_mismatch, schema.reference_invalid, semantic.validation_failed, extension.validation_failed, merge.schema_version_changed, qualification.invalid, qualification.unknown_domain, qualification.unknown_channel, provider.requirements_unsatisfied, receipt.validation_failed, clock.evidence_invalid, status.invalid, input.parse_failed, input.non_finite_number, input.invalid_timestamp, input.invalid, and cli.arguments_invalid. Extension references resolve offline; dangling or non-terminating references encountered during validation are extension errors. Timestamp comparisons share one offset-aware parser; timestamp format validation remains the responsibility of the contract schema.

schema_dir() and schema_path() still return pathlib.Path objects. For zip imports, importlib.resources.as_file extracts the schema directory once and its context stays open until process exit; an atexit handler removes the temporary directory, following the resource context lifetime. Callers can retain these paths within the process without adopting a context-manager API. Paths must not be persisted for another process.

Version Policy

Known consumers include the acceptance harness and runtime infra. They use different contract generations; see the dated consumer table in COMPATIBILITY.md and the 0.15 to 0.16 migration guide. Published schema names now permit only additive changes; breaking changes require a new schema major and migration notes. The structural compatibility gate is planned, so published schema changes remain deferred. Released tags and artifacts remain immutable. See CHANGELOG.md.

HIL and real-target contracts are observation-only. A valid document is not an authorization to actuate hardware and is not proof that a device or accelerator has been qualified.

Development

uv sync --locked --all-groups
uv run pre-commit run --all-files --show-diff-on-failure
uv run pytest
uv build --no-sources

Consumer examples live in consumer-examples/. Contributions must follow CONTRIBUTING.md, and security reports must follow SECURITY.md.

License

MIT

Metadata

Release files for robotics-runtime-contracts 0.18.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for robotics-runtime-contracts 0.18.1
File Size Uploaded
robotics_runtime_contracts-0.18.1.tar.gz 167.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for robotics-runtime-contracts 0.18.1
File Interpreter ABI Platform
robotics_runtime_contracts-0.18.1-py3-none-any.whl Python 3 none any Details

Total release size: 291.7 kB

Release files / robotics_runtime_contracts-0.18.1.tar.gz

Download URL robotics_runtime_contracts-0.18.1.tar.gz
Size 167.8 kB
Tags Source
SHA-256 checksum
How to use checksums
6f54db6a588869c3835e7d9883c81aa693cd380c9d0990a468be9645254dcdd2
BLAKE2b-256 checksum
How to use checksums
da33c0df9704f192d9bdb40b36b9ac284fd11a6db43ef9b000a9eb4019ebab49
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release files / robotics_runtime_contracts-0.18.1-py3-none-any.whl

Download URL robotics_runtime_contracts-0.18.1-py3-none-any.whl
Size 123.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1bd7ef9c88668382d121533cef5bedd733a799385dec03ef016c24866608bc62
BLAKE2b-256 checksum
How to use checksums
3a5e7a1c2c8680dc27aab0fbf9725431ee1fb8a95188c8285ea465bfdc5902e8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 2, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.18.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page