[Transcriptions signées et chaînées par Merkle ; PROV-O JSON-LD ; HMAC par défaut avec PQC en opt-in]

Provenance et conformité

Chaque opération mutante sur un enregistrement de preuve émet une ligne signée et chaînée par Merkle dans `provenance_records`, dans la même transaction de base de données que l’entrée d’audit. Les partenaires peuvent récupérer la transcription complète en JSON ou en PROV-O JSON-LD, la vérifier côté serveur ou localement avec un helper sans réseau dans le SDK, et la présenter comme chaîne de garde pour des procédures judiciaires. HMAC-SHA256 par défaut ; ML-DSA-65 (post-quantique) en opt-in via l’adaptateur PQC existant.

Provenance et conformité

Provenance chaînée par Merkle

Chaque ligne de `provenance_records` porte `parent_id` et `parent_hash`, de sorte que la transcription forme une chaîne append-only par `evidence_id`. Le premier enregistrement d’un objet de preuve a `parent_hash: null` ; chaque opération suivante (`evidence.create`, `evidence.update`, `redaction.apply`, `access.read`, événements émis par des jobs, etc.) hashe le JSON canonique de l’enregistrement précédent dans son `parent_hash`. La transcription est ensuite liée par un Merkle root sur les enregistrements ordonnés et signée indépendamment. Un vérificateur remonte la chaîne jusqu’à l’enregistrement génésis, recalcule chaque maillon et rejette la transcription si un `parent_hash` ne correspond pas - c’est ce qui rend une altération d’un seul octet détectable de bout en bout.

Algorithmes de signature

La canonicalisation JSON suit RFC 8785 (JCS) : le payload de signature par enregistrement est le JSON canonique de `{tenant_id, evidence_id, parent_id, operation, actor_id, actor_kind, content_hash, parent_hash, signed_at}`. L’algorithme par défaut est HMAC-SHA256 avec un secret par tenant dérivé de la clé maître de plateforme (`ARGUS_PROVENANCE_MASTER_KEY`) via HKDF sur le label `argus.provenance.tenant.{tenant_id}`. ML-DSA-65 est en opt-in via `?algorithm=ml-dsa-65` et réutilise le `pqc_adapter` existant ; ses clés publiques de vérification sont exposées sur `/v1/.well-known/provenance-keys/ml-dsa-65` afin que des auditeurs externes et des vérificateurs hors-ligne puissent valider les signatures sans contacter la plateforme.

Formats de sortie

`GET /v1/evidence/{evidence_id}/provenance` accepte `?format=json` (par défaut) et `?format=jsonld`. La forme JSON est la représentation native sur le wire utilisée par l’endpoint de vérification et les SDK. La forme JSON-LD émet un document PROV-O avec `@context: https://www.w3.org/ns/prov` et un mappage standard des termes : chaque enregistrement mappe sur un `prov:Activity`, `prov:wasGeneratedBy` lie l’activité à son acteur (`prov:Agent`), `prov:wasDerivedFrom` relie le nouvel état de la preuve à l’état précédent (`prov:Entity`), et le Merkle root est exposé comme un `prov:Bundle` de niveau supérieur avec la signature de liaison attachée comme attribution de style `prov:hadPrimarySource`. Choisissez JSON-LD lorsque vous devez interopérer avec un outillage W3C PROV, des catalogues de preuves ou des plateformes legaltech qui consomment déjà PROV-O.

Vérification

Deux chemins de vérification sont supportés. Côté serveur : `POST /v1/evidence/{evidence_id}/provenance/verify` avec `{transcript: <objet transcription>}` réexécute la canonicalisation, recalcule chaque signature d’enregistrement, valide le Merkle root et renvoie `{ valid, broken_links, checked_records, merkle_root_verified }`. L’endpoint ne renvoie jamais 4xx pour une transcription altérée mais bien formée : 400 est réservé à une entrée malformée, et une transcription altérée renvoie 200 avec `valid: false` et un tableau `broken_links` peuplé. Local/hors-ligne : les SDK livrent `evidence.verifyProvenanceLocal(transcript)` (TypeScript) et `evidence.verify_provenance_local(transcript)` (Python). Le helper hors-ligne effectue les mêmes vérifications sans round-trip réseau ; pour ML-DSA-65 il lit la clé publique de vérification depuis `/v1/.well-known/provenance-keys/ml-dsa-65`. La vérification HMAC reste côté serveur parce que le secret par tenant n’est jamais publié.

