# Template catalogue

Kind

reference

For

developer · governance

Verified against

@veritio/core@0.4.7

A host that writes its own action strings will eventually write two spellings of the same thing. The audit templates exist to prevent that: each builder owns one canonical action string, one target resource type, and a set of evidence defaults. This page is the mapping, produced from a fixture that calls every builder in the published package.

## Templates are pure builders

[Section titled “Templates are pure builders”](#templates-are-pure-builders)

A template takes host inputs and returns an `AuditEventInput`. It does not hash, sequence, store, or redact. Those happen later, when the recorder turns the input into a record.

```text
host inputs
     ↓ auditTemplates.<set>.<builder>
AuditEventInput  { actor, action, target, purpose, lawfulBasis, retention, metadata }
     ↓ createAuditEvent      (id, occurredAt, scope validation, redaction)
     ↓ AuditStore.append     (sequence, previousHash, hash)
record
```

Because templates are pure, the catalogue below can be generated without a store, a clock, or a tenant. That is also why a template cannot fail closed on a missing tenant: scope validation belongs to `createAuditEvent`, not to the builder.

Every field a template sets is a default, not a lock — but the two halves of the event resolve in opposite directions, and mixing them up is the most common source of surprise.

```text
evidence fields                     metadata
purpose, lawfulBasis,               caller keys
dataCategories, retention                ↓  merged first
        ↑                           template-reserved keys
caller value wins                        ↓  merged last
        ↓                           activityEpisodeId, riskSignals
template default fills the gap           ↓  stamped last of all
```

`buildTemplate` resolves `purpose`, `lawfulBasis`, `dataCategories`, and `retention` as caller value first, template value second: you can always override an evidence classification. Metadata goes the other way. Template-reserved keys such as `sessionId`, `proposalId`, and `subjectId` are merged after caller metadata, and `activityEpisodeId` plus normalized `riskSignals` are stamped after that. A caller cannot shadow the grouping keys read models join on. That asymmetry is intentional: classification is a host policy decision, identity and grouping are not.

Only fields the caller actually supplies appear on the event. A template never emits a present-but-null field, so absent optional metadata leaves no key behind — which matters because the canonical bytes, and therefore the record hash, differ between an omitted key and a key set to `null`.

## The catalogue

[Section titled “The catalogue”](#the-catalogue)

Twenty-six builders ship in `@veritio/core@0.4.7`. Twenty-five are reachable through the `auditTemplates` registry and advertised by `auditTemplateSets`. One is not; it is listed last and explained below.

Builder

Action

Target type

Purpose

Lawful basis

Retention

Required inputs

`auditTemplates.auth.userCreated`

`auth.user.created`

`user`

`access_management`

`contract`

`security_1y`

`userId`

`auditTemplates.auth.signedIn`

`auth.session.created`

`session`

`access_management`

`contract`

`security_1y`

`userId`, `sessionId`

`auditTemplates.auth.signedOut`

`auth.session.revoked`

`session`

`access_management`

`contract`

`security_1y`

`userId`, `sessionId`

`auditTemplates.auth.passwordResetRequested`

`auth.password.reset.requested`

`password_reset_request`

`access_management`

`contract`

`security_1y`

`userId`, `resetRequestId`

`auditTemplates.organization.created`

`org.created`

`organization`

`access_management`

`contract`

`security_1y`

`organizationId`, `actor`

`auditTemplates.organization.memberInvited`

`org.member.invited`

`organization_invitation`

`access_management`

`contract`

`security_1y`

`organizationId`, `invitationId`, `inviter`

`auditTemplates.organization.memberJoined`

`org.member.joined`

`organization_member`

`access_management`

`contract`

`security_1y`

`organizationId`, `memberId`, `actor`

`auditTemplates.organization.memberRemoved`

`org.member.removed`

`organization_member`

`access_management`

`contract`

`security_1y`

`organizationId`, `memberId`, `actor`

`auditTemplates.organization.memberRoleChanged`

`org.member.role.changed`

`organization_member`

`access_management`

`contract`

`security_1y`

`organizationId`, `memberId`, `actor`

`auditTemplates.data.consentGranted`

`consent.granted`

`consent`

`consent_management`

`consent`

`consent_7y`

`actor`, `consentId`

`auditTemplates.data.consentRevoked`

`consent.revoked`

`consent`

`consent_management`

`consent`

`consent_7y`

`actor`, `consentId`

`auditTemplates.data.subjectRequestCreated`

`data.subject.request.created`

`subject_request`

`data_subject_workflow`

`legal_obligation`

`subject_request_3y`

`actor`, `subjectRequestId`, `requestType`

`auditTemplates.data.exportBundleCreated`

`export.bundle.created`

`export_bundle`

`data_subject_workflow`

`legal_obligation`

`export_1y`

`actor`, `exportBundleId`

`auditTemplates.data.retentionPolicyApplied`

`retention.policy.applied`

`policy`

`retention_management`

`legal_obligation`

`retention_audit_7y`

`actor`, `policyId`

`auditTemplates.agent.sessionStarted`

`agent.session.started`

`agent_session`

`change_provenance`

—

`security_1y`

`sessionId`, `agentActor`

`auditTemplates.agent.promptRecorded`

`agent.prompt.recorded`

`agent_session`

`change_provenance`

—

`security_1y`

`sessionId`, `promptHash`, `agentActor`

`auditTemplates.agent.toolCalled`

`agent.tool.called`

`tool_call`

`change_provenance`

—

`security_1y`

`sessionId`, `toolCallId`, `tool`, `status`, `agentActor`

`auditTemplates.code.changeProposalCreated`

`change.proposal.created`

`change_proposal`

`change_provenance`

—

`security_1y`

`proposalId`, `actor`

`auditTemplates.code.filesChanged`

`change.files.changed`

`source_tree`

`change_provenance`

—

`security_1y`

`sourceTreeId`, `actor`

`auditTemplates.code.reviewApprovalRecorded`

`review.approval.recorded`

`pull_request`

`change_provenance`

—

`security_1y`

`pullRequestId`, `reviewer`

`auditTemplates.code.reviewFindingCreated`

`review.finding.created`

`pull_request`

`change_provenance`

—

`security_1y`

`pullRequestId`, `reviewer`

`auditTemplates.code.reviewWaiverRecorded`

`review.waiver.recorded`

`pull_request`

`change_provenance`

—

`security_1y`

`pullRequestId`, `reviewer`

`auditTemplates.code.ciJobCompleted`

`ci.job.completed`

`ci_run`

`change_provenance`

—

`security_1y`

`ciRunId`, `service`, `status`

`auditTemplates.code.deploymentCreated`

`deploy.deployed`

`deployment`

`change_provenance`

—

`security_1y`

`deploymentId`, `service`

`auditTemplates.code.runtimeObserved`

`audit.runtime.observed`

`runtime_event`

`runtime_observation`

—

`security_1y`

`runtimeEventId`, `actor`

`episodeStartedTemplate` (unregistered)

`activity.episode.started`

`activity_episode`

`change_provenance`

—

`security_1y`

`activityEpisodeId`, `actor`

A dash in the lawful-basis column means the template sets no `lawfulBasis` at all. The field is then absent from the built event, not present-and-empty. That is deliberate for provenance and agent events: the SDK will not guess a basis on the host’s behalf. Supplying `lawfulBasis` as a caller input adds it.

### Builder names do not predict action strings

[Section titled “Builder names do not predict action strings”](#builder-names-do-not-predict-action-strings)

Six builders in the table emit an action you would not derive from the method name. Treat the action column as authoritative and never reconstruct one from a camel-case identifier.

```text
auditTemplates.code.deploymentCreated  →  deploy.deployed
auditTemplates.code.runtimeObserved    →  audit.runtime.observed
auditTemplates.auth.signedIn           →  auth.session.created
auditTemplates.auth.signedOut          →  auth.session.revoked
auditTemplates.organization.created    →  org.created          (not organization.created)
auditTemplates.data.subjectRequestCreated → data.subject.request.created
```

The organization set is the sharpest trap. Its registry key is `organization`, its input fields are `organizationId` and `organizationDisplay`, and its actions are all short `org.*` strings. A host filter written against `organization.member.joined` matches nothing.

### One purpose default breaks the pattern

[Section titled “One purpose default breaks the pattern”](#one-purpose-default-breaks-the-pattern)

Seven of the eight `code` builders default `purpose` to `change_provenance`. `runtimeObservedTemplate` defaults to `runtime_observation` instead, because a production observation is not evidence about who changed the software. It is the only builder in any set that uses that purpose. If you group evidence by purpose to answer “what did this release change”, `runtime_observation` rows are outside that answer by design.

## Verified catalogue output

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

The fixture below calls all twenty-five registered builders plus `episodeStartedTemplate` with pinned ids, a pinned timestamp, and a pinned scope, then flattens each result to the row shape used in the table. It runs in CI and its output is byte-compared.

verified catalogue output

```json
{
  "auditTemplateSets": {
    "auth": [
      "auth.user.created",
      "auth.session.created",
      "auth.session.revoked",
      "auth.password.reset.requested"
    ],
    "organization": [
      "org.created",
      "org.member.invited",
      "org.member.joined",
      "org.member.removed",
      "org.member.role.changed"
    ],
    "data": [
      "consent.granted",
      "consent.revoked",
      "data.subject.request.created",
      "export.bundle.created",
      "retention.policy.applied"
    ],
    "agent": [
      "agent.session.started",
      "agent.prompt.recorded",
      "agent.tool.called"
    ],
    "code": [
      "change.proposal.created",
      "change.files.changed",
      "review.approval.recorded",
      "review.finding.created",
      "review.waiver.recorded",
      "ci.job.completed",
      "deploy.deployed",
      "audit.runtime.observed"
    ]
  },
  "catalogue": [
    {
      "set": "auth",
      "declaredActions": [
        "auth.user.created",
        "auth.session.created",
        "auth.session.revoked",
        "auth.password.reset.requested"
      ],
      "builders": [
        {
          "builder": "auditTemplates.auth.userCreated",
          "action": "auth.user.created",
          "actorType": "user",
          "targetType": "user",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.auth.signedIn",
          "action": "auth.session.created",
          "actorType": "user",
          "targetType": "session",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.auth.signedOut",
          "action": "auth.session.revoked",
          "actorType": "user",
          "targetType": "session",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.auth.passwordResetRequested",
          "action": "auth.password.reset.requested",
          "actorType": "user",
          "targetType": "password_reset_request",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        }
      ],
      "coverage": {
        "declaredCount": 4,
        "builderCount": 4,
        "emittedMatchesDeclared": true,
        "duplicateActions": false
      }
    },
    {
      "set": "organization",
      "declaredActions": [
        "org.created",
        "org.member.invited",
        "org.member.joined",
        "org.member.removed",
        "org.member.role.changed"
      ],
      "builders": [
        {
          "builder": "auditTemplates.organization.created",
          "action": "org.created",
          "actorType": "user",
          "targetType": "organization",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.organization.memberInvited",
          "action": "org.member.invited",
          "actorType": "user",
          "targetType": "organization_invitation",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.organization.memberJoined",
          "action": "org.member.joined",
          "actorType": "user",
          "targetType": "organization_member",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.organization.memberRemoved",
          "action": "org.member.removed",
          "actorType": "user",
          "targetType": "organization_member",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.organization.memberRoleChanged",
          "action": "org.member.role.changed",
          "actorType": "user",
          "targetType": "organization_member",
          "purpose": "access_management",
          "lawfulBasis": "contract",
          "retention": "security_1y"
        }
      ],
      "coverage": {
        "declaredCount": 5,
        "builderCount": 5,
        "emittedMatchesDeclared": true,
        "duplicateActions": false
      }
    },
    {
      "set": "data",
      "declaredActions": [
        "consent.granted",
        "consent.revoked",
        "data.subject.request.created",
        "export.bundle.created",
        "retention.policy.applied"
      ],
      "builders": [
        {
          "builder": "auditTemplates.data.consentGranted",
          "action": "consent.granted",
          "actorType": "user",
          "targetType": "consent",
          "purpose": "consent_management",
          "lawfulBasis": "consent",
          "retention": "consent_7y"
        },
        {
          "builder": "auditTemplates.data.consentRevoked",
          "action": "consent.revoked",
          "actorType": "user",
          "targetType": "consent",
          "purpose": "consent_management",
          "lawfulBasis": "consent",
          "retention": "consent_7y"
        },
        {
          "builder": "auditTemplates.data.subjectRequestCreated",
          "action": "data.subject.request.created",
          "actorType": "user",
          "targetType": "subject_request",
          "purpose": "data_subject_workflow",
          "lawfulBasis": "legal_obligation",
          "retention": "subject_request_3y"
        },
        {
          "builder": "auditTemplates.data.exportBundleCreated",
          "action": "export.bundle.created",
          "actorType": "user",
          "targetType": "export_bundle",
          "purpose": "data_subject_workflow",
          "lawfulBasis": "legal_obligation",
          "retention": "export_1y"
        },
        {
          "builder": "auditTemplates.data.retentionPolicyApplied",
          "action": "retention.policy.applied",
          "actorType": "service",
          "targetType": "policy",
          "purpose": "retention_management",
          "lawfulBasis": "legal_obligation",
          "retention": "retention_audit_7y"
        }
      ],
      "coverage": {
        "declaredCount": 5,
        "builderCount": 5,
        "emittedMatchesDeclared": true,
        "duplicateActions": false
      }
    },
    {
      "set": "agent",
      "declaredActions": [
        "agent.session.started",
        "agent.prompt.recorded",
        "agent.tool.called"
      ],
      "builders": [
        {
          "builder": "auditTemplates.agent.sessionStarted",
          "action": "agent.session.started",
          "actorType": "ai_agent",
          "targetType": "agent_session",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.agent.promptRecorded",
          "action": "agent.prompt.recorded",
          "actorType": "ai_agent",
          "targetType": "agent_session",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.agent.toolCalled",
          "action": "agent.tool.called",
          "actorType": "ai_agent",
          "targetType": "tool_call",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        }
      ],
      "coverage": {
        "declaredCount": 3,
        "builderCount": 3,
        "emittedMatchesDeclared": true,
        "duplicateActions": false
      }
    },
    {
      "set": "code",
      "declaredActions": [
        "change.proposal.created",
        "change.files.changed",
        "review.approval.recorded",
        "review.finding.created",
        "review.waiver.recorded",
        "ci.job.completed",
        "deploy.deployed",
        "audit.runtime.observed"
      ],
      "builders": [
        {
          "builder": "auditTemplates.code.changeProposalCreated",
          "action": "change.proposal.created",
          "actorType": "ai_agent",
          "targetType": "change_proposal",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.filesChanged",
          "action": "change.files.changed",
          "actorType": "ai_agent",
          "targetType": "source_tree",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.reviewApprovalRecorded",
          "action": "review.approval.recorded",
          "actorType": "user",
          "targetType": "pull_request",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.reviewFindingCreated",
          "action": "review.finding.created",
          "actorType": "user",
          "targetType": "pull_request",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.reviewWaiverRecorded",
          "action": "review.waiver.recorded",
          "actorType": "user",
          "targetType": "pull_request",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.ciJobCompleted",
          "action": "ci.job.completed",
          "actorType": "service",
          "targetType": "ci_run",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.deploymentCreated",
          "action": "deploy.deployed",
          "actorType": "service",
          "targetType": "deployment",
          "purpose": "change_provenance",
          "lawfulBasis": null,
          "retention": "security_1y"
        },
        {
          "builder": "auditTemplates.code.runtimeObserved",
          "action": "audit.runtime.observed",
          "actorType": "service",
          "targetType": "runtime_event",
          "purpose": "runtime_observation",
          "lawfulBasis": null,
          "retention": "security_1y"
        }
      ],
      "coverage": {
        "declaredCount": 8,
        "builderCount": 8,
        "emittedMatchesDeclared": true,
        "duplicateActions": false
      }
    }
  ],
  "unregisteredBuilders": [
    {
      "builder": "episodeStartedTemplate",
      "action": "activity.episode.started",
      "actorType": "user",
      "targetType": "activity_episode",
      "purpose": "change_provenance",
      "lawfulBasis": null,
      "retention": "security_1y"
    }
  ],
  "totals": {
    "sets": 5,
    "declaredActions": 25,
    "registeredBuilders": 25,
    "unregisteredBuilders": 1,
    "everySetFullyCovered": true
  }
}
```

Read the output in three parts.

`auditTemplateSets` is the vocabulary the package advertises. It is exported data, not derived from the builders, which is exactly why it can drift from them.

`catalogue[].coverage` is the invariant check. `declaredCount` and `builderCount` must agree, `emittedMatchesDeclared` compares the ordered list of actions the builders actually returned against the ordered list the set declares, and `duplicateActions` catches two builders claiming the same action. All five sets report `emittedMatchesDeclared: true` and `duplicateActions: false`. `totals.everySetFullyCovered: true` is the roll-up. That is the useful result: in this version, no advertised action is unimplemented and no builder emits an action outside its set.

`unregisteredBuilders` is the gap. It holds exactly one row.

## The builder that no set advertises

[Section titled “The builder that no set advertises”](#the-builder-that-no-set-advertises)

`totals` reports 25 declared actions, 25 registered builders, and 1 unregistered builder. The unregistered one is `activity.episode.started`.

It is a first-class, documented, exported builder in all three SDKs. It is absent from every `auditTemplateSets` list in all three SDKs. So a host that validates its outbound actions against `auditTemplateSets` — the obvious thing to do with an exported vocabulary — will reject an action its own SDK emits.

The three SDKs also do not agree on what the builder is called, or on whether it is reachable from a grouped registry.

SDK

Builder name

In the grouped registry?

In the advertised set?

TypeScript

`episodeStartedTemplate`

No — top-level export only

No

Python

`activity_episode_started_template`

Yes — `audit_templates["agent"]["episode_started"]`

No

Go

`EpisodeStartedTemplate`

Go ships no grouped registry

No

Python’s `agent` registry group therefore holds four builders while `audit_template_sets["agent"]` declares three actions. TypeScript’s `agent` group holds three and matches. Go has `AuditTemplateSets` but no `AuditTemplates` equivalent, so Go callers must reference the exported functions directly and get no set-to-builder mapping at all.

The practical consequence: `auditTemplateSets` is a floor, not a closed vocabulary. Validate against it plus `activity.episode.started`, or validate against the schema’s action pattern instead.

## When a template is the wrong tool

[Section titled “When a template is the wrong tool”](#when-a-template-is-the-wrong-tool)

Templates cover lifecycle events with a stable, single target: an account, a session, a consent, a deployment. Three situations fall outside that shape.

A **governed create/update/delete** on host domain data needs changed-path derivation, a tenant-scoped idempotency hash, and revision evidence. [`createGovernedActionDraft`](/docs/guides/governed-actions/) produces those; no template does. Reaching for `auditTemplates.code.changeProposalCreated` to record a row edit produces an event that looks right and carries none of the diff evidence.

An **event with no canonical action here** should use a host-defined action through `createAuditEvent` directly. Do not repurpose the closest template — an `org.member.role.changed` event that actually records a billing plan change is worse than an honest custom action, because it silently poisons every filter and export built on the canonical vocabulary.

**High-volume request logging** is out of scope for all of them. These builders are for events a reviewer would want to see individually, and each one costs a sequence number in the tenant chain.

## Failure modes

[Section titled “Failure modes”](#failure-modes)

**Raw content in agent and code metadata.** Every `agent.*` and `code.*` builder runs its caller metadata through a raw-content guard before building. Key names ending in `prompt`, `diff`, `patch`, `path`, `stdout`, `stderr`, `output`, `args`, `log`, `token`, `secret`, `password`, or `apikey` throw. So do string values containing a `diff --git` line, a `@@ -1,2 +1,2 @@` hunk header, or a `Bearer` token shape. Keys ending in `hash`, `hashes`, `id`, `ids`, `count`, or `status` are explicitly allowed — that is the intended shape of the metadata these templates want.

This is a throw, not a redaction. The event is never built.

**The guard is not applied consistently to the episode builder.** Go’s `EpisodeStartedTemplate` passes `blockRawContent: true`. TypeScript’s `episodeStartedTemplate` and Python’s `activity_episode_started_template` do not. Metadata that throws in Go is accepted in TypeScript and Python, where it then falls back to key-name redaction in `createAuditEvent`. Do not rely on the template guard as your only minimization step.

**Scope.** `organizationCreatedTemplate` and the three member builders default `scope` to `{ tenantId: organizationId }` when the caller omits it. No other builder defaults scope. Omit scope elsewhere and the missing tenant surfaces later, at event creation.

**Role normalization.** Passing `role` as an array deduplicates and sorts it before it reaches metadata, so `['admin','viewer']` and `['viewer','admin','viewer']` produce identical bytes and identical hashes. Passing a whitespace-only string drops the key entirely.

## What this does not prove

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

The verified output covers the **TypeScript** package only. There is no fixture under `spec/conformance` pinning template output across languages — the conformance suite covers canonical JSON, hashing, redaction, risk scoring, governed-action drafts, and export bundles, but not templates. Python and Go template output is not byte-compared against TypeScript by any check in the repository. The action strings and defaults in the table match across the three files as read at the pinned revision; nothing enforces that they stay matched.

`everySetFullyCovered: true` proves the registry is internally consistent. It does not prove the vocabulary is complete, correct for your domain, or stable across versions.

The action vocabulary is not enforced by the protocol. `spec/event.schema.json` accepts any dotted action string, so a typo’d custom action is a valid event. Templates are a convention that keeps hosts on one spelling; they are not a validator.

Finally, `retention: 'consent_7y'` is a named policy label carried on the event. It records which retention class the host assigned. It does not delete anything, and it does not establish that the assignment is the right one — enforcement lives in your storage and retention jobs. These builders produce compliance evidence; they are not legal advice and do not establish that any regulatory obligation has been met.

Continue with [Audit templates](/docs/guides/audit-templates/) for how to call these builders in a host boundary, [Event schema](/docs/reference/event-schema/) for the fields a template fills in, and [Retention](/docs/concepts/retention/) for what the retention labels above are supposed to drive.

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

Last updated: Aug 23, 2026

[Previous  
Governed change API](/docs/reference/governed-change-api/)[Next  
Risk policy](/docs/reference/risk-policy/)

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/)
