Skip to content
VeritioDocs

Python SDK

Kind
guide
For
developer
Verified against
veritio 0.0.1 (Python) at veritio@c4100ee · @veritio/core@0.4.7

The Python SDK is a producer, not a verifier. It builds portable audit events, evidence edges, evidence commits, governed action drafts, risk assessments, and security.risk assertions, and it hashes all of them with the same canonical bytes TypeScript and Go produce. It does not verify record chains, does not record provenance, does not assemble export bundles, and does not own storage.

That boundary is the most important thing on this page. A Python service can participate fully in a Veritio deployment on the write side. On the read-and-prove side, one of the three chain verifiers exists in Python and two do not.

The distribution is one module tree with five files, and everything public is re-exported from the package root.

veritio/
event.py events, edges, commits, canonical JSON, hashing, redaction
governed_change.py entity definitions, change and action drafts, revision ids
risk.py signal normalization, per-step scoring, episode rollup, assertions
risk_policy.py temperature-derived policies over DEFAULT_RISK_POLICY
templates.py 26 event builders plus the audit-log classification helpers

There is no store.py, no provenance.py, and no export_bundle.py. Their absence is a package boundary, not a protocol gap: the protocol defines those behaviors, and at the verified revision only TypeScript implements them.

The package is veritio version 0.0.1 and is installed from the pinned checkout rather than from an index.

Terminal window
git clone https://github.com/getveritio/veritio.git
git -C veritio checkout c4100ee7b678d0c6b227c67ae6ea1d8a1f373967
python3 -m pip install -e ./veritio/sdks/python

pyproject.toml declares requires-python = ">=3.11". Confirm which checkout is actually imported:

Terminal window
python3 -c "import veritio; print(veritio.__file__)"

The printed path must resolve inside the checked-out veritio/sdks/python/src/veritio directory. If it resolves anywhere else, every hash on this page is being produced by code you have not pinned.

src/examples/quickstart/python.py
from veritio import create_audit_event, hash_audit_event
event = create_audit_event(
{
"id": "evt_member_invited_01",
"occurredAt": "2026-08-09T10:00:00.000Z",
"actor": {"type": "user", "id": "usr_123"},
"action": "organization.member.invited",
"target": {"type": "organization", "id": "org_acme"},
"scope": {"tenantId": "org_acme", "environment": "production"},
"purpose": "access_management",
"lawfulBasis": "contract",
"retention": "security_1y",
"metadata": {"role": "viewer"},
}
)
event_hash = hash_audit_event(event)
print({"eventId": event["id"], "hashPrefix": event_hash[:12]})
Terminal window
python3 src/examples/quickstart/python.py
verified output
{'eventId': 'evt_member_invited_01', 'hashPrefix': '3c1eb99f7a8f'}

create_audit_event fails closed before it produces anything. It raises on a missing actor.id, actor.type, action, target.id, or target.type, and on an action that does not match the dotted lowercase protocol form. It sorts and de-duplicates dataCategories, drops None fields entirely, and passes metadata through deterministic key-pattern redaction before the value is ever hashed. Redaction happens on the way in, so a redacted event and its hash are consistent by construction; there is no separate “redact later” step to forget.

Capability Python surface Status
Events, edges, canonical JSON, hashing create_audit_event, create_evidence_edge, canonical_json, hash_audit_event, hash_audit_record, hash_evidence_edge, hash_evidence_edge_record present
Idempotency digest hash_idempotency_key present
Evidence commits create_evidence_commit, hash_evidence_commit, verify_evidence_commits present
Governed changes define_entity, create_governed_change_draft, create_governed_action_draft, governed_revision_id present
Risk normalize_risk_signals, score_risk_signals, rollup_episode_risk, risk_policy, band_of, with_risk_signals present
security.risk assertions create_security_risk_assertion, hash_assertion_record, build_security_risk_assessed_event present
Templates 26 builders plus audit_templates, audit_template_sets present
Audit-record verifier verify_audit_records absent — TypeScript only
Edge-record verifier verify_evidence_edge_records absent — TypeScript only
Provenance recorder create_provenance_recorder absent — TypeScript only
Export bundles (vevb-1) build_export_bundle, verify_export_bundle absent — TypeScript only
Stores and recorders AuditStore, MemoryAuditStore, create_audit_recorder absent — TypeScript only

The four absent groups are not stubs that raise; the names do not exist. from veritio import verify_audit_records is an ImportError, not a runtime failure later.

