ED03 - Formato de diario: artefactos JSON, firma temporal, V7GUID, Sidecars

Problema

Un empresario quiere llevar su diario en Git - pero ¿cómo se ve técnicamente una entrada, exactamente? La práctica actual no conoce ninguna respuesta vinculante:

Idea central

Una entrada de diario de GitCover es un artefacto JSON con:

  1. JSON-Schema-First - el esquema existe antes de la entrada; la entrada se valida contra el esquema (hook de pre-commit)
  2. V7GUID (Class Identifier) - clasifica el documento/la acción según el Registry .gitcover (qué/qué tipo); sirve para la organización del archivado y las consultas a la BD (p. ej., EF Core en Vertical App)
  3. uuidV7 (Object ID) - ya es por sí sola la identidad unívoca (DocID) de la entrada, RFC 9562 §5.7, generada a partir de una marca de tiempo predefinida (o now()) mediante el Helper de GitCover (UuidV7Gen), el resto se rellena con aleatoriedad
  4. Clave compuesta V7GUID:uuidV7 - la clave compuesta del Sidecar GCPN, sirve para la organización del archivado y las consultas; la hora de registro está contenida en la uuidV7 (marca de tiempo de 48 bits), sin campo datetime/date separado
  5. Obligación de Sidecar - cada comprobante recibe un Sidecar .v7g.md con su propia uuidV7 (acto de clasificación) y la uuidV7 del documento
  6. Rastreabilidad retrógrada/progresiva - resoluble desde el asiento contable → comprobante → entrada de diario → uuidV7 y de vuelta

Compliance by Design: El formato no es algo posterior - es estructural. Esquema, V7GUID (Class), uuidV7 (Object) y Sidecar son campos obligatorios sin los cuales una entrada no se incorpora al repositorio.

Sin datetime/date redundante: La hora de registro está anclada en la propia uuidV7 (marca de tiempo de 48 bits, RFC 9562 §5.7). Una representación como string separada en esquemas JSON o datos JSON es redundante y no se mantiene en los hechos. La representación como string para la transferencia DTO/HTMX es responsabilidad del Harness, no de la capa de hechos.

El esquema JSON (Schema-First)

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart LR S["JSON-Schema
(vor Eintrag)"] S --> V["Schema-Validierung
(Pre-Commit-Hook)"] E["Tagebucheintrag
(JSON)"] E --> V V -->|gültig| C["Commit akzeptiert
+ uuidV7 generiert"] V -->|ungültig| R["Commit abgelehnt
Schema-Verstoß"] style S fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style V fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style E fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7 style C fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style R fill:#FDBA74,stroke:#C2410C,color:#0F1B33

El esquema existe antes de la entrada - se almacena en .gitcover/schemas/diary-entry-1.0.schema.json y está versionado. Una entrada sin referencia de esquema ($schema) es rechazada por el hook de pre-commit.

Esquema diary-entry-1.0.schema.json (simplificado)

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://gitcover.org/schemas/diary-entry-1.0.schema.json",
  "title": "GitCover Diary Entry v1.0",
  "type": "object",
  "required": [
    "$schema", "V7GUID", "docUid",
    "author", "role", "tenant", "sphere", "source"
  ],
  "properties": {
    "$schema": {
      "type": "string",
      "format": "uri",
      "const": "https://gitcover.org/schemas/diary-entry-1.0.schema.json"
    },
    "V7GUID": {
      "type": "string",
      "description": "Class Identifier - klassifiziert das Dokument/die Action aus .gitcover Registry (was/welcher Typ)"
    },
    "docUid": {
      "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}$",
      "description": "Object ID - RFC 9562 §5.7 UUIDv7, generiert aus vorgegebener Zeitmarke (nicht now()), 48-Bit-Zeitstempel in der GUID verankert"
    },
    "author": { "type": "string", "description": "Akteur-Platzhalter, z. B. E1" },
    "role": {
      "type": "string",
      "enum": ["GF", "Buchhalter", "Lohnverantwortlicher", "F&E-Leiter", "Administrator"]
    },
    "tenant": { "type": "string", "description": "Tenant-Platzhalter, z. B. ORG-1" },
    "sphere": {
      "type": "string",
      "enum": ["ideell", "vermögensverwaltend", "zweckbetrieblich", "wirtschaftlich", "n/a"]
    },
    "source": {
      "type": "string",
      "enum": ["E1", "StB", "Notar", "Bank", "DRV", "FA", "BA", "VBG", "KK", "auto"]
    },
    "source_sha256": {
      "type": "string",
      "pattern": "^[0-9a-f]{64}$",
      "description": "SHA-256 des referenzierten Belegs (eindeutige Beleg-ID)"
    },
    "tags": { "type": "array", "items": { "type": "string" } }
  }
}

