| Metadato | Valor |
|---|---|
| Versión / Version | 1 |
| Estado / Status | Vigente desde la fase 06.3 para todo AIP nuevo |
| URI del perfil / Profile URI | https://docs.docuten.com/earchiving/audit-register-profile/v1 |
| Dónde se declara / Where it is declared | Elemento auditRegister/profile de documentation/package-profile.xml (esquema package-profile-v1.xsd) y, como identificadores, los linkingEventIdentifier del objeto de entidad intelectual de metadata/preservation/premis.xml |
| Implementación / Implementation | AuditAppender y AuditEntryHashes (earchiving-auditlog), HashChain y SealEnvelope (earchiving-core/.../core/crypto/), PremisEventPersistenceService (earchiving-ingest), PremisHistoryReaderAdapter (earchiving-app) |
| Decisiones / Decisions | D-14, D-15, D-16 (06.3-CONTEXT.md); D-09 para el orden temporal de los eventos del sello #1 |
| Fecha / Date | 2026-10-21 |
Este documento explica cómo se localiza en el registro de auditoría de Docuten eArchiving la historia de un AIP a partir de identificadores estables que el propio AIP lleva dentro, y cómo se comprueba que esas entradas no han sido alteradas. Es el documento que la URI de la tabla anterior identifica.
La URI es un identificador, no una URL que deba resolverse (D-16). No se dereferencia en tiempo de ingesta, sellado ni verificación.
This document explains how the history of an AIP is located in the Docuten eArchiving audit register from stable identifiers carried inside the AIP itself, and how those entries are checked for alteration. The URI is an identifier, not a URL to be dereferenced (D-16).
En metadata/preservation/premis.xml (generado en el paso 10, antes del sello #2):
intellectualEntity) con tres identificadores: el aipId
(tipo UUID), el tenant (tipo tenant-id) y el SIP de origen (tipo sip-id).linkingEventIdentifier por cada evento de los pasos 1-7 de la saga. Sus valores son los
mismos que la columna audit_log.premis_event_id, en el orden de audit_log.seq.En documentation/package-profile.xml, el elemento auditRegister con tres valores: esta URI
(profile), el tenantId y el sipId. No lleva la lista de ids de evento, que vive solo en
premis.xml (D-15). Los dos identificadores coinciden con los tenant-id y sip-id del IE.
Lo que NO lleva, a propósito: ni el id del sello del lote (sealId) ni un rango de seq. El
paquete se sella antes de que exista el sello del lote que contendrá sus eventos, así que esa
referencia no se puede conocer en el momento de sellar el AIP (D-14).
The IE in premis.xml carries the aipId, tenant-id and sip-id identifiers and one
linkingEventIdentifier per event of steps 1-7, whose values are audit_log.premis_event_id, in
audit_log.seq order. The package profile carries a pointer (this URI, tenantId, sipId) without
the event list. Neither carries a sealId or a seq range: the package is sealed before the batch
seal covering its events exists.
Eventos de la ingesta (pasos 1-11), por tenant + sipId + premis_event_id.
PremisEventPersistenceService.persist escribe cada evento PREMIS de la saga en dos sitios:
audit_log con premis_event_id = el eventIdentifierValue del evento y
detail_json = el evento serializado.sip_event_index (premis_event_id, sip_id, tenant_id).Para localizar los eventos se recorren esas dos tablas en ese orden: el índice da los ids del SIP y
audit_log da las entradas, ordenadas por seq. Las dos tablas tienen FORCE ROW LEVEL SECURITY por
tenant. Así lo hacen:
GET /api/v1/ingest/sip/{sipId}/audit (AuditSipController), con paginación por
cursor y la respuesta 404 tanto para un sipId inexistente como para uno de otro tenant;PremisHistoryReaderAdapter, que lee la historia que se serializa en premis.xml.Los eventos posteriores al paso 7 de la misma ingesta se localizan por el mismo índice del sipId:
Están en audit_log y NO en premis.xml (D-01).
Eventos posteriores ligados al AIP, por aipId. Varias entradas posteriores llevan el aipId
dentro de su detail_json y no se indexan por sipId:
AIP_HASH_SEALED, CatalogIndexAdapter);AIP_DOWNLOADED) y de la atestación (ATTESTATION_DOWNLOADED);AIP_INTEGRITY_CHECK, AipIntegrityCheckAdapter);DIP_DISSEMINATED);AIP_BLOB_MISSING (hay fila en el catálogo y el blob WORM no está en el contenedor del tenant), escrita por AipBlobInventoryAuditor en el barrido periódico del inventario de AIP, desde la fase 06.9.1;SIDECAR_MISSING (AIP con aip_hash no nulo y sidecar de sello ausente), del mismo barrido;AIP_BLOB_UNRESOLVED (la fila existe pero el paso 6 no declara el aipBlobId), del mismo barrido.Las tres últimas entradas se escriben con deduplicación atómica: una sola por (tenant, acción, aipId)
aunque el barrido se repita (un blob que reaparece y vuelve a perderse no emite otra). Su detalle lleva
solo las claves aipId, tenantId, kind y rowSubmittedAt, todas en texto.
Hoy no existe un endpoint que filtre por aipId. Estas entradas se leen con
GET /api/v1/admin/audit (AuditController, paginado por cursor y filtrable por action) y se
seleccionan por el aipId del detalle. El aipId y el sipId están en el IE de premis.xml y en
la fila del catálogo (aips.aip_id, aips.sip_id).
Ingestion events (steps 1-11) are indexed per SIP in sip_event_index and read from audit_log in
seq order (GET /api/v1/ingest/sip/{sipId}/audit). Later AIP-bound entries carry the aipId in
their detail_json (including the inventory-sweep findings AIP_BLOB_MISSING, SIDECAR_MISSING and
AIP_BLOB_UNRESOLVED, one entry per tenant, action and aipId) and are read through GET /api/v1/admin/audit; there is no aipId filter today.
Cadena de entradas (por tenant).
audit_log lleva
entry_hash = SHA-256( JCS(id, tenantId, seq, timestamp, action, detailJson) || prev_hash ).
Detalles:JcsCanonicalizer.detailJson nulo se normaliza a "{}".prev_hash son los 32 bytes del entry_hash de la entrada anterior del mismo tenant.AuditAppender (escritura), AuditEntryHashes.recomputeEntryHashHex
(recomputación) y HashChain.computeEntryHash.prev_hash el génesis
SHA-256("") (HashChain.genesisHashHex).seq es MAX(seq) + 1 por tenant bajo un pg_advisory_xact_lock por tenant: la
cadena es lineal y sin huecos dentro de cada tenant.Sellos del lote.
SHA-256(JCS(sobre)) (SealEnvelope.imprint), con un sobre de siete campos:
sealKind, tenantId, seqFrom, seqTo, lastEntryHash, prevSealHash y tree. El tree
es el árbol Merkle de las hojas del lote, definido en merkle-profile-v2.md.prevSealHash = SHA-256(tsa_token del sello anterior) (SealEnvelope.prevSealHashOf). El primer
sello del tenant lleva el mismo génesis que la cadena de entradas.GET /api/v1/admin/audit/seals/{sealId}/verify (AuditController,
AuditQueryService.verifySeal) hace tres cosas:entry_hash;AuditChainVerifier (verifyTenant,
verifyAllTenants). La ejecuta de forma periódica AuditChainVerificationSweeper; hoy no tiene
endpoint propio.Each entry carries entry_hash = SHA-256(JCS(id, tenantId, seq, timestamp, action, detailJson) ||
prev_hash), with genesis SHA-256("") per tenant. Batches are sealed over a seven-field envelope
(SealEnvelope) whose tree follows merkle-profile-v2.md, chained by prevSealHash. A seal is
verified through GET /api/v1/admin/audit/seals/{sealId}/verify; the full-chain verifier
(AuditChainVerifier) runs as a scheduled sweep.
seq)
puede no coincidir con el orden por eventDateTime. Caso concreto: los eventos del sello #1 que
se emiten en una reanudación del paso 8 llevan el instante de la reanudación. La excepción es el
evento de sello de tiempo, que lleva el genTime del token TSA (D-09), y ese instante puede ser
anterior a eventos que ya están en la cadena. premis.xml sigue siempre el orden de seq
(D-02).sip_event_index ni de audit_log del SIP. La lectura de la historia falla cerrada: el paso 10 no
genera premis.xml sin los eventos de los pasos 6 y 7.Chain order (seq) may differ from eventDateTime order (seal #1 events emitted on a resume; the
timestamp event carries the token genTime, D-09). A foreign tenant sees no rows and the history read
fails closed. D-16 is an assumption: identifier, not resolvable URL; what Umbus expects is pending.
La v1 es inmutable: todo AIP sellado que la cite la cita para siempre. Cualquier cambio en lo que este
documento describe se publica como un perfil nuevo con su propia URI (…/audit-register-profile/v2) y
no se edita esta versión.
v1 is immutable; any change is published as v2 under its own URI.