Skip to content

Concepts

Attestation

Attest records what a model was asked and what it produced at the moment of generation, and signs it into the firm's ledger.

@bedrockgovernance/attest is the SDK that captures a generation (an AI model producing text that helps shape a piece of advice) and records it as an immutable ledger entry. Where a review job attests what document was produced and reviewed, an attestation captures exactly what the model saw and returned, at the instant it happened.

What a generation captures

A single attest() call records the complete generation bundle. Only correlationId, model, input, and output are required. Everything else is optional, included when your pipeline has it.

json
{
  "correlationId": "chat_9f2c1e90b3",
  "clientReference": "CLI-41269355",
  "adviser": { "name": "Jane Smith", "fcaRef": "JXS01234" },
  "model": {
    "provider": "anthropic",
    "name": "claude-opus-4-8",
    "parameters": { "temperature": 0.2, "maxTokens": 4096 }
  },
  "instructions": "You are a suitability report assistant. Never recommend...",
  "input": [
    { "role": "user", "content": "What should I weigh for a balanced investor nearing retirement?" }
  ],
  "promptTemplate": { "id": "suitability-v3", "version": "3.2.1", "hash": "sha256:9f2c..." },
  "retrievedContext": [
    { "source": "intelliflo:factfind", "reference": "20655934", "hash": "sha256:1a4b..." }
  ],
  "guardrails": [
    { "rule": "MAX_EQUITY_ALLOCATION", "triggered": false },
    { "rule": "VULNERABLE_CLIENT_CHECK", "triggered": true, "action": "flagged_for_review" }
  ],
  "output": {
    "content": "For a balanced investor nearing retirement, weigh...",
    "finishReason": "stop"
  },
  "usage": { "inputTokens": 3120, "outputTokens": 1840 }
}

Fields

FieldTypeReqDescription
correlationIdstringYesA stable id your tool already holds that scopes one drafting conversation, such as a chat, thread, or case id. Reuse it on every generation in that conversation so they group together. It need not be globally unique, and it does not need to survive to review time. See Grouping generations.
modelobjectYes{ provider, name, version?, parameters? }. Pin the exact name and version, not a moving alias, so drift detection stays meaningful.
inputstring | { role, content }[]YesWhat the model saw for this call: a single prompt string, or the messages sent as an array of { role, content } (role is user, assistant, tool, or system), in order, verbatim. This is the input to this one call and may be a single message, not necessarily a conversation. Canonicalised into the record hash so any later change is detectable.
instructionsstringSystem-level instructions the model was given, exactly as sent. Optional, but governance-critical: capture it whenever your call has one.
outputobjectYesWhat the model produced: { content, finishReason?, toolCalls? }. The platform stamps outputHash so each generation's output stays independently tamper-evident.
promptTemplateobject{ id, version, hash } of the template that rendered the prompt, if you use one.
retrievedContextarrayGrounding documents given to the model: { source, reference?, hash, content? }. The hash makes content provable even when stored by reference; large corpora can omit content.
toolsarrayTool or function definitions the model could call, verbatim.
guardrailsarrayRules evaluated during generation: { rule, triggered, action? }. Same shape as AI context.
usageobjectToken counts: { inputTokens, outputTokens }.
adviserobject{ name, fcaRef } of the responsible individual, recorded as the ledger actor under SM&CR. Optional: when omitted, the record is attributed to the firm credential that authenticated the call.
supersedesstringgenerationId of a prior generation this one revises, giving an ordered draft, revision, final lineage within a conversation. Optional; without it the ledger sequence gives chronological order.
clientReferencestringLinks the generation to the client record. A client may have several pieces of advice in flight, so this scopes rather than identifies. Include it whenever your tool has the CRM's client identifier.
documentReferencestringThe document identifier once it exists. Rarely known at generation time.

Two ways to call it

Attest explicitly when you want to hand the platform the bundle yourself. Tag every call with the id of the conversation it belongs to, an id your tool already holds:

ts
import { Bedrock } from '@bedrockgovernance/attest';

const bedrock = new Bedrock({ apiKey: process.env.BEDROCK_API_KEY });

const { generationId } = await bedrock.attest({
  correlationId: chatId,
  clientReference,
  adviser: { name: 'Jane Smith', fcaRef: 'JXS01234' },
  model: { provider: 'anthropic', name: 'claude-opus-4-8' },
  instructions,
  input,
  retrievedContext,
  output: { content, finishReason: 'stop' },
});

Or wrap your model with the AI SDK middleware so every call is notarised automatically, with no change to your generation code:

ts
import { wrapLanguageModel } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import { attestMiddleware } from '@bedrockgovernance/attest/ai-sdk';

const model = wrapLanguageModel({
  model: anthropic('claude-opus-4-8'),
  middleware: attestMiddleware({
    bedrock,
    correlationId: chatId, // the conversation's stable id
    adviser: { name: 'Jane Smith', fcaRef: 'JXS01234' },
  }),
});

// Every generateText / streamText call now writes an attestation.

The middleware captures the request parameters and the result and posts the attestation in the background, so it never adds latency to the response. For stacks that do not use the AI SDK, the explicit attest() call works from any runtime.

How it enters the chain

An attestation is written as a GENERATION_RECORDED ledger record through the same write path as every other event: canonicalised, hashed, chained to the previous record, signed with the firm's key, and mirrored to object-locked storage. It becomes a first-class link in the firm's ledger the moment the model call happens, before any document is submitted.

  1. The generation tool calls attest() for each model call, tagging it with the correlationId of the conversation it belongs to. The platform records a GENERATION_RECORDED event and returns a generationId.
  2. The advice document is produced and, in time, submitted for review through the firm's usual path.
  3. At review, the platform surfaces the generations that informed the advice, so the reviewer sees the prompts and model outputs behind it alongside the document.

Grouping generations

A single piece of advice is rarely one model call, and the calls are often exploratory: the adviser asks the model to weigh options, check considerations, and draft passages, then writes the final document themselves. Each call is a separate attest() and a separate GENERATION_RECORDED record.

  • correlationId groups a conversation. Pass the same stable id (a chat, thread, or case id your tool already has) on every call in one drafting conversation, so the records form a set rather than isolated entries.
  • supersedes orders within it, optionally. A regenerated turn can name the generationId it replaces, giving an explicit draft, revision, final lineage. Leave it out and the ledger sequence still gives chronological order.

A piece of advice can draw on more than one conversation. The platform associates a firm's conversations with the advice they informed using the client reference, the responsible adviser, and the timeline, so the review of a piece of advice surfaces every generation behind it. The result is that a regulator can trace the final wording back through every prompt and output that shaped it, each independently hashed into the chain.

Try it

bash
npm install @bedrockgovernance/attest

Source

github.com/bedrockgovernance/attest

See also

Hi! I'm Bedrock's AI assistant. I can answer questions about the product, pricing, compliance coverage, and integrations. What would you like to know?