Importante - sin campo datetime/date: La hora de registro está contenida en la uuidV7 (marca de tiempo de 48 bits, RFC 9562 §5.7). Una representación como string separada es redundante y no se mantiene en los hechos. La uuidV7 por sí sola es la DocID unívoca; la clave compuesta V7GUID:uuidV7 sirve para la organización del archivado y las consultas a la base de datos.

V7GUID - estructura y marca de tiempo de 48 bits

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart LR subgraph V7["V7GUID (128 Bit, RFC 9562 §5.7)"] direction LR T["timestamp_ms
Bits 0–47
48 Bit Unix-ms"] V["version
Bits 48–51
0111 (UUIDv7)"] R["rand_a
Bits 52–63
12 Bit"] VA["variant
Bits 64–65
10"] RB["rand_b
Bits 66–127
62 Bit"] end T --> DT["48-Bit-Zeitstempel
in uuidV7 verankert
(kein separates datetime-Feld)"] V7 --> ID["uuidV7-Feld
Object ID
(vorgegebene Zeitmarke)"] style T fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style V fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style R fill:#E5E7EB,stroke:#6B7280,color:#0F1B33 style VA fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style RB fill:#E5E7EB,stroke:#6B7280,color:#0F1B33 style DT fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7 style ID fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7
Rango de bits Ancho Contenido Fijado por RFC
0–47 48 bits Milisegundos Unix desde la época (1970-01-01T00:00:00Z) Sí (RFC 9562 §5.7)
48–51 4 bits Version = 0111 (UUIDv7) Sí
52–63 12 bits rand_a (aleatorio) No
64–65 2 bits Variant = 10 Sí
66–127 62 bits rand_b (aleatorio) No

Principio central: El componente de marca de tiempo de 48 bits (bits 0–47) es autoritativo para la hora de registro. Está anclado en la propia uuidV7 y no puede modificarse posteriormente sin invalidar la GUID. Un campo datetime separado no existe - el tiempo está en la uuidV7. La representación como string para DTO/HTMX es responsabilidad del Harness.

Extensión GitCover: jerarquía e IDs de proceso en los bits 66–127

La especificación V7GUID de GitCover (work/OSS/TOP/.gitcover/specs/v7guid/) amplía el componente aleatorio de 62 bits (bits 66–127) con una jerarquía de 6 niveles e IDs de proceso - esta es la extensión protegida por patente (DPMA Az. 10 2025 003 091.6):

Rango de bits Ancho Contenido
66–89 6×4 bits Jerarquía de 6 niveles (L1–L6, 4 bits cada uno)
90–97 8 bits ProcessTypeId (tipo de proceso)
98–105 8 bits GatewayId (punto de control)
106–113 8 bits Status (máscara de bits del ciclo de vida)
114–121 8 bits Kind (tipo de actividad)
122–127 6 bits VariantId (clase de variante)

Nota: Esta extensión no es imprescindible para la serie sobre el diario - cobra relevancia en ED02 (Tenant/Esfera/Rol) y en artículos posteriores (Harness). Aquí basta la base RFC 9562 con marca de tiempo de 48 bits.

Rutinas Helper: creación de V7GUID a partir de claves externas

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart LR F["Fremd-Key
(Zeitmarke in Daten
oder Dateiname)"] F --> H["Helper-Routine
UuidV7Gen"] H --> TS["48-Bit-Zeitstempel
extrahiert/konvertiert"] TS --> G["uuidV7 generiert
(Guid.CreateVersion7)"] G --> A["JSON-Artefakt
V7GUID + uuidV7"] A --> D["GoBD-Dokumentation
zeitnahe Erfassung
nachvollziehbar"] style F fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style H fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style TS fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style G fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style A fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7 style D fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33

La herramienta OSS de GitCover UuidV7Gen (work/OSS/TOP/tools/UuidV7Gen/) proporciona dos operaciones centrales:

gen - generar uuidV7 (guid)

# Erzeugt eine neue uuidV7 mit vorgegebener Zeitmarke (nicht now())
# Zeitmarke z. B. aus Dateiname "260815_0900" → ISO 2026-08-15T09:00:00Z
uuidv7gen gen "2026-08-15T09:00:00Z" "Tagebucheintrag-260815"

# Ausgabe: Label;UUID;ISO-Zeitstempel
# Tagebucheintrag-260815;019f2c6f-0900-7000-8000-000000000000;2026-08-15T09:00:00.000Z

decode - extraer la marca de tiempo de una uuidV7

# Extrahiert den 48-Bit-Zeitstempel aus einer uuidV7
uuidv7gen decode 019f2c6f-0900-7000-8000-000000000000

# Ausgabe: ISO-8601-Zeitstempel
# 2026-08-15T09:00:00.000Z

Clave externa → uuidV7

Las rutinas Helper permiten crear una clave uuidV7 válida a partir de una clave externa (p. ej., una marca de tiempo en el nombre de archivo como 260815_0900_Lohnabrechnung.pdf) - con marca de tiempo predefinida, no now():

  1. Parsear la clave externa - 260815_0900 → 2026-08-15T09:00:00Z
  2. Calcular la marca de tiempo de 48 bits - milisegundos Unix desde la época
  3. Generar la uuidV7 - Guid.CreateVersion7() con marca de tiempo explícita, el resto con aleatoriedad
  4. Clave compuesta - V7GUID (Class del Registry) : uuidV7 (Object con marca de tiempo)
  5. Documentación GoBD - registro contemporáneo anclado de forma trazable (sin campo datetime separado)

Referencia GoBD: El registro contemporáneo (GoBD Rz. 146) queda documentado criptográficamente mediante la marca de tiempo de 48 bits en la uuidV7 - no solo afirmado. La marca de tiempo es parte de la identidad, no modificable a posteriori.

Estructura del Sidecar (.v7g.md)

Cada comprobante recibe un Sidecar con dos referencias uuidV7:

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart TD D["Dokument
(PDF, XML, EML)"] D --> S["Sidecar .v7g.md"] S --> U1["uuidV7 (eigen)
Identität des Sidecars
+ 48-Bit-Erfassungszeit"] S --> U2["uuidV7 (fremd)
Identität des Dokuments
in v7g_taxonomy"] S --> SH["sha256
Eindeutige Beleg-ID"] S --> T["v7g_taxonomy
Klassifizierung
(Tenant, Sphäre, Kategorie)"] S --> O["obsolescence
status: active/superseded/obsolete"] style D fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7 style S fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style U1 fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style U2 fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style SH fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style T fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style O fill:#E5E7EB,stroke:#6B7280,color:#0F1B33

Ejemplo de Sidecar

{
  "$schema": "https://gitcover.org/schemas/v7g-sidecar-1.0.schema.json",
  "V7GUID": "<V7GUID-Class-aus-Registry>",
  "docUid": "019f2c6f-0900-7001-8000-000000000001",
  "sha256": "ab94670c0dd8499c27ef2feddd1d9c5925977d55a52150a6fca0f6567d2e5e79",
  "title": "260815_Lohnabrechnung.pdf",
  "original_filename": "260815_Lohnabrechnung.pdf",
  "locations": [
    {
      "unc_path": "./ORG-1/sources/lohn/260815_Lohnabrechnung.pdf",
      "from": "260815",
      "to": null,
      "note": "Primärspeicherort"
    }
  ],
  "v7g_taxonomy": [
    {
      "v7guid": "019f2c6f-0900-7000-8000-000000000000",
      "taxonomy": "ORG-1/Lohn",
      "valid_from": "260815",
      "valid_to": null,
      "note": "Tenant: ORG-1, Category: Lohn, Sphäre: ideell"
    }
  ],
  "gcpn": {
    "prima_nota_ref": null,
    "journal_entry_ref": null
  },
  "obsolescence": {
    "status": "active",
    "superseded_by": null,
    "superseded_at": null
  }
}

Clave compuesta V7GUID:uuidV7:

  • V7GUID (campo 1) - Class Identifier del Sidecar (clasificación según el Registry .gitcover: qué/qué tipo)
  • uuidV7 (campo 2) - Object ID del propio Sidecar, generada con marca de tiempo predefinida (¿cuándo se clasificó?), marca de tiempo de 48 bits anclada en la GUID
  • v7g_taxonomy[].v7guid - Object ID del documento clasificado (¿qué documento se clasifica?)
  • sha256 - identidad del documento original clasificado — la prueba de originalidad, siempre y en todas partes (incluso si el documento se desplaza: independiente de ruta/nombre, calculada a partir del contenido)

Así no solo queda identificado de forma unívoca el documento (mediante su SHA-256), sino también el acto de clasificación en sí - incluido el momento (en la uuidV7), quién clasificó y en qué rol. Sin campo datetime separado - el tiempo está en los valores uuidV7.

Rastreabilidad retrógrada y progresiva

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart LR subgraph RET["Retrograd (vom Buchungssatz rückwärts)"] direction LR B["Buchungssatz
(SKR04)"] B --> BE["Beleg
(SHA-256)"] BE --> DE["Tagebucheintrag
(uuidV7)"] DE --> V7["uuidV7
(48-Bit-Zeitstempel)"] end subgraph PRO["Progressiv (vom Beleg vorwärts)"] direction LR BE2["Beleg
(SHA-256)"] BE2 --> BS["Buchungssatz
(SKR04)"] BS --> GB["Grundbuch
(JSON)"] GB --> EB["Eröffnungsbilanz"] end style B fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style BE fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style DE fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style V7 fill:#0F1B33,stroke:#0F1B33,color:#FBFAF7 style BE2 fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style BS fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style GB fill:#DBEAFE,stroke:#1D4ED8,color:#0F1B33 style EB fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33
Dirección Inicio Resolución mediante Objetivo
Retrógrada Asiento contable (SKR04) source_sha256 → comprobante → uuidV7 Entrada de diario con marca de tiempo de 48 bits
Progresiva Comprobante (SHA-256) uuidV7 → entrada de diario → source_sha256 Asiento contable → Grundbuch → balance de apertura

Referencia GoBD: La rastreabilidad retrógrada corresponde a GoBD Rz. 146 (trazabilidad). La rastreabilidad progresiva corresponde a GoBD Rz. 147 (verificabilidad). Ambas están garantizadas by Design mediante uuidV7 + SHA-256 - no mediante referencias cruzadas manuales.

Registro de tiempo en el diario

Cada entrada de diario contiene una tabla de registro de tiempo:

Inicio Fin Horas Actividad Tenant Esfera Fuente
0900 1200 3,0 Datos maestros de cuenta de nómina ORG-1 ideell E1
1200 1400 2,0 I+D: especificación V7GUID ORG-1 ideell E1

Referencia FZul: El registro de tiempo es la base para los justificantes de horas FZul (ED28). La separación entre I+D y administración se realiza mediante el campo role (F&E-Leiter vs. GF) y las tags (["fzul", "forschung"] vs. ["verwaltung"]).

Qué entra en el repositorio Git - y qué no

%%{init: {'theme':'base','themeVariables':{'primaryColor':'#FBFAF7','primaryTextColor':'#0F1B33','primaryBorderColor':'#6B7280','lineColor':'#6B7280'}}}%% flowchart TD IN["Ins Git-Repo"] IN --> J["JSON-Artefakte
(Tagebucheinträge, Grundbuch)"] IN --> S["Sidecars .v7g.md"] IN --> B["Belege
(PDF, XML, EML)"] IN --> SC["Schemata"] IN --> D["Dictionaries"] OUT["Nicht im Git-Repo"] OUT --> P["Private Dokumente
(sofern nicht ausdrücklich eingeführt)"] OUT --> T["Temporäre Dateien"] OUT --> C["Credentials/Secrets"] style IN fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style J fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style S fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style B fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style SC fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style D fill:#D1FAE5,stroke:#0A7F5C,color:#0F1B33 style OUT fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style P fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style T fill:#FDBA74,stroke:#C2410C,color:#0F1B33 style C fill:#FDBA74,stroke:#C2410C,color:#0F1B33

Importante: Solo los artefactos estructurados (JSON, Sidecars, comprobantes, esquemas, diccionarios) se incorporan al repositorio Git. Los documentos privados nunca se incorporan al repositorio, a menos que hayan sido introducidos y clasificados explícitamente como artefacto. Este es un requisito de protección de datos y de cumplimiento (DSGVO, GoBD).

Apalancamiento de riesgo

Hoy (barato) Mañana (a prueba de auditoría) Riesgo mitigado
Artefacto JSON con V7GUID (Class) + uuidV7 (Object) Hora de registro anclada criptográficamente en uuidV7 Negación de la hora de registro
Validación de esquema (Pre-Commit) Solo entradas válidas entran en el repositorio Violación del esquema, campos obligatorios incompletos
Sidecar con clave compuesta V7GUID:uuidV7 Acto de clasificación trazable Negación de la clasificación
source_sha256 por entrada Rastreabilidad retrógrada Negación de la asignación del comprobante
Helper UuidV7Gen para claves externas Registro contemporáneo documentado (marca de tiempo en uuidV7) Infracción GoBD "no contemporáneo"

Requisitos del Harness (vista previa)

Derivable de ED03:

ID Requisito Prioridad
FA-1.1 Entradas de diario como artefactos JSON (Schema-First) MUST
FA-1.2 Clave compuesta V7GUID (Class) : uuidV7 (Object) - sin campo datetime/date separado; el tiempo en uuidV7 (RFC 9562 §5.7) MUST
FA-1.3 uuidV7 generada a partir de una marca de tiempo predefinida (no now()) mediante el Helper MUST
FA-2.1 SHA-256 como ID de comprobante MUST
FA-2.2 Obligación de Sidecar .v7g.md MUST
FA-2.3 Sidecar con clave compuesta V7GUID:uuidV7 (Class + Object) MUST
FA-2.12 Rastreabilidad retrógrada (asiento → comprobante → uuidV7) MUST
FA-2.13 Rastreabilidad progresiva (comprobante → asiento → Grundbuch) MUST
TA-1.5 Rutinas Helper para la creación de V7GUID a partir de claves externas (UuidV7Gen) MUST
TA-1.6 Requisitos temporales de GoBD documentados (marca de tiempo de 48 bits en uuidV7) MUST
TA-2.1 Pre-Commit: validación de esquema JSON MUST

La lista completa de requisitos en Harness-Anforderungen.md.

Fuentes

Topología de fuentes y enlaces de referencia CDN

Rol Lugar Propósito
Primary / SSoT git.gitcover.org/GCC Almacén canónico (firmado con GPG, versionado)
Public OSS Mirror / CDN codeberg.org/gitcover-commons Espejo de solo lectura; descubrimiento FLOSS
Community Hub github.com/gitcover-commons Issues & Discussions; referencia del código fuente en Codeberg

Nota: Esta asignación de fuentes, mirror y community hub refleja el estado actual y puede cambiar. Por favor, consulte la fuente canónica respectiva en gitcover.org para conocer el estado actual.