Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

TRO Declaration Format

TROV 0.1 DRAFT

This document is a draft and subject to revision. Please submit feedback or report issues.

A TRO Declaration is a JSON-LD document that describes a Transparent Research Object. It records which digital artifacts were involved in a computational workflow, how they were arranged at each stage, what performances (supervised computations) were conducted, and what transparency claims are warranted about the whole process.

Document SectionDescription
OverviewWhat a TRO declaration contains at a high level
Document StructureThe @context, @graph, and namespace prefixes
The TRO ObjectTRS, compositions, arrangements, artifacts, locations, performances
The Warrant ChainHow performance (TRP) attributes link back to TRS capabilities
Identifier Conventions@id patterns for TRS, performance, arrangement, and artifact nodes
Signing and TimestampingGPG and X.509/CMS signing, timestamp authorities
VerificationWhat verifying a TRO declaration may involve
Complete ExampleA full annotated TRO declaration
NotesJSON-LD conventions, design rationale, known limitations

For definitions of the vocabulary terms used here, see the TROV Vocabulary Reference. For the conceptual background, see the TRACE Conceptual Model. For the design rationale behind the JSON-LD format, see TRO Declaration Design.


Overview

A TRO declaration is a single JSON-LD file (conventionally *.jsonld) containing:

  1. A @context block that maps short term names to full URIs.

  2. A @graph array containing a single TRO object with all of its nested components. (See Note 2 for why an array.)

The TRO object itself contains:

Optionally, the TRO is accompanied by:


Document Structure

Top-Level Envelope

{
    "@context": [ ... ],
    "@graph": [ { /* single TRO object */ } ]
}

The @context maps short property names (like trov:hash) to full URIs. In 0.1, the @graph array contains a single object — the TRO declaration itself (see Note 2 for why it is an array).

The @context Block

"@context": [
    {
        "rdf":    "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
        "rdfs":   "http://www.w3.org/2000/01/rdf-schema#",
        "trov":   "https://w3id.org/trace/trov/0.1#",
        "schema": "https://schema.org"
    }
]

The context defines four namespace prefixes:

PrefixNamespacePurpose
rdf:http://www.w3.org/1999/02/22-rdf-syntax-ns#RDF type system
rdfs:http://www.w3.org/2000/01/rdf-schema#Labels and comments
trov:https://w3id.org/trace/trov/0.1#All TROV terms
schema:https://schema.orgStandard metadata (name, description, dates)

The mandatory parts of a TRO declaration depend only on TROV and the foundational rdf: and rdfs: namespaces. The schema: prefix is a convenience for common metadata — it may be omitted if no schema: properties are used, and no TRACE-compliant tool may reject a TRO declaration for missing schema: properties. Conversely, consumers of TROs — especially those curating triples extracted from TRO declarations — are free to discard non-TROV triples (e.g. schema: properties) to avoid undesired inferences that would result from importing large external ontologies.

From the JSON perspective, the @context is boilerplate copied into every TRO declaration, and everything after the @context is ordinary JSON. See TRO Declaration Design for background.


The TRO Object

In 0.1, the single object in the @graph array is the TRO itself.

Root Properties

Example root properties:

{
    "@id": "tro",
    "@type": ["trov:TransparentResearchObject", "schema:CreativeWork"],
    "trov:vocabularyVersion": "0.1",
    "schema:creator":     "SIVACOR",
    "schema:name":        "Example TRO",
    "schema:description": "TRO produced by an example workflow",
    "schema:dateCreated": "2024-06-15T14:30:00",

    "trov:wasAssembledBy":  { ... },
    "trov:createdWith":     { ... },
    "trov:hasComposition":  { ... },
    "trov:hasArrangement":  [ ... ],
    "trov:hasPerformance":  [ ... ],
    "trov:hasAttribute":    [ ... ]
}

The schema: properties shown above are optional metadata. See The @context Block for the dependency boundary.