Veritio has three independent chains. Python implements the verifier for exactly one of them.

audit record chain → verify_audit_records absent in Python
edge record chain → verify_evidence_edge_records absent in Python
commit ledger → verify_evidence_commits present in Python

verify_evidence_commits walks each streamId independently and returns {"ok": True} or {"ok": False, "index": n, "reason": "..."} at the first failure. It checks the hash algorithm, the veritio-json-v1 canonicalization label, the veritio-merkle-v1 tree algorithm, per-stream sequence continuity, previous-hash linkage, member-manifest normalization, recordCount, the recomputed recordsRoot, and the recomputed commit hash.

It deliberately does not reconcile member record hashes against independently verified records. A commit chain built entirely over fabricated record hashes verifies clean in isolation. The module’s own docstring says to compose it with verify_audit_records and verify_evidence_edge_records — functions that do not exist in this package. Treat a Python-side commit pass as a statement about the ledger, and run end-to-end record verification in a TypeScript process or against your own implementation of the rules in Hash chain.

Risk scoring is protocol math, not a TypeScript detail

Section titled “Risk scoring is protocol math, not a TypeScript detail”

risk.py is a byte-for-byte port of the TypeScript scorer. Every operation uses only clamp, floor, divide, and multiply — never pow, exp, or log — because those are the operations whose results are guaranteed to agree across language runtimes. Every constant lives in DEFAULT_RISK_POLICY under policyVersion veritio.reference.v1.

The parity fixture scores the same three pinned signal sets the TypeScript fixture scores and prints the score, level, policy version, and ordered factor contributions.

src/examples/risk/parity.py
"""Cross-language parity twin of ``src/examples/risk/score-and-explain.ts``.
The invariant this fixture proves: risk scoring is language-neutral protocol
math, not a TypeScript implementation detail. It scores the SAME three pinned
signal sets as the TypeScript fixture, under the same reference policy
(``veritio.reference.v1``), and prints the score, level, policyVersion and the
ordered factor contributions. Every printed number must match the corresponding
value in ``src/examples/risk/expected/score-and-explain.txt`` exactly — if the
Python port of ``round4`` / ``_sat`` / ``clamp01`` ever drifts from the
TypeScript one, a documented score moves here and the two expected outputs stop
agreeing.
Two details make the comparison honest rather than cosmetic:
* The signal dicts are copied verbatim from the TypeScript fixture, including
the deliberately sparse first case, so the fail-closed normalization defaults
(recoverable / production / magnitude 0) are exercised in both languages.
* Numbers are rendered through ``canonical_json``, the cross-language byte
contract, so a whole-valued float prints as ``1`` the way TS and Go emit it
instead of Python's ``1.0``. Comparing raw ``repr()`` would report a false
divergence on a score that is in fact byte-identical.
Deterministic by construction: literal inputs, pinned policy constants, no
clock, no randomness, no filesystem.
Run with the sibling SDK on the path:
PYTHONPATH=../veritio/sdks/python/src python3 src/examples/risk/parity.py
"""
from typing import Any
from veritio import (
DEFAULT_RISK_POLICY,
canonical_json,
normalize_risk_signals,
score_risk_signals,
)
# Pinned verbatim from src/examples/risk/score-and-explain.ts. Do not "improve"
# these values: their only job is to be the same bytes the TypeScript fixture
# scores, so the two expected outputs can be diffed field by field.
RISK_SCENARIOS: list[dict[str, Any]] = [
{
"name": "read-config-lookup",
"signals": {"operationType": "read"},
},
{
"name": "bulk-export-staging",
"signals": {
"operationType": "bulk",
"reversibility": "reversible",
"envCriticality": "staging",
"dataVolume": 5000,
"fanOut": 3,
"referenceCount": 12,
},
},
{
"name": "destructive-drop-production",
"signals": {
"operationType": "destructive",
"reversibility": "irreversible",
"envCriticality": "production",
"dataVolume": 250000,
"fanOut": 40,
"referenceCount": 180,
},
},
]
def canonical_number(value: float) -> str:
"""Render one score/contribution the way canonical JSON hashes it.
Parity is a claim about bytes, so the fixture must not print Python's float
repr: ``clamp01`` returns ``1.0`` for a saturated score while TypeScript
emits ``1``. ``canonical_json`` already coerces whole-valued finite floats to
int for exactly this reason, so routing every number through it makes the
printed line comparable to the TypeScript expected output instead of
manufacturing a difference that no hash would ever see.
"""
return canonical_json(value)
def factor_line(assessment: dict[str, Any]) -> str:
"""Flatten the ordered factor breakdown into one comparable line.
Factor ORDER is part of the cross-language conformance contract, not a
presentation choice, so the line is emitted in the order the scorer returns
rather than sorted: a reordered breakdown must show up as a diff here.
"""
return " ".join(
f"{factor['key']}:{canonical_number(factor['contribution'])}"
for factor in assessment["factors"]
)
def main() -> None:
"""Print the parity report for every pinned scenario under the reference policy.
Kept free of any ambient input (clock, environment, argv) so the program is
byte-identical across runs and machines; the documentation pipeline diffs
this stdout against expected/parity.txt.
"""
print(f"policyVersion={DEFAULT_RISK_POLICY['policyVersion']}")
print(f"bands={canonical_json(DEFAULT_RISK_POLICY['bands'])}")
for scenario in RISK_SCENARIOS:
assessment = score_risk_signals(scenario["signals"], DEFAULT_RISK_POLICY)
print("")
print(f"scenario={scenario['name']}")
print(f" signals={canonical_json(scenario['signals'])}")
print(f" normalized={canonical_json(normalize_risk_signals(scenario['signals']))}")
print(f" score={canonical_number(assessment['score'])}")
print(f" level={assessment['level']}")
print(f" policyVersion={assessment['policyVersion']}")
print(f" factors={factor_line(assessment)}")
if __name__ == "__main__":
main()
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

