Skip to content
Open the portal
Developer docs

The record format, walked through

Engineers12 min read

SRF is an open, Apache-2.0 specification for a decision record: one verifiable account of what an AI system saw, did, and who allowed it, keyed by the business subject it affected. The format is not the moat; it exists so any system can emit it and any auditor can verify it without trusting Stratl. This page is the implementer’s walkthrough; the normative text is spec/SPEC.md and the schema spec/srf-v0.1.schema.json in the backend repository.

Decision
├─ srf_version: "0.1"
├─ decision_id: "DEC-" + ULID
├─ subject { type, id, display? }
├─ agent { id, name?, version?, owner? }
├─ actors[] { kind: human|system|agent, id, identity_provider?, role? }
├─ events[] (at least one; see below)
├─ provenance { source, source_type, source_record_ids?, connector_version?, ingested_at? }
├─ assurance: observed | attested | enforced
├─ custody { storage: stratl|customer, key: stratl|customer, region?, key_id_url? }
├─ retention? { policy_id?, hold_until?, legal_hold? }
└─ integrity (added by the custodian; never part of the hash)

subject, agent, each actor, custody and retention reject unknown members. Events accept extra type-specific members, which is how connectors carry facts such as truncated, token counts, or content: not_captured.

Every event has seq (1-based, in order), type and at (RFC 3339 UTC, Z). Then, by type:

type fields
input hash, ref?
retrieval source, doc_refs[]
model_call provider, model, version?, config_hash?, input_hash, output_hash, output_ref?
tool_call name, args_hash, args_ref?, result_hash, result_ref?, status
policy_eval policy_id, version, result, rule?
approval required, requested_at?, approver?, identity_provider?, authority?, decision?, comment?
action name, target, params_hash?, executed_at, result
outcome status, adverse, category?, effect_on_subject?

Hashes are sha256: followed by 64 lowercase hex characters. *_ref fields are opaque references a source may attach (a document id, a storage path); they are not fetched by Stratl.

The wire form is the JSON exactly as transmitted: no null-valued members, no defaults injected by any implementation. Producers omit absent optional fields rather than send null; verifiers strip any null members before hashing. A field that is present is hashed; a field that is absent is not; implementations never add fields during validation.

This rule is what lets Python, TypeScript and Go agree. If one implementation filled in display: null and another omitted it, they would hash different bytes. The conformance fixtures under spec/fixtures/ pin this: each is a record and its expected hash, and make conformance runs all three implementations over all of them on every change.

assurance says how the events were captured and is never upgraded by Stratl: an SDK or a tracer produces observed; Stratl’s own workflow endpoints produce enforced because the action could not have happened without them. custody says who holds storage and key. Both are visible in the portal, in the bundle and in the verifier’s output, and a verifier never collapses “signature valid” with “custody: customer”.

The Python and TypeScript SDKs build conforming records, fingerprint content under shared rules, and submit with originals. Use them unless you have a reason not to. Python, TypeScript.

srf_version is a string. Minor versions add optional fields only, and hash inputs never change within a major version. A verifier written for 0.1 will verify 0.x records.

spec/MAPPING-AAT.md maps records from the IETF draft-sharif-agent-audit-trail format to SRF. The GenAI OpenTelemetry semantic conventions map through the OpenTelemetry connector.

Next: how integrity is computed.