[Trascrizioni firmate e Merkle-chained; PROV-O JSON-LD; HMAC default con PQC opt-in]

Provenance e conformità

Ogni operazione mutante su un record di evidence emette una riga firmata e Merkle-chained in `provenance_records`, all’interno della stessa transazione di database della voce di audit. I partner possono recuperare la trascrizione completa come JSON o PROV-O JSON-LD, verificarla server-side o localmente con un helper senza rete nell’SDK, e presentarla come catena di custodia per procedimenti legali. HMAC-SHA256 di default; ML-DSA-65 (post-quantum) opt-in tramite l’adapter PQC esistente.

Provenance e conformità

Provenance Merkle-chained

Ogni riga in `provenance_records` porta `parent_id` e `parent_hash`, così la trascrizione forma una catena append-only per `evidence_id`. Il primo record di un oggetto evidence ha `parent_hash: null`; ogni operazione successiva (`evidence.create`, `evidence.update`, `redaction.apply`, `access.read`, eventi emessi da job, e così via) hasha il JSON canonico del record precedente nel proprio `parent_hash`. La trascrizione viene poi vincolata da una Merkle root sui record ordinati e firmata in modo indipendente. Un verificatore percorre la catena fino al record genesi, ricomputa ogni anello e rifiuta la trascrizione se un `parent_hash` non corrisponde - è ciò che rende rilevabile end-to-end un’alterazione di un singolo byte.

Algoritmi di firma

La canonicalizzazione JSON segue RFC 8785 (JCS): il payload di firma per record è il JSON canonico di `{tenant_id, evidence_id, parent_id, operation, actor_id, actor_kind, content_hash, parent_hash, signed_at}`. L’algoritmo di default è HMAC-SHA256 con un secret per tenant derivato dalla chiave master di piattaforma (`ARGUS_PROVENANCE_MASTER_KEY`) tramite HKDF sull’etichetta `argus.provenance.tenant.{tenant_id}`. ML-DSA-65 è opt-in tramite `?algorithm=ml-dsa-65` e riusa l’`pqc_adapter` esistente; le sue chiavi pubbliche di verifica sono esposte su `/v1/.well-known/provenance-keys/ml-dsa-65` così auditor esterni e verificatori offline possono validare le firme senza contattare la piattaforma.

Formati di output

`GET /v1/evidence/{evidence_id}/provenance` accetta `?format=json` (default) e `?format=jsonld`. La forma JSON è la rappresentazione wire-native usata dall’endpoint di verifica e dagli SDK. La forma JSON-LD emette un documento PROV-O con `@context: https://www.w3.org/ns/prov` e mappatura standard dei termini: ogni record mappa su un `prov:Activity`, `prov:wasGeneratedBy` collega l’attività al suo attore (`prov:Agent`), `prov:wasDerivedFrom` lega il nuovo stato dell’evidence allo stato precedente (`prov:Entity`), e la Merkle root è esposta come `prov:Bundle` di top level con la firma di binding allegata come attribuzione stile `prov:hadPrimarySource`. Scegli JSON-LD quando devi interoperare con tooling W3C PROV, cataloghi di evidence o piattaforme legaltech che già consumano PROV-O.

Verifica

Sono supportati due percorsi di verifica. Server-side: `POST /v1/evidence/{evidence_id}/provenance/verify` con `{transcript: <oggetto trascrizione>}` rilancia la canonicalizzazione, ricomputa ogni firma per record, valida la Merkle root e restituisce `{ valid, broken_links, checked_records, merkle_root_verified }`. L’endpoint non restituisce mai 4xx per una trascrizione manipolata ma ben formata: 400 è riservato all’input malformato, e una trascrizione manipolata restituisce 200 con `valid: false` e un array `broken_links` popolato. Local/offline: gli SDK forniscono `evidence.verifyProvenanceLocal(transcript)` (TypeScript) ed `evidence.verify_provenance_local(transcript)` (Python). L’helper offline esegue le stesse verifiche senza round-trip di rete; per ML-DSA-65 legge la chiave pubblica di verifica da `/v1/.well-known/provenance-keys/ml-dsa-65`. La verifica HMAC resta server-side perché il secret per tenant non è mai pubblicato.

Vocabolario PROV-O e interoperabilità legale

PROV-O è la W3C Recommendation per rappresentare la provenance: chi ha fatto cosa, quando, e su quale stato precedente. Mappare i nostri record su `prov:Activity`, `prov:Agent` e `prov:Entity` permette a un auditor nominato dal tribunale, a un regolatore o a un partner di ingerire una trascrizione con tooling standard invece di un parser specifico Knogin. La forma JSON-LD mantiene tutti i campi specifici di Argus (`tenant_id`, `signature_alg`, `merkle_root`, `trace_id`, `job_id`) sotto il namespace `argus:` accanto ai termini PROV-O, così l’auditor vede prima il grafo standard e poi i dettagli implementativi. Questa separazione conta per l’ammissibilità legale: l’auditor può ragionare sulla catena nel linguaggio dello standard W3C e poi scendere nei campi del namespace `argus:` quando una domanda probatoria richiede contesto specifico della piattaforma.

Uso in procedimenti legali

La trascrizione è progettata per soddisfare un test di chain of custody. Ogni record nomina l’attore (`actor_id`, `actor_kind` tra `user`, `service`, `job`, `system`), l’operazione, il content hash dell’evidence in quel punto, il record precedente da cui deriva, e un `signed_at` da orologio reale preso dalla transazione di database. La trascrizione porta lo stesso `trace_id` che fluisce attraverso la superficie di observability di G2 e, quando applicabile, il `job_id` di G3 - così un investigatore può correlare una voce di provenance con l’attività di sistema che l’ha prodotta. L’integrità crittografica (HMAC o ML-DSA-65) più la Merkle root significano che un singolo byte alterato invalida la trascrizione in fase di verifica. ML-DSA-65 è l’algoritmo raccomandato per pacchetti di evidence ad alta garanzia perché le sue chiavi pubbliche di verifica rendono la trascrizione verificabile in modo indipendente anni dopo l’apertura del caso, anche se Knogin non è più il custode.

Isolamento per tenant

Ogni lettura filtra su `tenant_id` E `organization_id` dal bearer token. Gli accessi cross-tenant restituiscono 404 (mai 403), così l’esistenza di una catena di provenance in un altro tenant non viene mai rivelata. Il SecrecyLevel dell’evidence sottostante si applica: un operatore al di sotto del livello di segretezza dell’evidence non può leggerne la provenance neppure all’interno del proprio tenant. Il secret HMAC è per tenant (derivato via HKDF dalla chiave master di piattaforma sull’etichetta `argus.provenance.tenant.{tenant_id}`), così una trascrizione firmata per un tenant non può essere rivalidata contro un altro. Entrambi gli endpoint emettono una voce di audit `provenance.read` con `resource_id = evidence_id` e `metadata = { record_count, format, algorithm, valid }`, così l’atto di recuperare o verificare una trascrizione viene tracciato nell’audit trail.

Algoritmi supportati

HMAC-SHA256 (default, tenant-scoped) e ML-DSA-65 (post-quantum, FIPS 204; opt-in via ?algorithm=ml-dsa-65).

Recuperare una trascrizione 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"

Recuperare una trascrizione 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
#     }
#   ]
# }

Verificare una trascrizione server-side

# 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
# }

Verifica offline (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");

Verifica offline (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}")

Hai bisogno di provenance per un workflow regolamentato?

Apri la reference API per il contratto pubblico o contatta Knogin se hai bisogno di chiavi ML-DSA-65 provisionate per un auditor esterno.