Two details make this a real comparison rather than a cosmetic one. The first scenario is deliberately sparse, so the fail-closed normalization defaults — reversibility: recoverable, envCriticality: production, magnitudes 0 — are exercised in both languages instead of being supplied by the fixture. And every number is rendered through canonical_json, so a saturated score prints as 1 the way TypeScript emits it rather than as Python’s 1.0. Comparing raw repr() would report a divergence on a score that is byte-identical under hashing.

Factor order is part of the contract. The line is emitted in the order the scorer returns, not sorted, so a reordered breakdown shows up as a diff.

score_risk_signals(signals, policy=DEFAULT_RISK_POLICY) takes the policy as an optional keyword, matching TypeScript. Go requires it explicitly. Python raises on an unknown operationType, reversibility, or envCriticality, and on a magnitude that is not a non-negative integer — it never silently degrades an unrecognized signal to a low score.

One small surface asymmetry: clamp01 and round4 are public in veritio.risk but are not re-exported from the package root, so from veritio import round4 fails while from veritio.risk import round4 works. _sat is private in both. band_of is re-exported from the root.

Governed action drafts derive identical identifiers

Section titled “Governed action drafts derive identical identifiers”

create_governed_action_draft is the host application’s server-side mutation boundary helper. It is present in Python with the same semantics as createGovernedActionDraft and CreateGovernedActionDraft, pinned by spec/conformance/governed-action-draft.json.

