Record your first audit event
This tutorial records an invitation followed by the resulting membership. The two actions share one tenant scope, so the store orders them in one chain and the second record points to the first record’s hash.
Before you run it
Section titled “Before you run it”- Complete Installation for TypeScript.
- Run this in a server-side module where the tenant and actor have already been authenticated.
- Use the in-memory store only for this local exercise.
Two records, one tenant-local chain
Section titled “Two records, one tenant-local chain”import { type AuditRecord, MemoryAuditStore, createAuditEvent, verifyAuditRecords,} from '@veritio/core'
export const tutorialScope = { tenantId: 'org_acme', environment: 'production' } as const
/** * Records the deterministic two-event chain reused by the opening tutorial and * its tamper exercise. Fixed identifiers and timestamps keep the documented * output reproducible while the store still assigns the authoritative sequence * numbers, previous hashes, and record hashes. */export async function recordTutorialChain(): Promise<AuditRecord[]> { const store = new MemoryAuditStore()
const invitation = createAuditEvent({ id: 'evt_member_invited_01', occurredAt: '2026-08-09T10:00:00.000Z', actor: { type: 'user', id: 'usr_owner' }, action: 'organization.member.invited', target: { type: 'organization', id: 'org_acme' }, scope: tutorialScope, purpose: 'access_management', lawfulBasis: 'contract', retention: 'security_1y', metadata: { role: 'viewer' }, })
const membership = createAuditEvent({ id: 'evt_member_joined_01', occurredAt: '2026-08-09T10:01:00.000Z', actor: { type: 'user', id: 'usr_member' }, action: 'organization.member.joined', target: { type: 'organization', id: 'org_acme' }, scope: tutorialScope, purpose: 'access_management', lawfulBasis: 'contract', retention: 'security_1y', metadata: { role: 'viewer' }, })
await store.append(invitation, { idempotencyKey: 'invitation:inv_123' }) await store.append(membership, { idempotencyKey: 'membership:mem_123' }) return store.list(tutorialScope)}
if (import.meta.main) { const records = await recordTutorialChain() const output = { sequences: records.map((record) => record.sequence), previousHashLinked: records[1]?.previousHash === records[0]?.hash, verification: verifyAuditRecords(records), } console.log(JSON.stringify(output, null, 2))}Run it with:
bun src/examples/tutorial/record-and-verify.tsThe checked fixture prints this exact output:
{ "sequences": [ 1, 2 ], "previousHashLinked": true, "verification": { "ok": true }}sequences confirms gapless tenant-local ordering. previousHashLinked confirms that record two stores record one’s hash. verification.ok confirms that the current canonical bytes, sequence values, and links agree.
What your application supplies
Section titled “What your application supplies”The host supplies the event identity, timestamp, actor, action, target, scope, governance labels, and minimized metadata. Resolve tenantId and actor identity from trusted server context; do not copy either from an untrusted request body without authorization.
The two idempotency keys identify the logical invitation and membership operations. A retry should reuse the same key and the same event bytes. A different operation needs a different key.
What the store adds
Section titled “What the store adds”append creates the AuditRecord envelope: tenant-local sequence, previousHash, hash, appendedAt, the canonicalization label, and a hashed idempotency key. list requires tenant scope again before returning the ordered records.
That separation matters: the application describes what happened; the authoritative store owns ordering and conflict checks.
Checkpoint: Verification passed
Section titled “Checkpoint: Verification passed”Before continuing, confirm all three output properties match the checked output above. A successful chain proves that the recorded bytes remain internally consistent. It does not prove the original application claim was truthful or authorized.
Next, tamper with the copied records and inspect the exact failures.