FieldTypeRequiredDescription
@idstringYesLocal identifier for the TRO within this document. Conventionally "tro".
@typestring or arrayYesMust include "trov:TransparentResearchObject". May optionally include "schema:CreativeWork" to make the TRO discoverable by schema.org-aware tools (not required for using schema: properties).
trov:vocabularyVersionstringYesThe TROV vocabulary version this declaration conforms to, e.g. "0.1".
schema:creatorstringnoName of the agent or system that created this TRO declaration.
schema:namestringnoShort title for the TRO.
schema:descriptionstringnoHuman-readable description.
schema:dateCreatedstring (ISO 8601)noWhen the TRO declaration was created.
trov:wasAssembledByobjectYesThe TRS that produced and signed this TRO.
trov:createdWithobjectnoSoftware tool that generated this declaration. Contains schema:name and schema:softwareVersion.
trov:hasCompositionobjectYesThe artifact composition.
trov:hasArrangementarrayYesOne or more artifact arrangements.
trov:hasPerformancearraynoZero or more performance records.
trov:hasAttributearraynoZero or more TRO-level transparency attributes.

TRS Description (trov:wasAssembledBy)

The TRS block identifies the Trusted Research System that supervised the workflow and signed the TRO. It includes the TRS’s declared capabilities and signing identity.

Example TRS description:

"trov:wasAssembledBy": {
    "@id": "trs",
    "@type": ["trov:TrustedResearchSystem", "schema:Organization"],
    "schema:name":        "SIVACOR",
    "schema:description": "Secure Interactive Virtual Appliance ...",
    "trov:publicKey": "...",
    "trov:hasCapability": [
        {
            "@id": "trs/capability/0",
            "@type": "trov:CanProvideInternetIsolation"
        },
        {
            "@id": "trs/capability/1",
            "@type": "trov:CanRecordInternetAccess"
        }
    ],
    "trov:customTerm": [
        {
            "@id": "mytrs:CanProvideAuditLogging",
            "rdfs:subClassOf": { "@id": "trov:TRSCapabilityType" }
        }
    ]
}
FieldTypeRequiredDescription
@idstringYesLocal identifier. Conventionally "trs".
@typestring or arrayYesMust include "trov:TrustedResearchSystem".
trov:publicKeystringGPG: Yes; X.509/CMS: noThe TRS’s public key. Required for GPG signing (binds key to signed content). Not needed for X.509/CMS (certificate is in the .p7s file).
trov:hasCapabilityarraynoZero or more capability declarations.
trov:customTermarraynoDeclarations of custom terms that extend TROV classes. Each entry names the custom term and the TROV class it extends via rdfs:subClassOf. See TROV Extension Guide.
schema:namestringnoShort name for the TRS.
schema:descriptionstringnoHuman-readable description of the TRS.

Each capability object declares that the TRS is able to enforce a specific transparency condition:

FieldTypeDescription
@idstringLocal identifier, e.g. "trs/capability/0". Referenced by performance attributes.
@typestringA capability type from the TROV vocabulary, e.g. "trov:CanProvideInternetIsolation".

See TROV Vocabulary Reference — TRS Capability Types for the full list of defined capability types.


Timestamping Authority (trov:wasTimestampedBy)

If the TRS obtained an RFC 3161 timestamp for the TRO signature from a separate timestamping authority, the TSA is recorded as a property of the TRO root object (a sibling of trov:wasAssembledBy). Not all signing mechanisms require this — X.509/CMS signatures can embed the timestamp directly in the .p7s file, making a separate TSA block unnecessary.

{
    "@id": "tro",
    "@type": "trov:TransparentResearchObject",
    "trov:wasAssembledBy": { ... },
    "trov:wasTimestampedBy": {
        "@id": "tsa",
        "@type": "trov:TimeStampingAuthority",
        "trov:publicKey": "..."
    },
    ...
}
FieldTypeRequiredDescription
@idstringYesLocal identifier. Conventionally "tsa".
@typestringYesMust be "trov:TimeStampingAuthority".
trov:publicKeystringnoPublic key of the timestamping authority. Not needed when the timestamp is embedded in the .p7s file.