src/examples/governed/action-draft.py
"""Cross-language parity twin of ``src/examples/governed/action-draft.ts``.
The invariant this fixture proves: governed-action derivation is
language-neutral protocol math, not a TypeScript implementation detail. It
drafts the SAME pinned subscription upgrade as the TypeScript fixture — the
same entity definition, the same before/after rows, the same
``occurredAt``/``idempotencyKey`` — and prints the derived identifiers a host
application depends on at its mutation boundary:
* ``create_governed_action_draft`` derives the change id and the activity id
from ONE seed, sha256(``tenantId``:``idempotencyKey``), so a replayed
mutation reproduces the same change/activity pair in Python that it does in
TypeScript and Go instead of forking the graph per SDK.
* ``define_entity`` is the only place field capture is decided: the ``omit``
field never reaches the state commitment (and so can never become a governed
changed path), and the ``content_digest`` field is committed as a digest
rather than a raw value.
* The state-commitment digest and the derived ``revisionId`` are byte-identical
to the TypeScript ones, which is the real parity claim: canonical JSON,
sha256 framing and field ordering agree across languages.
* The draft is INPUT ONLY. It carries no store-assigned sequence and no record
hash; nothing is evidence until a conforming ``AuditStore`` appends it.
The printed JSON deliberately mirrors the TypeScript fixture's structure key
for key, so ``src/examples/governed/expected/action-draft.txt`` and
``src/examples/governed/expected/action-draft-python.txt`` can be diffed
directly. Exactly one line is allowed to differ: the prose ``note``, which
names the language's own entrypoint (``create_governed_action_draft`` vs
``createGovernedActionDraft``). Any diff on an id, digest, changed path, event
action or edge relation is a real cross-language divergence, not formatting.
Deterministic by construction: literal rows, a pinned ``occurredAt``, a pinned
idempotency key, no clock, no randomness, no filesystem. (``occurredAt`` must
stay pinned — ``create_governed_action_draft`` falls back to
``datetime.now(timezone.utc)`` when it is omitted.)
Run with the sibling SDK on the path:
PYTHONPATH=../veritio/sdks/python/src python3 src/examples/governed/action-draft.py
"""
import json
from typing import Any
from veritio import create_governed_action_draft, define_entity
# Pinned verbatim from src/examples/governed/action-draft.ts. Do not "improve"
# these values: their only job is to be the same inputs the TypeScript fixture
# drafts, so the two expected outputs can be diffed field by field.
GOVERNED_SCOPE = {"tenantId": "org_acme", "environment": "production"}
SUBSCRIPTION = define_entity(
authority="acme-billing",
entity_type="subscription",
schema_ref="acme://schemas/subscription@3",
field_set_ref="acme://fieldsets/subscription-governed@1",
identity=lambda row: row["id"],
fields={
"id": {"capture": "full"},
"plan": {"capture": "full"},
"seatCount": {"capture": "full"},
"status": {"capture": "full"},
# PII stays out of evidence as a value; only its digest is committed.
"accountEmail": {"capture": "content_digest"},
# Ungoverned operator prose never enters the commitment or changed paths.
"internalNotes": {"capture": "omit"},
},
)
BEFORE: dict[str, Any] = {
"id": "sub_9f31",
"accountEmail": "billing@acme.example",
"plan": "team",
"seatCount": 12,
"status": "active",
"internalNotes": "renewal call scheduled",
}
AFTER: dict[str, Any] = {
**BEFORE,
"plan": "enterprise",
"seatCount": 25,
"internalNotes": "upgrade approved on the renewal call",
}
def build_subscription_upgrade_draft() -> dict[str, Any]:
"""Build the pinned upgrade draft shared by the docs page and this fixture.
Kept as a named function so the documentation can show one call site while
CI re-runs the identical derivation. Every argument matches
``buildSubscriptionUpgradeDraft`` in the TypeScript twin, including
``occurredAt`` and ``idempotencyKey``, because both feed hashed evidence:
``occurredAt`` is stamped on every drafted event and edge, and
``idempotencyKey`` seeds the change/activity ids and the tenant-scoped
idempotency hash.
"""
return create_governed_action_draft(
{
"scope": GOVERNED_SCOPE,
"entity": SUBSCRIPTION,
"before": BEFORE,
"after": AFTER,
"actionType": "subscription.upgraded",
"activityType": "billing.plan_change",
"initiatedBy": {
"authority": "acme-billing",
"kind": "principal",
"type": "user",
"id": "usr_owner",
},
"performedBy": {
"authority": "acme-billing",
"kind": "principal",
"type": "service",
"id": "svc_billing_api",
},
"producer": {
"authority": "acme-billing",
"kind": "principal",
"type": "service",
"id": "svc_billing_api",
},
"occurredAt": "2026-08-09T10:00:00.000Z",
"idempotencyKey": "subscription.upgraded:sub_9f31:req_7c2a",
"mutationBinding": "same_transaction",
}
)
def main() -> None:
"""Print the governed-action parity report for the pinned upgrade.
Kept free of any ambient input (clock, environment, argv) so the program is
byte-identical across runs and machines; the documentation pipeline diffs
this stdout against expected/action-draft-python.txt, and a reviewer diffs
that file against the TypeScript twin's expected output.
"""
draft = build_subscription_upgrade_draft()
change_id = draft["changeRef"]["id"]
id_seed = change_id[change_id.rfind("_") + 1 :]
entity_id = draft["entityRef"]["id"]
commitment = draft["revision"]["stateCommitment"]
output = {
"derivedIds": {
"changeId": change_id,
"activityId": draft["activityRef"]["id"],
"entityId": entity_id,
"revisionId": draft["revision"]["ref"]["id"],
"idSeed": id_seed,
# Both ids are `<prefix>_<entityType>_<entityId>_<seed>` off the same
# sha256(tenantId:idempotencyKey) seed, so replay is idempotent.
"changeAndActivityShareOneSeed": (
change_id == f"chg_subscription_{entity_id}_{id_seed}"
and draft["activityRef"]["id"] == f"act_subscription_{entity_id}_{id_seed}"
),
},
"changedPaths": draft["revision"]["changedPaths"],
"stateCommitment": {
"algorithm": commitment["algorithm"],
"canonicalization": commitment["canonicalization"],
"schemaRef": commitment["schemaRef"],
"fieldSetRef": commitment["fieldSetRef"],
"digest": commitment["digest"],
"committedFields": sorted(commitment["fields"].keys()),
# `internalNotes` is declared `omit`, so it is absent from the
# commitment and can never become a governed changed path.
"omittedFieldPresent": "internalNotes" in commitment["fields"],
"accountEmailCommitment": commitment["fields"]["accountEmail"],
},
"eventActions": [event["action"] for event in draft["events"]],
"edgeRelations": [edge["relation"] for edge in draft["edges"]],
"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": any(
"sequence" in event for event in draft["events"]
),
"eventsCarryRecordHash": any("hash" in event for event in draft["events"]),
"outboxMutationBinding": draft["outboxEntry"]["mutationBinding"],
"outboxSchemaVersion": draft["outboxEntry"]["schemaVersion"],
},
}
print(json.dumps(output, indent=2))
if __name__ == "__main__":
main()
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"
}
}

