Cross-language parity matrix
The Veritio event protocol is language-neutral, but the three SDKs that implement it are not equally complete. Some capabilities exist in all three and are pinned byte-for-byte by shared fixtures; some exist in one language only; and a few are present everywhere under different names or different call signatures. This page states exactly which is which at the pinned revision, so you can decide whether a workflow you prototyped in TypeScript can be ported without writing protocol code yourself.
How to read the matrix
Section titled “How to read the matrix”Three separate questions get conflated when people ask “is this supported in Go?”. Keep them apart.
1. Does the symbol exist? → the capability matrix below2. Is its output byte-identical? → the conformance-fixture table3. Does it have the same shape? → the API-shape sectionA capability can exist in all three languages and still be unpinned, which means nothing in CI would notice if one implementation drifted. It can also be pinned and still differ in call signature, return convention, or exported name — those differences are visible to your code but invisible to the hash.
Capability matrix
Section titled “Capability matrix”| Capability | TypeScript | Python | Go | Pinned by fixture |
|---|---|---|---|---|
| Event creation | createAuditEvent |
create_audit_event |
CreateAuditEvent |
yes |
| Canonical JSON | canonicalJson |
canonical_json |
CanonicalJSON |
yes |
| Event and record hashing | hashAuditEvent, hashAuditRecord |
hash_audit_event, hash_audit_record |
HashAuditEvent, HashAuditRecord |
yes |
| Idempotency-key hashing | hashIdempotencyKey |
hash_idempotency_key |
HashIdempotencyKey |
yes |
| Metadata redaction | applied inside creation | applied inside creation | applied inside creation | yes |
| Audit templates | templates.ts builders |
templates.py builders |
templates.go builders |
no |
| Governed change and action drafts | createGovernedActionDraft |
create_governed_action_draft |
CreateGovernedActionDraft |
yes |
| Revision-id derivation | governedRevisionId |
governed_revision_id |
GovernedRevisionID |
yes |
| Risk scoring | scoreRiskSignals |
score_risk_signals |
ScoreRiskSignals |
yes |
| Episode rollup | rollupEpisodeRisk |
rollup_episode_risk |
RollupEpisodeRisk |
yes |
| Risk policy derivation | riskPolicy |
risk_policy |
RiskPolicy |
yes |
security.risk assertions |
createSecurityRiskAssertion |
create_security_risk_assertion |
CreateSecurityRiskAssertion |
yes |
| Assertion hashing | hashAssertionRecord |
hash_assertion_record |
HashAssertionRecord |
yes |
| Evidence edges | createEvidenceEdge |
create_evidence_edge |
CreateEvidenceEdge |
yes |
| Evidence commits | createEvidenceCommit |
create_evidence_commit |
CreateEvidenceCommit |
yes |
| Commit verifier | verifyEvidenceCommits |
verify_evidence_commits |
VerifyEvidenceCommits |
yes |
| Audit-record verifier | verifyAuditRecords |
none | none | TypeScript only |
| Edge-record verifier | verifyEvidenceEdgeRecords |
none | none | TypeScript only |
| Provenance recorder | createProvenanceRecorder |
none | none | TypeScript only |
Export bundles (vevb-1) |
buildExportBundle, verifyExportBundle |
none | none | TypeScript only |
| Store implementations | MemoryAuditStore, @veritio/storage |
none | none | TypeScript only |
| AI gateway | @veritio/gateway |
none | none | TypeScript only |
Everything in the last six rows is a TypeScript package boundary, not a protocol gap. A Python or Go service can still participate in a Veritio deployment by producing events and records and shipping them to a store that a TypeScript process owns — what it cannot do today is verify a chain, assemble an export bundle, or own authoritative ordering in-process.
The verifier asymmetry
Section titled “The verifier asymmetry”This is the divergence most likely to bite. Veritio has three independent chains — audit records, evidence-edge records, and evidence commits — and only one of the three verifiers exists in all three languages.
audit record chain → verifyAuditRecords TS onlyedge record chain → verifyEvidenceEdgeRecords TS onlycommit ledger → verifyEvidenceCommits TS + Python + GoverifyEvidenceCommits is deliberately the narrowest of the three. It proves the commit ledger’s internal consistency: per-stream sequence continuity, previous-hash linkage, member manifest normalization, the veritio-merkle-v1 records root, and the commit hash. It does not reconcile member record hashes against independently verified records. A fabricated commit chain built over fabricated record hashes verifies clean in isolation.
The Go doc comment on VerifyEvidenceCommits tells you to compose it with VerifyAuditRecords and VerifyEvidenceEdgeRecords for end-to-end verification. Those functions do not exist in the Go module. The advice describes the correct protocol composition, not an available Go API. Until a Go or Python record verifier ships, end-to-end verification has to run in a TypeScript process, or in your own independent implementation of the record-chain rules described in Hash chain.
Treat a Go-side or Python-side VerifyEvidenceCommits pass as a statement about the ledger only. It is not evidence that the underlying records were unchanged.
API-shape asymmetries
Section titled “API-shape asymmetries”These do not change any hashed byte. They change the code you write.
TypeScript scoreRiskSignals(signals, policy = DEFAULT_RISK_POLICY): RiskAssessmentPython score_risk_signals(signals, policy=DEFAULT_RISK_POLICY) -> dictGo ScoreRiskSignals(signals, policy) (RiskAssessment, error)Go requires the policy argument and returns an error. There are no default parameters in Go, so ScoreRiskSignals, RollupEpisodeRisk, and RiskPolicy all take an explicit policy or options value, and all return (value, error) where TypeScript and Python throw or raise. Fail-closed behavior is identical; the control flow is not. A Go caller that ignores the error and prints the zero-valued assessment will document score=0 for a signal set the SDK actually rejected — the opposite of the contract.
Go exports only BandOf among the scoring primitives. clamp01, round4, and sat are unexported in the Go package. If you need to reproduce a per-component contribution outside ScoreRiskSignals, you have to reimplement those three functions in your own code.
Python’s _sat is private. clamp01 and round4 are public module-level functions in veritio.risk, but _sat is underscore-prefixed and not part of the supported surface.
Python does not re-export clamp01 or round4 from the package root. They are absent from the veritio package’s __all__, so from veritio import round4 fails while from veritio.risk import round4 works. band_of is re-exported from the root.
One template is named differently. TypeScript exports episodeStartedTemplate and Go exports EpisodeStartedTemplate, but Python exports activity_episode_started_template. The emitted event is the same; a mechanical name translation will miss it.
Go supplies a pointer helper for optional overrides. Float64 exists in the Go risk-policy module purely to build the optional-float fields that TypeScript and Python express with an absent key.
What conformance fixtures pin
Section titled “What conformance fixtures pin”spec/conformance holds the shared JSON fixtures each SDK’s test suite loads and byte-compares against. At the pinned revision, eighteen fixtures are exercised by all three SDKs, covering canonical JSON, event and edge creation, redaction, every hashing surface, evidence commits, governed action drafts, revision-id derivation, risk normalization, default-policy scoring, episode rollup, frequency rules, policy temperature, and security.risk assertions.
Two fixture families are loaded by the TypeScript suite alone, because the behavior they pin exists only there: provenance-ids.json and the four export-bundle-*.json bundles.
The gap worth naming explicitly is templates. There is no template conformance fixture in any language. The builders exist in all three SDKs and their tests are written per-language, so nothing byte-compares the action strings, metadata defaults, or classifier stamping across TypeScript, Python, and Go. Templates are the most likely place for silent drift. If your deployment mixes languages and depends on templates producing identical events, pin the emitted events yourself in your own test suite. See Template catalogue for the per-language builder list.
Verified scoring parity
Section titled “Verified scoring parity”The risk parity fixtures score the same three pinned signal sets under veritio.reference.v1 in Python and in Go, rendering every number through each language’s canonical-JSON writer so the printed digits are the digits a hash would see. Both outputs are byte-identical to each other and to the TypeScript twin.
policyVersion=veritio.reference.v1bands={"critical":0.75,"high":0.5,"low":0.05,"medium":0.25}
scenario=read-config-lookup signals={"operationType":"read"} normalized={"dataVolume":0,"envCriticality":"production","fanOut":0,"operationType":"read","referenceCount":0,"reversibility":"recoverable"} score=0.05 level=low policyVersion=veritio.reference.v1 factors=operationType:0.05 dataVolume:0 fanOut:0 referenceCount:0 reversibility:1 envCriticality:1
scenario=bulk-export-staging signals={"dataVolume":5000,"envCriticality":"staging","fanOut":3,"operationType":"bulk","referenceCount":12,"reversibility":"reversible"} normalized={"dataVolume":5000,"envCriticality":"staging","fanOut":3,"operationType":"bulk","referenceCount":12,"reversibility":"reversible"} score=0.3718 level=medium policyVersion=veritio.reference.v1 factors=operationType:0.55 dataVolume:0.1961 fanOut:0.0129 referenceCount:0.0155 reversibility:0.6 envCriticality:0.8
scenario=destructive-drop-production signals={"dataVolume":250000,"envCriticality":"production","fanOut":40,"operationType":"destructive","referenceCount":180,"reversibility":"irreversible"} normalized={"dataVolume":250000,"envCriticality":"production","fanOut":40,"operationType":"destructive","referenceCount":180,"reversibility":"irreversible"} score=1 level=critical policyVersion=veritio.reference.v1 factors=operationType:0.85 dataVolume:0.1999 fanOut:0.0738 referenceCount:0.0626 reversibility:1.3 envCriticality:1policyVersion=veritio.reference.v1bands={"critical":0.75,"high":0.5,"low":0.05,"medium":0.25}
scenario=read-config-lookup signals={"operationType":"read"} normalized={"dataVolume":0,"envCriticality":"production","fanOut":0,"operationType":"read","referenceCount":0,"reversibility":"recoverable"} score=0.05 level=low policyVersion=veritio.reference.v1 factors=operationType:0.05 dataVolume:0 fanOut:0 referenceCount:0 reversibility:1 envCriticality:1
scenario=bulk-export-staging signals={"dataVolume":5000,"envCriticality":"staging","fanOut":3,"operationType":"bulk","referenceCount":12,"reversibility":"reversible"} normalized={"dataVolume":5000,"envCriticality":"staging","fanOut":3,"operationType":"bulk","referenceCount":12,"reversibility":"reversible"} score=0.3718 level=medium policyVersion=veritio.reference.v1 factors=operationType:0.55 dataVolume:0.1961 fanOut:0.0129 referenceCount:0.0155 reversibility:0.6 envCriticality:0.8
scenario=destructive-drop-production signals={"dataVolume":250000,"envCriticality":"production","fanOut":40,"operationType":"destructive","referenceCount":180,"reversibility":"irreversible"} normalized={"dataVolume":250000,"envCriticality":"production","fanOut":40,"operationType":"destructive","referenceCount":180,"reversibility":"irreversible"} score=1 level=critical policyVersion=veritio.reference.v1 factors=operationType:0.85 dataVolume:0.1999 fanOut:0.0738 referenceCount:0.0626 reversibility:1.3 envCriticality:1The sparse first scenario matters: {"operationType":"read"} supplies none of the optional signals, so the printed normalized line shows the fail-closed defaults each language applies — recoverable, production, magnitude 0. If one SDK’s defaults drifted, the score on that line would move and the two files would stop agreeing.
Verified governed-draft parity
Section titled “Verified governed-draft parity”create_governed_action_draft and CreateGovernedActionDraft derive the same change id, activity id, revision id, tenant-scoped idempotency seed, changed paths, state commitment, event actions, and edge relations.
{ "derivedIds": { "changeId": "chg_subscription_sub_9f31_312ca4bde590b545", "activityId": "act_subscription_sub_9f31_312ca4bde590b545", "entityId": "sub_9f31", "revisionId": "rev_subscription_sub_9f31_a71e2768812c_fe34f6d0", "idSeed": "312ca4bde590b545", "changeAndActivityShareOneSeed": true }, "changedPaths": [ "/plan", "/seatCount" ], "stateCommitment": { "algorithm": "sha256", "canonicalization": "veritio-json-v1", "schemaRef": "acme://schemas/subscription@3", "fieldSetRef": "acme://fieldsets/subscription-governed@1", "digest": "sha256:a71e2768812cca4e3609ce7e24460967d253e1ddab13d9a8898f4fd31a3564cb", "committedFields": [ "accountEmail", "id", "plan", "seatCount", "status" ], "omittedFieldPresent": false, "accountEmailCommitment": { "captureMode": "content_digest", "digest": "sha256:aa69b0bc0b21a0ca7b5b5d1beb7556eb7d598f21d638db3eb9770ddd4fb30e45" } }, "eventActions": [ "change.declared", "activity.recorded", "entity.revision.created" ], "edgeRelations": [ "has_activity", "has_output", "performed_by", "generated" ], "draftIsInputOnly": { "note": "create_governed_action_draft returns evidence INPUTS, not persisted records. Append draft['outboxEntry'] through a conforming AuditStore inside the same mutation to make it evidence.", "eventsCarryStoreAssignedSequence": false, "eventsCarryRecordHash": false, "outboxMutationBinding": "same_transaction", "outboxSchemaVersion": "2026-06-23" }}{ "derivedIds": { "changeId": "chg_subscription_sub_9f31_312ca4bde590b545", "activityId": "act_subscription_sub_9f31_312ca4bde590b545", "entityId": "sub_9f31", "revisionId": "rev_subscription_sub_9f31_a71e2768812c_fe34f6d0", "idSeed": "312ca4bde590b545", "changeAndActivityShareOneSeed": true }, "changedPaths": [ "/plan", "/seatCount" ], "stateCommitment": { "algorithm": "sha256", "canonicalization": "veritio-json-v1", "schemaRef": "acme://schemas/subscription@3", "fieldSetRef": "acme://fieldsets/subscription-governed@1", "digest": "sha256:a71e2768812cca4e3609ce7e24460967d253e1ddab13d9a8898f4fd31a3564cb", "committedFields": [ "accountEmail", "id", "plan", "seatCount", "status" ], "omittedFieldPresent": false, "accountEmailCommitment": { "captureMode": "content_digest", "digest": "sha256:aa69b0bc0b21a0ca7b5b5d1beb7556eb7d598f21d638db3eb9770ddd4fb30e45" } }, "eventActions": [ "change.declared", "activity.recorded", "entity.revision.created" ], "edgeRelations": [ "has_activity", "has_output", "performed_by", "generated" ], "draftIsInputOnly": { "note": "CreateGovernedActionDraft returns evidence INPUTS, not persisted records. Append draft.OutboxEntry through a conforming AuditStore inside the same mutation to make it evidence.", "eventsCarryStoreAssignedSequence": false, "eventsCarryRecordHash": false, "outboxMutationBinding": "same_transaction", "outboxSchemaVersion": "2026-06-23" }}Every derived value matches. The only difference between the two files is the prose inside draftIsInputOnly.note, which names each language’s own call and field spelling. That note also carries the invariant both languages share: a draft is evidence input, not persisted evidence. Nothing in it carries a store-assigned sequence or a record hash until you append outboxEntry through a conforming store inside the same transaction, as described in Governed changes.
Documented parity TODOs
Section titled “Documented parity TODOs”Two recorder-level behaviors are normative contract but currently implemented in TypeScript only. They are recorded as parity obligations in the repo’s SDK parity rules, not as accidental gaps.
The capture risk-signal classifier. bashRiskSignals, fileChangeRiskSignals, and envCriticalityOf in the Claude Code adapter stamp metadata.riskSignals before hashing, which makes the command-to-class mapping hash-affecting capture contract rather than an implementation detail. A Python or Go capture adapter must reproduce the patterns, the precedence order, and the unmatched-means-no-signal rule byte-identically. See Risk signals.
The sessionId and activityEpisodeId stamps. Every event a provenance session emits carries metadata.sessionId and metadata.activityEpisodeId, applied after caller-supplied metadata so a caller cannot shadow them. Read models depend on those keys to attribute downstream change, review, CI, deploy, and runtime events back to one session and one episode. Both keys are non-PII and must not match the redaction key pattern. Any future Python or Go provenance recorder has to apply the same post-merge stamping order.
What this matrix does and does not prove
Section titled “What this matrix does and does not prove”A row marked pinned means the three SDKs produced identical bytes for the fixture cases in CI at the pinned revision. It does not mean the implementations agree on inputs the fixtures do not cover, and it does not mean a capability behaves identically under error conditions — Go’s error returns are a separate surface from TypeScript’s thrown exceptions.
A row marked “TypeScript only” is a statement about @veritio/core@0.4.7 and its sibling packages at this revision, not a permanent protocol boundary. Check the SDK reference for your language before assuming a gap still exists.
Continue with the Python SDK reference or the Go SDK reference for the per-language surface, or the Verifier reference to interpret each verification result you can actually obtain in your language.