| name | audit-trail-bcb538 |
| description | Use SEMPRE que o usuário mencionar SealedRecibo, DecisionID, Merkle chain, audit trail, evidência criptográfica, BCB 538/2025, retention paradox, LGPD vs retenção bancária, RFC 3161 timestamping, TSA, Zero-Persistence, shred_key, crypto-shredding, ou WORM tier no BigQuery. Triggers literais: "audit trail", "Merkle", "SealedRecibo", "DecisionID", "BCB 538", "retention paradox", "Zero-Persistence", "shred_key", "RFC 3161". Esta skill NÃO é genérica — é específica do contrato de evidência do REX Guard. Se o pedido for sobre logging/observabilidade comum, use outra skill. |
Audit Trail BCB 538/2025 — Contrato de Evidência
ATI (Auditable Trust Infrastructure) vive ou morre por causa desse pipeline. Se SealedRecibo está fraco, o moat regulatório que o AUDIT-2026-0409 confirmou (compliance layer, NÃO Thought Signatures) cai. Esta skill é o playbook canônico para implementar/validar o stack de evidência.
Conceitos não-negociáveis
SealedRecibo
Estrutura criptograficamente selada que prova que uma decisão de inferência aconteceu, com qual policy, em qual timestamp, com qual input/output. Selo = hash assinado + posição na Merkle chain + timestamp TSA RFC 3161.
DecisionID
Identificador único e globalmente ordenável de uma decisão. Recomendação: UUID v7 (timestamp embutido + entropy) — NÃO UUID v4 (perde ordenação) e NÃO ULID (compatibilidade BigQuery pior).
Retention Paradox
BCB 538/2025 exige retenção de logs de decisão por períodos específicos (5-10 anos dependendo do contexto). LGPD Art. 18 VI dá direito de deleção ao titular. Conflito estrutural. ATI resolve via crypto-shredding: o registro permanece (compliance bancário), a chave é destruída (deleção criptográfica satisfaz LGPD).
Zero-Persistence
Promessa do CISO Brief: dado sensível NÃO persiste em forma legível após inferência. Implementação real exige shred_key() que destrói chave de envelope encryption — não é flag, é deleção física da chave em Cloud KMS.
Schema canônico — SealedRecibo
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "SealedRecibo",
"type": "object",
"required": [
"decision_id",
"policy_snapshot_hash",
"input_hash",
"output_hash",
"model_id",
"model_version",
"tenant_id",
"sealed_at",
"merkle_position",
"merkle_root",
"tsa_token",
"signature"
],
"properties": {
"decision_id": {
"type": "string",
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$",
Procedure — Construção da Merkle Chain
import { createHash } from 'node:crypto';
interface MerkleNode {
hash: string;
left?: MerkleNode;
right?: MerkleNode;
}
function leafHash(recibo: SealedReciboCore): string {
const canonical = canonicalize(recibo);
return 'sha256:' + createHash('sha256').update(canonical).digest('hex');
}
function pairHash(left: string, right: string): string {
const concat = left.replace('sha256:', '') + right.replace('sha256:', '');
return 'sha256:' + createHash().(concat, ).();
}
(): {
(leaves. === ) ();
(leaves. === ) { : leaves[] };
padded = leaves. % === ? [...leaves, leaves[leaves. - ]] : leaves;
: [] = [];
( i = ; i < padded.; i += ) {
nextLevel.((padded[i], padded[i + ]));
}
(nextLevel);
}
Procedure — TSA RFC 3161
Em produção: integrar com TSA autoritativa (DigiCert, GlobalSign, ICP-Brasil — preferência por ICP-Brasil para defesa em juízo brasileiro).
async function getTSAToken(merkleRoot: string): Promise<string> {
const tsq = buildTimeStampRequest({
messageImprint: merkleRoot,
hashAlgorithm: 'SHA-256',
nonce: crypto.randomBytes(8),
certReq: true,
});
const response = await fetch(TSA_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/timestamp-query' },
body: tsq,
});
if (!response.ok) {
throw new TSAUnavailableError(`TSA returned ${response.status}`);
}
const tsr = await response.arrayBuffer();
return Buffer.from(tsr).toString('base64');
}
Stub aceitável para staging/dev: timestamp local + assinatura própria, claramente marcado tsa_token: "DEV-STUB:<iso8601>". NUNCA aceitável em produção — defesa regulatória cai.
Procedure — Crypto-shredding (CG-003 fix)
import { KeyManagementServiceClient } from '@google-cloud/kms';
async function shredKey(envelopeKeyRef: string): Promise<ShredProof> {
const kms = new KeyManagementServiceClient();
const [version] = await kms.destroyCryptoKeyVersion({ name: envelopeKeyRef });
const shredEvent = {
key_ref: envelopeKeyRef,
scheduled_destroy_time: version.destroyTime,
requested_at: new Date().toISOString(),
requestor: getActorIdentity(),
legal_basis: 'LGPD Art. 18 VI — direito de deleção',
};
await writeAuditEvent('CRYPTO_SHRED_INITIATED', shredEvent);
return {
key_destroyed: true,
destruction_scheduled_at: version.destroyTime,
audit_event_id: shredEvent.audit_event_id,
};
}
Validação obrigatória pós-shred:
gcloud kms keys versions describe ${VERSION} \
--key=${KEY} --keyring=${KEYRING} --location=${LOCATION} \
--format="value(state)"
BigQuery WORM Tier
Tabela audit_trail.recibos_sealed precisa ser write-once:
CREATE TABLE `${PROJECT}.audit_trail.recibos_sealed` (
decision_id STRING NOT NULL,
policy_snapshot_hash STRING NOT NULL,
input_hash STRING NOT NULL,
output_hash STRING NOT NULL,
model_id STRING NOT NULL,
model_version STRING NOT NULL,
tenant_id STRING NOT NULL,
sealed_at TIMESTAMP NOT NULL,
merkle_position INT64 NOT NULL,
merkle_root STRING NOT NULL,
tsa_token STRING NOT NULL,
signature STRING NOT NULL,
envelope_key_ref STRING,
ingested_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP()
)
PARTITION BY DATE(sealed_at)
CLUSTER BY tenant_id, model_id
OPTIONS (
description = "WORM audit trail — BCB 538/2025 compliance",
partition_expiration_days = 3650
);
Output Contract — Validação de SealedRecibo
Função pública mandatória:
interface VerificationResult {
valid: boolean;
checks: {
schema_valid: boolean;
signature_valid: boolean;
merkle_inclusion_valid: boolean;
tsa_token_valid: boolean;
policy_hash_resolvable: boolean;
};
failures: string[];
}
async function verifyRecibo(recibo: SealedRecibo): Promise<VerificationResult> {
}
Boundaries (CRITICAL)
- NUNCA persistir input ou output em claro — apenas hashes
- NUNCA usar UUID v4 para DecisionID — perde ordenação cronológica
- NUNCA aceitar TSA stub em produção — só staging/dev claramente marcado
- NUNCA implementar
shred_key() como return true — é fraude do CISO Brief
- NUNCA permitir DELETE/UPDATE na tabela
recibos_sealed
- NUNCA misturar tenants no mesmo Merkle batch — cross-contamination de evidência
- SEMPRE validar
policy_snapshot_hash resolvível antes de selar (CG-001)
- SEMPRE fail-closed: TSA down = recibo não emitido = inferência rejeitada
- SEMPRE registrar
CRYPTO_SHRED_INITIATED antes de chamar KMS destroy
- SEMPRE preferir ICP-Brasil para TSA quando defesa em juízo brasileiro for relevante