Skip to content
VeritioDocs

Cross-language parity matrix

Kind
reference
For
developer · governance
Verified against
@veritio/core@0.4.7

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.

Three separate questions get conflated when people ask “is this supported in Go?”. Keep them apart.

1. Does the symbol exist? → the capability matrix below
2. Is its output byte-identical? → the conformance-fixture table
3. Does it have the same shape? → the API-shape section

A 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 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.

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 only
edge record chain → verifyEvidenceEdgeRecords TS only
commit ledger → verifyEvidenceCommits TS + Python + Go

verifyEvidenceCommits 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.

These do not change any hashed byte. They change the code you write.

TypeScript scoreRiskSignals(signals, policy = DEFAULT_RISK_POLICY): RiskAssessment
Python score_risk_signals(signals, policy=DEFAULT_RISK_POLICY) -> dict
Go 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.

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.

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.

Python — verified output
policyVersion=veritio.reference.v1
bands={"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:1
Go — verified output
policyVersion=veritio.reference.v1
bands={"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:1

The 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.

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.

Python — verified output
{
"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"
}
}
Go — verified output
{
"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.

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.

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.