Skip to content
VeritioDocs

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.

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.

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.

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.

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”

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.

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.

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.

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
{
"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.

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.

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

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.

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 for how to call these builders in a host boundary, Event schema for the fields a template fills in, and Retention for what the retention labels above are supposed to drive.