Artifact Composition (trov:hasComposition)

The composition is the complete, deduplicated set of all artifacts described by the TRO. Each artifact is identified solely by its content hash, and artifact objects carry no resource path information. Paths are recorded separately in artifact locations. An artifact appearing at different paths — or across different workflow stages — is listed once in the composition and referenced by identity wherever it appears.

"trov:hasComposition": {
    "@id": "composition/1",
    "@type": "trov:ArtifactComposition",
    "trov:hasFingerprint": {
        "@id": "fingerprint",
        "@type": "trov:CompositionFingerprint",
        "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "218d9c33..." }
    },
    "trov:hasArtifact": [
        {
            "@id": "composition/1/artifact/0",
            "@type": "trov:ResearchArtifact",
            "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "b5bb9d80..." },
            "trov:mimeType": "text/plain"
        },
        {
            "@id": "composition/1/artifact/1",
            "@type": "trov:ResearchArtifact",
            "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "7d865e95..." },
            "trov:mimeType": "application/x-python"
        }
    ]
}

Composition fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier. Conventionally "composition/1".
@typestringYesMust be "trov:ArtifactComposition".
trov:hasFingerprintobjectYesThe composition fingerprint (see below).
trov:hasArtifactarrayYesOne or more artifact objects.

Fingerprint fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier. Conventionally "fingerprint".
@typestringYesMust be "trov:CompositionFingerprint".
trov:hashobjectYesHash computed over the sorted hash values of all artifacts (see below). Contains trov:hashAlgorithm and trov:hashValue.

Artifact fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "composition/1/artifact/0". Referenced by artifact locations.
@typestringYesMust be "trov:ResearchArtifact".
trov:hashobject or arrayYesOne or more hashes of the artifact’s content. Each object contains trov:hashAlgorithm (e.g. "sha256") and trov:hashValue.
trov:mimeTypestringnoMIME type of the artifact (e.g. "text/plain", "application/pdf").

Computing the Composition Fingerprint

The composition fingerprint allows two TROs that describe the same set of artifacts to be identified as equivalent, regardless of arrangement or metadata differences.

Algorithm:

  1. Collect all trov:hashValue values from every trov:hash object on every artifact in the composition. If an artifact has multiple hashes, all of them are included.

  2. Sort the hash values lexicographically.

  3. Concatenate the sorted values into a single string (no separator).

  4. Compute the hash of the concatenated string (UTF-8 encoded). The fingerprint’s hash algorithm is independent of the algorithms used to hash individual artifacts.

Example (Python):

import hashlib

def collect_hash_values(artifact):
    h = artifact["trov:hash"]
    if isinstance(h, list):
        return [entry["trov:hashValue"] for entry in h]
    return [h["trov:hashValue"]]

all_hashes = sorted(v for art in composition["trov:hasArtifact"] for v in collect_hash_values(art))
fingerprint = hashlib.sha256("".join(all_hashes).encode("utf-8")).hexdigest()

Artifact Arrangements (trov:hasArrangement)

An arrangement captures where artifacts were located at a specific point in the workflow. A simple TRO might have just two arrangements — the artifacts present before and after a single computation — but multi-step workflows can have many, with intermediate arrangements shared between performances.

Each arrangement contains artifact locations — records that place a specific artifact at a specific resource path.