Three invariants are visible in that output.

One seed drives both identifiers. The change id and the activity id are both <prefix>_<entityType>_<entityId>_<seed> over the same sha256(tenantId:idempotencyKey) digest, so a replayed mutation reproduces the same change and activity pair in Python that it does in TypeScript instead of forking the evidence graph per SDK.

define_entity is the only place field capture is decided. internalNotes is declared omit, so it never enters the state commitment and can never become a governed changed path — omittedFieldPresent is false and changedPaths contains only /plan and /seatCount even though the operator prose also changed. accountEmail is declared content_digest, so the commitment carries a digest envelope rather than the address.

A draft is input, not evidence. eventsCarryStoreAssignedSequence and eventsCarryRecordHash are both false. The draft has no sequence and no record hash because nothing has been appended yet. Python cannot close that gap in-process: there is no conforming AuditStore in this package, so draft["outboxEntry"] has to be written by your own transaction and drained to a store a TypeScript process owns, or to the hosted ingest path. The mutationBinding of same_transaction is a declaration about how you persist it, not something the SDK enforces.

create_governed_action_draft raises when no governed field changes, when a required ref is missing, and when a keyed_digest field is declared without digestKeys.keyedDigest. Pin occurredAt in tests: when omitted it falls back to datetime.now(timezone.utc) and the draft stops being reproducible.

Python exports 26 event builders, the same 26 TypeScript exports, grouped by audit_template_sets into auth, organization, data, agent, and code. They are a convenience layer over create_audit_event, and they are not pinned by conformance fixtures. Nothing in CI would notice if one language’s template drifted in the metadata it stamps.

One name genuinely diverges:

TypeScript episodeStartedTemplate
Go EpisodeStartedTemplate
Python activity_episode_started_template

The emitted event is the same. A mechanical camelCase-to-snake_case translation will miss it, which is exactly the failure mode to expect when porting a TypeScript agent-instrumentation module. The builder stamps activityEpisodeId as a reserved, un-shadowable grouping key, along with authSessionId and authContextId in reserved metadata that caller-supplied metadata cannot override.

The SDK raises rather than degrading. create_audit_event rejects missing identities and malformed actions. create_evidence_edge rejects relations outside the supported vocabulary and entity types outside the evidence entity set. create_evidence_commit rejects a non-positive sequence and a previousCommitHash that is neither None nor a sha256: digest. canonical_json rejects non-finite numbers instead of inventing an unstable representation for them.

Never replace one of those exceptions with an empty tenant, a default actor, or a partially normalized dictionary. An event that was recorded with a substituted identity is worse than an event that was never recorded, because the chain will verify and the claim will be wrong.

Run the upstream suite against the pinned source:

Terminal window
PYTHONPATH=sdks/python/src python3 -m unittest discover -s sdks/python/tests

Then run bun run verify:examples in this website to re-execute both fixtures on this page and diff their stdout against the committed expected output.

Read Cross-language parity matrix for the full capability table across all three SDKs, including which behaviors are pinned by shared fixtures and which are merely present. Use FastAPI for a complete Python application example. If your architecture needs the conforming authoritative-store interface or a record-chain verifier in-process, take the TypeScript path or implement the missing capability in the OSS repository before documenting it as available.