Vocabulaire PROV-O et interopérabilité légale

PROV-O est la W3C Recommendation pour représenter la provenance : qui a fait quoi, quand, et sur la base de quel état préalable. Mapper nos enregistrements sur `prov:Activity`, `prov:Agent` et `prov:Entity` permet à un auditeur désigné par le tribunal, à un régulateur ou à un partenaire d’ingérer une transcription avec un outillage standard plutôt qu’un parser spécifique à Knogin. La forme JSON-LD conserve tous les champs spécifiques à Argus (`tenant_id`, `signature_alg`, `merkle_root`, `trace_id`, `job_id`) sous le namespace `argus:` aux côtés des termes PROV-O, de sorte que l’auditeur voit d’abord le graphe standard et ensuite les détails d’implémentation. Cette séparation compte pour l’admissibilité légale : l’auditeur peut raisonner sur la chaîne dans le langage du standard W3C, puis explorer les champs du namespace `argus:` quand une question probatoire exige un contexte spécifique à la plateforme.

Usage dans les procédures judiciaires

La transcription est conçue pour satisfaire un test de chaîne de garde. Chaque enregistrement nomme l’acteur (`actor_id`, `actor_kind` parmi `user`, `service`, `job`, `system`), l’opération, le content hash de la preuve à cet instant, l’enregistrement précédent dont il découle, et un `signed_at` d’horloge murale pris dans la transaction de base de données. La transcription porte le même `trace_id` que celui qui circule sur la surface d’observabilité G2 et, le cas échéant, le `job_id` de G3 - un enquêteur peut donc corréler une entrée de provenance avec l’activité système qui l’a produite. L’intégrité cryptographique (HMAC ou ML-DSA-65) plus le Merkle root signifient qu’un seul octet altéré invalide la transcription à la vérification. ML-DSA-65 est l’algorithme recommandé pour les paquets de preuve à forte garantie parce que ses clés publiques de vérification rendent la transcription vérifiable de manière indépendante des années après l’ouverture d’un dossier, même si Knogin n’est plus le dépositaire.

Isolation par tenant

Toute lecture filtre sur `tenant_id` ET `organization_id` du bearer token. Les accès inter-tenants renvoient 404 (jamais 403), donc l’existence d’une chaîne de provenance dans un autre tenant n’est jamais divulguée. Le SecrecyLevel de la preuve sous-jacente s’applique : un opérateur sous le niveau de secret de la preuve ne peut lire sa provenance, même au sein de son propre tenant. Le secret HMAC est par tenant (dérivé par HKDF de la clé maître de plateforme sur le label `argus.provenance.tenant.{tenant_id}`), de sorte qu’une transcription signée pour un tenant ne peut être revalidée pour un autre. Les deux endpoints émettent une entrée d’audit `provenance.read` avec `resource_id = evidence_id` et `metadata = { record_count, format, algorithm, valid }`, donc l’acte de récupérer ou de vérifier une transcription est lui-même tracé.

Algorithmes pris en charge

HMAC-SHA256 (par défaut, scoped par tenant) et ML-DSA-65 (post-quantique, FIPS 204 ; opt-in via ?algorithm=ml-dsa-65).

Récupérer une transcription JSON

# Fetch a JSON transcript (default algorithm: HMAC-SHA256)
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance" \
  -H "Authorization: Bearer $TOKEN"

# 200 OK
# {
#   "evidence_id": "ev_abc123",
#   "tenant_id": "tnt_123",
#   "records": [
#     {
#       "id": "01HFXY...",
#       "operation": "evidence.create",
#       "actor_id": "user_42",
#       "actor_kind": "user",
#       "content_hash": "sha256:b94d...",
#       "parent_hash": null,
#       "signature": "<hex>",
#       "signature_alg": "hmac-sha256",
#       "signed_at": "2026-05-08T10:00:00Z",
#       "trace_id": "0af7651916cd43dd8448eb211c80319c",
#       "job_id": null
#     }
#   ],
#   "merkle_root": "sha256:...",
#   "root_signature": "<hex>",
#   "root_signature_alg": "hmac-sha256",
#   "format": "json",
#   "version": "2026.4"
# }