"trov:hasArrangement": [
    {
        "@id": "arrangement/0",
        "@type": "trov:ArtifactArrangement",
        "rdfs:comment": "Artifacts before execution",
        "trov:hasArtifactLocation": [
            {
                "@id": "arrangement/0/location/0",
                "@type": "trov:ArtifactLocation",
                "trov:artifact": { "@id": "composition/1/artifact/0" },
                "trov:path": "data/input.csv"
            },
            {
                "@id": "arrangement/0/location/1",
                "@type": "trov:ArtifactLocation",
                "trov:artifact": { "@id": "composition/1/artifact/1" },
                "trov:path": "scripts/analyze.py"
            }
        ]
    },
    {
        "@id": "arrangement/1",
        "@type": "trov:ArtifactArrangement",
        "rdfs:comment": "Artifacts after execution",
        "trov:hasArtifactLocation": [
            {
                "@id": "arrangement/1/location/0",
                "@type": "trov:ArtifactLocation",
                "trov:artifact": { "@id": "composition/1/artifact/0" },
                "trov:path": "data/input.csv"
            },
            {
                "@id": "arrangement/1/location/1",
                "@type": "trov:ArtifactLocation",
                "trov:artifact": { "@id": "composition/1/artifact/1" },
                "trov:path": "scripts/analyze.py"
            },
            {
                "@id": "arrangement/1/location/2",
                "@type": "trov:ArtifactLocation",
                "trov:artifact": { "@id": "composition/1/artifact/2" },
                "trov:path": "results/output.csv"
            }
        ]
    }
]

Arrangement fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "arrangement/0". Referenced by performances.
@typestringYesMust be "trov:ArtifactArrangement".
rdfs:commentstringnoHuman-readable description of what this arrangement represents.
trov:hasArtifactLocationarrayYesOne or more artifact location objects.

Artifact location fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "arrangement/0/location/0".
@typestringYesMust be "trov:ArtifactLocation".
trov:artifactobjectYesReference to an artifact in the composition, e.g. { "@id": "composition/1/artifact/0" }.
trov:pathstringYesThe resource path (file path, URI, or other locator) for this artifact within this arrangement.

Key concept: The same artifact (same @id and content hash) can appear in multiple arrangements at different or identical paths. Conversely, different content at the same path across arrangements represents the file changing between workflow stages.


Performances (trov:hasPerformance)

A performance records a supervised unit of work — typically a computation executed inside the TRS. Each performance links input arrangements to output arrangements and carries transparency attributes warranted by TRS capabilities.

"trov:hasPerformance": [
    {
        "@id": "trp/0",
        "@type": "trov:TrustedResearchPerformance",
        "rdfs:comment": "Execution of analysis workflow",
        "trov:wasConductedBy": { "@id": "trs" },
        "trov:startedAtTime": "2024-06-15T14:00:00",
        "trov:endedAtTime":   "2024-06-15T14:25:00",
        "trov:accessedArrangement":       { "@id": "arrangement/0" },
        "trov:contributedToArrangement":  { "@id": "arrangement/1" },
        "trov:hasPerformanceAttribute": [
            {
                "@id": "trp/0/attribute/0",
                "@type": "trov:InternetIsolation",
                "trov:warrantedBy": { "@id": "trs/capability/0" }
            },
            {
                "@id": "trp/0/attribute/1",
                "@type": "trov:InternetAccessRecording",
                "trov:warrantedBy": { "@id": "trs/capability/1" }
            }
        ]
    }
]

Performance fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "trp/0".
@typestringYesMust be "trov:TrustedResearchPerformance".
rdfs:commentstringnoHuman-readable description.
trov:wasConductedByobjectYesReference to the TRS, e.g. { "@id": "trs" }.
trov:startedAtTimestring (ISO 8601)noWhen the performance began.
trov:endedAtTimestring (ISO 8601)noWhen the performance ended.
trov:accessedArrangementobject or arraynoReference(s) to input arrangement(s). A single arrangement may be a plain object reference; multiple arrangements use an array.
trov:contributedToArrangementobject or arraynoReference(s) to output arrangement(s). A single arrangement may be a plain object reference; multiple arrangements use an array.
trov:hasPerformanceAttributearraynoZero or more performance attribute objects.

Performance attribute fields:

FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "trp/0/attribute/0".
@typestringYesA performance attribute type, e.g. "trov:InternetIsolation".
trov:warrantedByobjectYesReference to the TRS capability that justifies this claim, e.g. { "@id": "trs/capability/0" }.

See TROV Vocabulary Reference — TRP Attribute Types for the defined attribute types and their required warranting capabilities.

Extension points: TRS implementations can extend performances in two ways. Custom properties (operational metadata like architecture, resource usage, container image digests) use a TRS-specific namespace alongside the standard trov: properties. Custom attribute and capability types (new transparency claims specific to a TRS) also use the adopter’s namespace and participate in the warrant chain through the standard trov:warrantedBy mechanism. Adopters who publish an RDF vocabulary for their custom types can declare subclass relationships to the core TROV types, enabling RDF consumers to discover them through inference. See TROV Extension Guide for details.


TRO Attributes (trov:hasAttribute)

TRO attributes are transparency claims about the TRO as a whole, warranted by attributes of its constituent performances.

"trov:hasAttribute": [
    {
        "@id": "tro/attribute/0",
        "@type": "trov:IncludesAllInputData",
        "trov:warrantedBy": { "@id": "trp/0/attribute/0" }
    }
]
FieldTypeRequiredDescription
@idstringYesLocal identifier, e.g. "tro/attribute/0".
@typestringYesA TRO attribute type, e.g. "trov:IncludesAllInputData".
trov:warrantedByobject or arrayYesReference(s) to the performance attribute(s) that justify this claim.

See TROV Vocabulary Reference — TRO Attribute Types for the defined attribute types.


The Warrant Chain

The warrant chain is TROV’s mechanism for accountability. TROV supports declaring part or all of the chain of warranting attributes and capabilities justifying a particular transparency claim. Downstream consumers — publishers, repositories, funding agencies — may impose their own requirements on the completeness of these chains.

TRO attribute    →  trov:warrantedBy  →  Performance attribute
                                           →  trov:warrantedBy  →  TRS capability

When the full chain is present, it works as follows in JSON terms:

  1. A TRO attribute (in trov:hasAttribute) references a performance attribute by @id.

  2. That performance attribute (in trov:hasPerformanceAttribute) references a TRS capability by @id.

  3. That TRS capability (in trov:hasCapability) declares the TRS’s ability to enforce the condition.

In this example, the TRO-level claim that all input data is included is warranted by a performance-level attribute asserting Internet isolation, which is in turn warranted by the TRS’s declared capability to enforce it:

"trov:hasCapability": [
    { "@id": "trs/capability/0", "@type": "trov:CanProvideInternetIsolation" }
],
...
"trov:hasPerformanceAttribute": [
    {
        "@id": "trp/0/attribute/0",
        "@type": "trov:InternetIsolation",
        "trov:warrantedBy": { "@id": "trs/capability/0" }
    }
],
...
"trov:hasAttribute": [
    {
        "@id": "tro/attribute/0",
        "@type": "trov:IncludesAllInputData",
        "trov:warrantedBy": { "@id": "trp/0/attribute/0" }
    }
]

Identifier Conventions

All @id values are local to the document. They are used for cross-referencing between objects within the same TRO declaration. The conventions used by tro-utils are:

ObjectPatternExamples
TROtro"tro"
TRStrs"trs"
TSAtsa"tsa"
Compositioncomposition/{n}"composition/1"
Fingerprintfingerprint"fingerprint"
Artifactcomposition/{n}/artifact/{i}"composition/1/artifact/0"
Arrangementarrangement/{i}"arrangement/0"
Artifact locationarrangement/{i}/location/{j}"arrangement/0/location/0"
TRS capabilitytrs/capability/{i}"trs/capability/0"
Performancetrp/{i}"trp/0"
Performance attributetrp/{i}/attribute/{j}"trp/0/attribute/0"
TRO attributetro/attribute/{i}"tro/attribute/0"

