# Governed change API

Kind

reference

For

developer · governance

Verified against

@veritio/core@0.4.7

A host application that mutates a row and then separately “logs” the change produces two artifacts that can disagree. The governed-change builders remove that gap: one call at the mutation boundary derives the change identity, the activity identity, the minimized state commitment, the changed paths, the event set, the edge set, and a single outbox entry to append. This page is the signature and semantics reference for those builders across the three SDKs.

## The six exported symbols

[Section titled “The six exported symbols”](#the-six-exported-symbols)

Symbol

Role

`defineEntity`

Declares one governed entity type and its per-field capture policy.

`createGovernedActionDraft`

Host-facing helper. Derives ids, idempotency hash, and changed paths, then delegates.

`createGovernedChangeDraft`

Lower-level builder. Owns event, edge, revision, and outbox semantics.

`governedRevisionId`

Derives the deterministic revision id from entity, state digest, and change id.

`mergeVeritioMetadata`

Merges caller metadata with SDK-owned context keys, rejecting shadowing.

`refKey`

Formats an authority-qualified `EvidenceRef` into a stable join key.

`createGovernedActionDraft` is the one most applications call. It is a derivation layer only — every protocol decision belongs to `createGovernedChangeDraft`, which it calls.

## Signatures

[Section titled “Signatures”](#signatures)

### TypeScript

[Section titled “TypeScript”](#typescript)

```ts
function defineEntity<Row extends Record<string, unknown>>(
  definition: {
    authority: string
    type: string
    schemaRef: string
    fieldSetRef: string
    identity(row: Row): string
    fields: Partial<Record<keyof Row & string, { capture: CaptureMode }>>
    lineagePolicy?: 'linear' | 'dag'   // RESERVED, not enforced by any SDK
  },
): GovernedEntity<Row>                  // adds ref(rowOrId: Row | string): EvidenceRef

function createGovernedActionDraft<Row extends Record<string, unknown>>(input: {
  scope: EvidenceScope & { tenantId: string }
  entity: GovernedEntity<Row>
  before?: Row
  after: Row
  actionType: string
  activityType: string
  initiatedBy: EvidenceRef
  performedBy: EvidenceRef
  producer: EvidenceRef
  occurredAt?: string | Date            // defaults to new Date()
  idempotencyKey: string
  changeId?: string
  activityId?: string
  changedPaths?: string[]               // inferred from the field policy when absent
  context?: VeritioContextMetadata
  metadata?: Record<string, unknown>
  capturePolicyRef?: { id: string; version: string }
  expectedParentRevisionRef?: EvidenceRef
  mutationBinding?: 'same_transaction' | 'not_transaction_bound' | 'best_effort'
  digestKeys?: { keyedDigest?: { keyVersion: string; secret: string } }
}): GovernedChangeDraft

function createGovernedChangeDraft<Row extends Record<string, unknown>>(input: {
  scope: EvidenceScope & { tenantId: string }
  entity: GovernedEntity<Row>
  before?: Row
  after: Row
  changedPaths: string[]                // required here, never inferred
  change: {
    id: string
    type: string
    initiatedBy: EvidenceRef
    authorizationAssertionRef?: EvidenceRef
    delegationAssertionRef?: EvidenceRef
  }
  activity: { id: string; type: string; performedBy: EvidenceRef }
  producer: EvidenceRef
  occurredAt: string | Date             // required here, no default
  idempotencyKeyHash: string            // required here, caller-computed
  // context / metadata / capturePolicyRef / expectedParentRevisionRef
  // / mutationBinding / digestKeys as above
}): GovernedChangeDraft

function governedRevisionId(
  entityType: string, entityId: string, stateDigest: string, changeId: string,
): string

function mergeVeritioMetadata(
  callerMetadata?: Record<string, unknown>, context?: VeritioContextMetadata,
): Record<string, unknown>

function refKey(ref: EvidenceRef): string
```

### Python

[Section titled “Python”](#python)

```python
def define_entity(
    *,                                  # keyword-only
    authority: str,
    entity_type: str,                   # emitted as the protocol key "type"
    schema_ref: str,
    field_set_ref: str,
    identity: Callable[[dict[str, Any]], str],
    fields: dict[str, dict[str, str]],
    lineage_policy: str | None = None,
) -> dict[str, Any]                     # a plain dict; there is no .ref() method

def create_governed_action_draft(input_action: dict[str, Any]) -> dict[str, Any]
def create_governed_change_draft(input_change: dict[str, Any]) -> dict[str, Any]

def governed_revision_id(
    entity_type: str, entity_id: str, state_digest: str, change_id: str,
) -> str

def merge_veritio_metadata(
    caller_metadata: dict[str, Any] | None = None,
    context: dict[str, Any] | None = None,
) -> dict[str, Any]

def ref_key(ref: dict[str, Any]) -> str
```

The two draft builders take one dictionary whose keys are the protocol camelCase names — `scope`, `entity`, `after`, `actionType`, `idempotencyKey`, `occurredAt`, `mutationBinding`, `digestKeys`. Only `define_entity` uses snake\_case parameters.

### Go

[Section titled “Go”](#go)

```go
func DefineEntity(definition GovernedEntityDefinition) (GovernedEntityDefinition, error)

func CreateGovernedActionDraft(input GovernedActionDraftInput) (GovernedChangeDraft, error)
func CreateGovernedChangeDraft(input GovernedChangeDraftInput) (GovernedChangeDraft, error)

func GovernedRevisionID(entityType, entityID, stateDigest, changeID string) string

func MergeVeritioMetadata(callerMetadata, context map[string]any) (map[string]any, error)

func RefKey(ref EvidenceRef) (string, error)

type GovernedEntityDefinition struct {
    Authority, Type, SchemaRef, FieldSetRef string
    Identity      func(map[string]any) string   // must be non-nil
    Fields        map[string]EntityFieldPolicy  // EntityFieldPolicy{Capture string}
    LineagePolicy string                        // RESERVED, not enforced
}
```

`GovernedActionDraftInput` mirrors the TypeScript field names in Go casing: `ActionType`, `IdempotencyKey`, `ChangeID`, `ActivityID`, `MutationBinding`, `DigestKeys`, plus `AuthorizationAssertionRef` and `DelegationAssertionRef` on the nested `GovernedChangeDeclaration` used by `CreateGovernedChangeDraft`.

## Capture modes

[Section titled “Capture modes”](#capture-modes)

`defineEntity` is the only place field capture is decided. The v1 state-commitment builder implements four of the eight declared modes. The other four are reserved names in the type: selecting one is a draft-time failure, never a silently weaker commitment.

Mode

Status

What lands in `stateCommitment.fields[key]`

`omit`

implemented

Nothing. The key is absent and can never become a changed path.

`full`

implemented

The normalized JSON value.

`content_digest`

implemented

`{ captureMode: 'content_digest', digest: 'sha256:…' }`

`keyed_digest`

implemented

`{ algorithm: 'hmac-sha256', keyVersion, digest: 'sha256:…' }`

`randomized_digest`

reserved

Fails closed at draft time.

`reference`

reserved

Fails closed at draft time.

`redact`

reserved

Fails closed at draft time.

`encrypt`

reserved

Fails closed at draft time.

A reserved mode raises `capture mode <mode> is not supported by the current governed-change draft helper`. A `keyed_digest` field without key material raises `digestKeys.keyedDigest is required for keyed_digest fields`; the key version and secret are each checked for a non-empty string. Key material is injected per call at the host boundary and is never stored in the draft.

Fields are iterated in sorted key order and any field not declared in `fields` is invisible to the commitment. That is what stops the outbox from becoming a copy of the application row.

## Two digests from one idempotency key

[Section titled “Two digests from one idempotency key”](#two-digests-from-one-idempotency-key)

`createGovernedActionDraft` consumes the raw `idempotencyKey` twice, with two different separators, and stores neither the key nor anything from which the key can be read back.

```text
idempotencyKey  +  scope.tenantId
        │
        ├── sha256("tenantId:idempotencyKey")      → first 16 hex → id seed
        │       used in chg_<type>_<entityId>_<seed>
        │       and     act_<type>_<entityId>_<seed>
        │
        └── sha256("tenantId\0idempotencyKey")     → full 64 hex
                stored as metadata.idempotencyKeyHash
```

The id seed is short and appears in publicly visible identifiers, so it is deliberately not the same value as the hash a store uses for idempotency conflict detection. The NUL separator in `hashIdempotencyKey` keeps the tenant/key boundary unambiguous — two different tenant-and-key splits cannot collide onto one hash. Both derivations are tenant-scoped, so the same application-level key in two tenants produces unrelated values.

The revision id is derived separately and is content-addressed **plus** change-scoped:

```text
rev_<entityType>_<entityId>_<digest12>_<change8>
                              │           │
                              │           └── first 8 hex of sha256(changeId)
                              └── 12 hex after the "sha256:" prefix of the state digest
```

Content addressing alone would merge a rollback with the earlier revision it restores, because both commit byte-identical state. Mixing in the change id keeps a rollback distinct while a replay of the same change stays idempotent.

## Reserved context keys

[Section titled “Reserved context keys”](#reserved-context-keys)

Nine keys belong to Veritio. `mergeVeritioMetadata` applies caller metadata first (sorted, with `null`/`undefined` values dropped), then writes the context keys over the top so a caller cannot shadow them.

```text
authSessionId   authContextId    activityEpisodeId
traceId         correlationId    causationEventId
changeId        capturePolicyId  collectionSource
```

Passing any of them inside `metadata` instead of `context` is rejected before anything is built, with the message `metadata.<key> is reserved by Veritio` (a `TypeError` in TypeScript and Python, a returned `error` in Go). These keys are what Change, Trace, and Explain projections group on; a caller-supplied value under the same name would silently corrupt that grouping.

## Draft output and outbox shape

[Section titled “Draft output and outbox shape”](#draft-output-and-outbox-shape)

Every builder returns the same structure: `changeRef`, `activityRef`, `entityRef`, `revision`, `events`, `edges`, and `outboxEntry`.

```text
outboxEntry
  schemaVersion              "2026-06-23"          frozen literal
  mutationBinding            "not_transaction_bound"  default when unset
  expectedParentRevisionRef  present only when a parent was resolved
  records                    the same three AuditEventInput values as .events
  edges                      the same EvidenceEdgeInput values as .edges
```

`schemaVersion` is a fixed string, not a version negotiated at runtime; a consumer that sees a different value is reading a different format. `mutationBinding` accepts `same_transaction`, `not_transaction_bound`, or `best_effort` and defaults to `not_transaction_bound` — the honest default, since the SDK cannot observe whether the host actually enrolled the append in its transaction. The value is copied into `metadata.captureAssurance` on all three events alongside `captureMethod: 'transactional_outbox'`.

The three events are always `change.declared`, `activity.recorded`, and `entity.revision.created`, and the base edges are always `has_activity`, `has_output`, `performed_by`, and `generated`. A fifth `derived_from` edge and `outboxEntry.expectedParentRevisionRef` appear only when `before` was supplied **and** the caller passed `expectedParentRevisionRef`. The builders never fabricate a placeholder parent: a synthetic parent would assert a `derived_from` edge to a revision that never existed and hand the store an optimistic-concurrency token its real head can never match.

## Verified fixture

[Section titled “Verified fixture”](#verified-fixture)

This example runs in CI and its output is byte-compared.

src/examples/governed/action-draft.ts

```ts
import {
  type GovernedChangeDraft,
  createGovernedActionDraft,
  defineEntity,
} from '@veritio/core'

/**
 * Proves the governed-action draft invariants a host application depends on at
 * its mutation boundary:
 *
 * - `defineEntity` is the only place field capture is decided: an `omit` field
 *   can never reach the state commitment, and a `content_digest` field is
 *   committed as a digest rather than a raw value, so the outbox never becomes
 *   a copy of the application row.
 * - `createGovernedActionDraft` derives the change id and the activity id from
 *   ONE seed — sha256(`tenantId:idempotencyKey`) — so a replayed mutation
 *   reproduces the same change/activity pair instead of forking the graph.
 * - Changed paths are inferred only from governed fields, and the draft emits
 *   three event actions plus four edge relations.
 * - The draft is INPUT ONLY. It carries no store-assigned sequence and no
 *   record hash; nothing exists as evidence until a conforming `AuditStore`
 *   appends it.
 *
 * Every id, timestamp, and idempotency key is pinned so the printed output is
 * byte-stable across runs and across the TS/Python/Go SDKs.
 */
type SubscriptionRow = {
  id: string
  accountEmail: string
  plan: string
  seatCount: number
  status: string
  internalNotes: string
}

export const governedScope = { tenantId: 'org_acme', environment: 'production' } as const

const subscription = defineEntity<SubscriptionRow>({
  authority: 'acme-billing',
  type: 'subscription',
  schemaRef: 'acme://schemas/subscription@3',
  fieldSetRef: 'acme://fieldsets/subscription-governed@1',
  identity: (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' },
  },
})

const before: SubscriptionRow = {
  id: 'sub_9f31',
  accountEmail: 'billing@acme.example',
  plan: 'team',
  seatCount: 12,
  status: 'active',
  internalNotes: 'renewal call scheduled',
}

const after: SubscriptionRow = {
  ...before,
  plan: 'enterprise',
  seatCount: 25,
  internalNotes: 'upgrade approved on the renewal call',
}

/**
 * Builds the pinned upgrade draft shared by the documentation page and this
 * fixture's byte-compared output. Kept as an exported function so the docs can
 * show one call site while CI re-runs the identical derivation.
 */
export function buildSubscriptionUpgradeDraft(): GovernedChangeDraft {
  return createGovernedActionDraft({
    scope: governedScope,
    entity: subscription,
    before,
    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',
  })
}

if (import.meta.main) {
  const draft = buildSubscriptionUpgradeDraft()
  const idSeed = draft.changeRef.id.slice(draft.changeRef.id.lastIndexOf('_') + 1)

  const output = {
    derivedIds: {
      changeId: draft.changeRef.id,
      activityId: draft.activityRef.id,
      entityId: draft.entityRef.id,
      revisionId: draft.revision.ref.id,
      idSeed,
      // Both ids are `<prefix>_<entityType>_<entityId>_<seed>` off the same
      // sha256(tenantId:idempotencyKey) seed, so replay is idempotent.
      changeAndActivityShareOneSeed:
        draft.changeRef.id === `chg_subscription_${draft.entityRef.id}_${idSeed}` &&
        draft.activityRef.id === `act_subscription_${draft.entityRef.id}_${idSeed}`,
    },
    changedPaths: draft.revision.changedPaths,
    stateCommitment: {
      algorithm: draft.revision.stateCommitment.algorithm,
      canonicalization: draft.revision.stateCommitment.canonicalization,
      schemaRef: draft.revision.stateCommitment.schemaRef,
      fieldSetRef: draft.revision.stateCommitment.fieldSetRef,
      digest: draft.revision.stateCommitment.digest,
      committedFields: Object.keys(draft.revision.stateCommitment.fields).sort(),
      // `internalNotes` is declared `omit`, so it is absent from the commitment
      // and can never become a governed changed path.
      omittedFieldPresent: 'internalNotes' in draft.revision.stateCommitment.fields,
      accountEmailCommitment: draft.revision.stateCommitment.fields.accountEmail,
    },
    eventActions: draft.events.map((event) => event.action),
    edgeRelations: draft.edges.map((edge) => edge.relation),
    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: draft.events.some((event) => 'sequence' in event),
      eventsCarryRecordHash: draft.events.some((event) => 'hash' in event),
      outboxMutationBinding: draft.outboxEntry.mutationBinding,
      outboxSchemaVersion: draft.outboxEntry.schemaVersion,
    },
  }

  console.log(JSON.stringify(output, null, 2))
}
```

verified output

```json
{
  "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"
  }
}
```

What each block proves:

-   `changeId` and `activityId` share the suffix `312ca4bde590b545`. One seed, two ids — a replayed request reproduces the same change/activity pair instead of forking the graph.
-   `revisionId` ends `_a71e2768812c_fe34f6d0`, and `a71e2768812c` is exactly the first twelve hex characters of the state digest below it. The derivation is visible in the id itself.
-   `changedPaths` contains `/plan` and `/seatCount` only. `internalNotes` changed in the row but is declared `omit`, and `accountEmail` did not change, so neither is a governed path.
-   `omittedFieldPresent: false` and the absence of `internalNotes` from `committedFields` prove the omit policy is enforced in the commitment, not just in the diff.
-   `accountEmailCommitment` is a `content_digest` object. The address itself never enters evidence.
-   `eventsCarryStoreAssignedSequence: false` and `eventsCarryRecordHash: false` are the important ones. A draft is input, not evidence.

## Fail-closed behavior

[Section titled “Fail-closed behavior”](#fail-closed-behavior)

Condition

Result

`scope.tenantId` empty

`scope.tenantId is required`

`actionType`, `activityType`, or `idempotencyKey` empty

`<field> is required`

Any `EvidenceRef` missing a part

`ref.authority` / `ref.kind` / `ref.type` / `ref.id is required`

`initiatedBy`, `performedBy`, or `producer` is not `kind: 'principal'`

`principal ref is required`

`identity(row)` returns an empty string

`entity.id is required`

No governed field changed

`at least one governed field must change`

Reserved key in `metadata`

`metadata.<key> is reserved by Veritio`

Reserved capture mode

`capture mode <mode> is not supported…`

`keyed_digest` without key material

`digestKeys.keyedDigest is required for keyed_digest fields`

Unparseable `occurredAt`

`occurredAt must be a valid date`

Non-finite number in a captured field

`field values must be finite JSON numbers`

Nothing degrades into a partial draft. TypeScript and Python raise (Python raises `ValueError` for the `occurredAt` case and `TypeError` elsewhere); Go returns a zero-value `GovernedChangeDraft` and an `error`.

A timezone-naive `occurredAt` string is interpreted as UTC in all three SDKs rather than as host-local time, because `occurredAt` is hashed evidence and a host-local reading would make the same input hash differently on two machines.

## API-shape drift between SDKs

[Section titled “API-shape drift between SDKs”](#api-shape-drift-between-sdks)

The derived bytes are identical across languages — that is pinned by `spec/conformance/governed-action-draft.json` and `governed-revision-id.json`. The call shapes are not identical, and porting code needs these differences.

-   **Python `define_entity` is keyword-only** and takes `entity_type`, not `type`, since `type` is a builtin. The value is emitted under the protocol key `type`. Its other parameters are snake\_case (`schema_ref`, `field_set_ref`, `lineage_policy`) while the draft-builder dictionaries stay camelCase.
-   **Python and Go return plain data.** Only the TypeScript `defineEntity` attaches a `ref(rowOrId)` method to the returned entity. In Python and Go the entity ref is resolved internally from `identity(row)`; use `ref_key` / `RefKey` to format a ref you already hold.
-   **Go `DefineEntity` validates `Identity` is non-nil** and returns `errors.New("identity is required")` otherwise. TypeScript and Python do not check this at definition time; a missing identity function surfaces later, when the draft builder calls it.
-   **Go takes `OccurredAt` as a plain string.** There is no `time.Time` overload. It parses RFC 3339 with optional nanoseconds, falls back to the timezone-naive layout, and formats to millisecond precision. TypeScript accepts `string | Date` and Python accepts `str | datetime`.
-   **Go’s capture mode is an untyped `string`.** TypeScript has a `CaptureMode` union, so a typo fails at compile time there and at draft time in Go and Python.
-   **`createGovernedChangeDraft` requires what the action helper derives.** Calling it directly means supplying `change.id`, `activity.id`, `changedPaths`, `occurredAt`, and `idempotencyKeyHash` yourself.

## Authorization and delegation refs

[Section titled “Authorization and delegation refs”](#authorization-and-delegation-refs)

`authorizationAssertionRef` and `delegationAssertionRef` are accepted **only** by `createGovernedChangeDraft`, nested inside `change`. `GovernedActionDraftInput` has no field for them in any of the three SDKs. To attach either one, call the lower-level builder and derive the ids and idempotency hash yourself.

Both land in `metadata` on the `change.declared` event and are dropped when absent. They are references to assertion records; the builder does not resolve, fetch, or validate the assertion they point at.

## What this does not prove

[Section titled “What this does not prove”](#what-this-does-not-prove)

A returned draft is a set of well-formed inputs. It is not evidence, and several things it contains are claims rather than proofs.

-   **No record exists yet.** The draft carries no store-assigned sequence and no record hash. Nothing is evidence until a conforming `AuditStore` appends `outboxEntry` and assigns both.
-   **`mutationBinding` is a declaration.** `same_transaction` records what the host says it did. The SDK cannot observe the host’s transaction and does not verify the claim.
-   **The state commitment covers declared fields only.** A field with no policy, or one set to `omit`, is outside the digest. Its later modification is invisible to any check against this revision.
-   **`content_digest` is not confidentiality for low-entropy values.** An email address or a status enum can be recovered by guessing and hashing candidates. Use `keyed_digest` with host-held key material when that matters.
-   **`changedPaths` describes the `before`/`after` pair the host passed in.** It does not prove the database row actually changed, or that the host passed its real prior state.
-   **`idempotencyKeyHash` does not enforce idempotency.** It only makes tenant-scoped conflict detection possible in a store that implements the uniqueness constraint.

Continue with [Governed changes](/docs/concepts/governed-changes/) for the model these builders serve, the [governed actions guide](/docs/guides/governed-actions/) for wiring one into a mutation boundary, and the [transactional outbox guide](/docs/guides/transactional-outbox/) for making `mutationBinding: 'same_transaction'` true rather than merely declared. The [hash chain](/docs/concepts/hash-chain/) page explains what the store adds once the outbox entry is appended.

[Edit page](https://github.com/getveritio/veritio-website/edit/main/src/content/docs/docs/reference/governed-change-api.mdx)

Last updated: Aug 23, 2026

[Previous  
Event schema](/docs/reference/event-schema/)[Next  
Template catalogue](/docs/reference/template-catalogue/)

Veritio provides evidence support, not legal advice or automatic compliance.

This site uses cookieless, anonymous analytics (Umami) by default. With your consent, we also enable Google Analytics, which sets cookies and sends usage data to Google. [Privacy Policy](/legal/privacy/)