# Same transcript, post-quantum signatures:
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance?algorithm=ml-dsa-65" \
  -H "Authorization: Bearer $TOKEN"

Récupérer une transcription PROV-O JSON-LD

# Fetch the same transcript as PROV-O JSON-LD
curl "https://api.knogin.com/v1/evidence/ev_abc123/provenance?format=jsonld" \
  -H "Authorization: Bearer $TOKEN"

# 200 OK
# {
#   "@context": [
#     "https://www.w3.org/ns/prov",
#     { "argus": "https://schema.knogin.com/argus/v1#" }
#   ],
#   "@type": "prov:Bundle",
#   "@id": "argus:evidence/ev_abc123/provenance",
#   "argus:tenant_id": "tnt_123",
#   "argus:merkle_root": "sha256:...",
#   "argus:root_signature": "<hex>",
#   "argus:root_signature_alg": "hmac-sha256",
#   "@graph": [
#     {
#       "@id": "argus:provenance/01HFXY",
#       "@type": "prov:Activity",
#       "prov:startedAtTime": "2026-05-08T10:00:00Z",
#       "argus:operation": "evidence.create",
#       "argus:content_hash": "sha256:b94d...",
#       "argus:signature": "<hex>",
#       "argus:signature_alg": "hmac-sha256",
#       "argus:trace_id": "0af7651916cd43dd8448eb211c80319c",
#       "prov:wasGeneratedBy": { "@id": "argus:agent/user_42", "@type": "prov:Agent" },
#       "prov:wasDerivedFrom": null
#     }
#   ]
# }

Vérifier une transcription côté serveur

# Server-side verification of a transcript fetched earlier.
curl -X POST https://api.knogin.com/v1/evidence/ev_abc123/provenance/verify \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"transcript\": $(cat transcript.json)}"

# 200 OK (intact)
# {
#   "valid": true,
#   "evidence_id": "ev_abc123",
#   "broken_links": [],
#   "checked_records": 5,
#   "merkle_root_verified": true
# }

# 200 OK (tampered: a single byte changed in record 01HFXY)
# {
#   "valid": false,
#   "evidence_id": "ev_abc123",
#   "broken_links": [
#     { "record_id": "01HFXY...", "reason": "content_hash_mismatch" }
#   ],
#   "checked_records": 5,
#   "merkle_root_verified": false
# }

Vérification hors-ligne (SDK TypeScript)

// Offline verification with @argus/client (TypeScript).
// HMAC verification stays server-side; ML-DSA-65 transcripts can be verified
// locally using the public verify keys from /v1/.well-known/provenance-keys.

import { ArgusClient } from "@argus/client";

const argus = new ArgusClient({
  baseUrl: "https://api.knogin.com",
  token: process.env.ARGUS_TOKEN!,
});

const transcript = await argus.evidence.getProvenance("ev_abc123", {
  format: "json",
  algorithm: "ml-dsa-65",
});

// No network round-trip; the helper fetches /v1/.well-known/provenance-keys
// once per kid, caches the result, and verifies signatures + Merkle root.
const result = await argus.evidence.verifyProvenanceLocal(transcript);

if (!result.valid) {
  console.error("Tampered transcript", result.brokenLinks);
  process.exit(1);
}
console.log("Verified", result.checkedRecords, "records");

Vérification hors-ligne (SDK Python)

# Offline verification with argus-client (Python).
# Same shape as the TypeScript helper; suitable for evidence packages
# stored in court archives long after the case opens.

from argus_client import ArgusClient

argus = ArgusClient(base_url="https://api.knogin.com", token=os.environ["ARGUS_TOKEN"])

transcript = argus.evidence.get_provenance(
    "ev_abc123",
    format="json",
    algorithm="ml-dsa-65",
)

result = argus.evidence.verify_provenance_local(transcript)

if not result.valid:
    raise SystemExit(f"Tampered transcript: {result.broken_links}")

print(f"Verified {result.checked_records} records; merkle_root_verified={result.merkle_root_verified}")

Besoin de provenance pour un workflow réglementé ?

Ouvrez la référence API pour le contrat public ou contactez Knogin si vous avez besoin de clés ML-DSA-65 provisionnées pour un auditeur externe.