Note: These identifier patterns are conventions, not requirements. The important thing is internal consistency: every @id referenced by another object must match a defined @id within the document. Tools should treat identifiers as opaque strings and rely on cross-references, not string parsing.


Signing and Timestamping

A TRO package includes the declaration and one or more signing artifacts. The signing mechanism is not fixed — different TRS implementations use different approaches. What matters is that the signature can be verified against the public key or certificate recorded in the TRO declaration.

Current implementations:

Signing mechanismPackage contents
GPG detached signature.jsonld declaration, .sig signature, .tsr RFC 3161 timestamp
X.509/CMS (PKCS #7).jsonld declaration, .p7s signature (embeds certificate and timestamp)

The signature covers the declaration file byte-for-byte. In the reference implementation (tro-utils), the declaration is serialized with json.dumps(data, indent=2, sort_keys=True) to ensure a deterministic byte sequence. The signature is a separate file, not embedded in the JSON.


Verification

Verification of a TRO declaration may include:

  1. Verifying that the signature on the declaration file matches the public key or certificate in the TRO declaration.

  2. Optionally verifying that the timestamp proves the signature existed at the claimed time.

  3. Recomputing the composition fingerprint from the listed artifact hashes and confirming it matches the declared fingerprint.

  4. Confirming that every artifact referenced by an arrangement is present in the composition.

  5. Tracing the warrant chain: confirming that each trov:warrantedBy reference resolves to an object of the expected type.


Complete Example

The following is a minimal but complete TRO declaration describing a data file and a script, a computation that reads them and produces an output, and a claim of Internet isolation.

{
    "@context": [
        {
            "rdf":    "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
            "rdfs":   "http://www.w3.org/2000/01/rdf-schema#",
            "trov":   "https://w3id.org/trace/trov/0.1#",
            "schema": "https://schema.org"
        }
    ],
    "@graph": [
        {
            "@id": "tro",
            "@type": ["trov:TransparentResearchObject", "schema:CreativeWork"],
            "trov:vocabularyVersion": "0.1",
            "schema:name": "Example analysis TRO",
            "schema:description": "TRO from an analysis reading a data file and a script, producing one output",
            "schema:dateCreated": "2024-06-15T14:30:00",

            "trov:wasAssembledBy": {
                "@id": "trs",
                "@type": ["trov:TrustedResearchSystem", "schema:Organization"],
                "schema:name": "Example TRS",
                "schema:description": "A TRS that enforces Internet isolation",
                "trov:publicKey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----\n",
                "trov:hasCapability": [
                    {
                        "@id": "trs/capability/0",
                        "@type": "trov:CanProvideInternetIsolation"
                    }
                ]
            },

            "trov:createdWith": {
                "@type": "schema:SoftwareApplication",
                "schema:name": "tro-utils",
                "schema:softwareVersion": "0.2.2"
            },

            "trov:wasTimestampedBy": {
                "@id": "tsa",
                "@type": "trov:TimeStampingAuthority",
                "trov:publicKey": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n"
            },

            "trov:hasComposition": {
                "@id": "composition/1",
                "@type": "trov:ArtifactComposition",
                "trov:hasFingerprint": {
                    "@id": "fingerprint",
                    "@type": "trov:CompositionFingerprint",
                    "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "a1b2c3d4..." }
                },
                "trov:hasArtifact": [
                    {
                        "@id": "composition/1/artifact/0",
                        "@type": "trov:ResearchArtifact",
                        "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "aaa1..." },
                        "trov:mimeType": "text/csv"
                    },
                    {
                        "@id": "composition/1/artifact/1",
                        "@type": "trov:ResearchArtifact",
                        "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "bbb2..." },
                        "trov:mimeType": "application/x-python"
                    },
                    {
                        "@id": "composition/1/artifact/2",
                        "@type": "trov:ResearchArtifact",
                        "trov:hash": { "trov:hashAlgorithm": "sha256", "trov:hashValue": "ccc3..." },
                        "trov:mimeType": "text/csv"
                    }
                ]
            },

            "trov:hasArrangement": [
                {
                    "@id": "arrangement/0",
                    "@type": "trov:ArtifactArrangement",
                    "rdfs:comment": "Artifacts before execution",
                    "trov:hasArtifactLocation": [
                        {
                            "@id": "arrangement/0/location/0",
                            "@type": "trov:ArtifactLocation",
                            "trov:artifact": { "@id": "composition/1/artifact/0" },
                            "trov:path": "data/input.csv"
                        },
                        {
                            "@id": "arrangement/0/location/1",
                            "@type": "trov:ArtifactLocation",
                            "trov:artifact": { "@id": "composition/1/artifact/1" },
                            "trov:path": "scripts/analyze.py"
                        }
                    ]
                },
                {
                    "@id": "arrangement/1",
                    "@type": "trov:ArtifactArrangement",
                    "rdfs:comment": "Artifacts after execution",
                    "trov:hasArtifactLocation": [
                        {
                            "@id": "arrangement/1/location/0",
                            "@type": "trov:ArtifactLocation",
                            "trov:artifact": { "@id": "composition/1/artifact/0" },
                            "trov:path": "data/input.csv"
                        },
                        {
                            "@id": "arrangement/1/location/1",
                            "@type": "trov:ArtifactLocation",
                            "trov:artifact": { "@id": "composition/1/artifact/1" },
                            "trov:path": "scripts/analyze.py"
                        },
                        {
                            "@id": "arrangement/1/location/2",
                            "@type": "trov:ArtifactLocation",
                            "trov:artifact": { "@id": "composition/1/artifact/2" },
                            "trov:path": "results/output.csv"
                        }
                    ]
                }
            ],

            "trov:hasPerformance": [
                {
                    "@id": "trp/0",
                    "@type": "trov:TrustedResearchPerformance",
                    "rdfs:comment": "Execution of analysis script",
                    "trov:wasConductedBy": { "@id": "trs" },
                    "trov:startedAtTime": "2024-06-15T14:00:00",
                    "trov:endedAtTime":   "2024-06-15T14:25:00",
                    "trov:accessedArrangement":      { "@id": "arrangement/0" },
                    "trov:contributedToArrangement": { "@id": "arrangement/1" },
                    "trov:hasPerformanceAttribute": [
                        {
                            "@id": "trp/0/attribute/0",
                            "@type": "trov:InternetIsolation",
                            "trov:warrantedBy": { "@id": "trs/capability/0" }
                        }
                    ]
                }
            ],

            "trov:hasAttribute": [
                {
                    "@id": "tro/attribute/0",
                    "@type": "trov:IncludesAllInputData",
                    "trov:warrantedBy": { "@id": "trp/0/attribute/0" }
                }
            ]
        }
    ]
}

Notes

Note 1: JSON-LD as JSON. A TRO declaration is valid JSON. Producers can build it with any JSON library, no RDF tooling required. The @context, @id, @type, and @graph keys are the only JSON-LD-specific syntax. Everything else is standard JSON objects, arrays, and strings.

Note 2: Why @graph is an array. In 0.1, the @graph array contains a single object, so the array wrapper may look redundant. It is kept for two reasons: it provides future flexibility for bundling additional top-level objects (e.g. supplementary hash/algorithm pairs for artifacts, or a standalone TRS profile) without changing the core JSON Schema for the TRO itself, and it matches the default output of common JSON-LD writer libraries.

Note 3: Multiple performances. A TRO may describe a multi-step workflow with multiple performances. Each performance links its own input and output arrangements. Arrangements may be shared: one performance’s output arrangement can be another performance’s input arrangement.

Note 4: Arrangement ordering. Arrangements are not explicitly ordered in the JSON array. The relationships between arrangements are expressed through the performances: trov:accessedArrangement identifies arrangements a performance read from, and trov:contributedToArrangement identifies arrangements it wrote to.