[Podpisane, Merkle-łańcuchowane transkrypcje; PROV-O JSON-LD; HMAC domyślnie z PQC opt-in]

Provenance i zgodność

Każda mutująca operacja na rekordzie evidence zapisuje podpisany, połączony Merkle wiersz w `provenance_records` w tej samej transakcji bazy danych co wpis audytowy. Partnerzy mogą pobrać pełną transkrypcję jako JSON lub PROV-O JSON-LD, zweryfikować ją server-side albo lokalnie z helperem bez sieci w SDK i przedstawić jako chain of custody w postępowaniach prawnych. HMAC-SHA256 domyślnie; ML-DSA-65 (post-kwantowy) opt-in przez istniejący adapter PQC.

Provenance i zgodność

Provenance łączone Merkle

Każdy wiersz w `provenance_records` niesie `parent_id` i `parent_hash`, więc transkrypcja tworzy łańcuch append-only per `evidence_id`. Pierwszy rekord obiektu evidence ma `parent_hash: null`; każda kolejna operacja (`evidence.create`, `evidence.update`, `redaction.apply`, `access.read`, zdarzenia emitowane przez joby itd.) hashuje kanoniczny JSON poprzedniego rekordu do swojego `parent_hash`. Transkrypcja jest następnie wiązana Merkle root nad uporządkowanymi rekordami i niezależnie podpisywana. Weryfikator przechodzi łańcuch wstecz do rekordu genesis, przelicza każde ogniwo i odrzuca transkrypcję, jeśli jakikolwiek `parent_hash` nie pasuje - to właśnie czyni zmianę nawet jednego bajtu wykrywalną end-to-end.

Algorytmy podpisu

Kanonikalizacja JSON zgodnie z RFC 8785 (JCS): payload podpisu per rekord to kanoniczny JSON `{tenant_id, evidence_id, parent_id, operation, actor_id, actor_kind, content_hash, parent_hash, signed_at}`. Domyślny algorytm to HMAC-SHA256 z sekretem per tenant wyprowadzonym z platformowego klucza master (`ARGUS_PROVENANCE_MASTER_KEY`) przez HKDF nad etykietą `argus.provenance.tenant.{tenant_id}`. ML-DSA-65 jest opt-in przez `?algorithm=ml-dsa-65` i reużywa istniejącego `pqc_adapter`; jego publiczne klucze weryfikacyjne są wystawione pod `/v1/.well-known/provenance-keys/ml-dsa-65`, dzięki czemu zewnętrzni audytorzy i weryfikatorzy offline mogą walidować podpisy bez kontaktowania się z platformą.

Formaty wyjściowe

`GET /v1/evidence/{evidence_id}/provenance` przyjmuje `?format=json` (domyślnie) i `?format=jsonld`. Forma JSON to wire-native reprezentacja używana przez endpoint weryfikacji oraz SDK. Forma JSON-LD emituje dokument PROV-O z `@context: https://www.w3.org/ns/prov` i standardowym mapowaniem pojęć: każdy rekord mapuje na `prov:Activity`, `prov:wasGeneratedBy` łączy aktywność z aktorem (`prov:Agent`), `prov:wasDerivedFrom` wiąże nowy stan evidence z poprzednim stanem (`prov:Entity`), a Merkle root jest wystawiany jako `prov:Bundle` najwyższego poziomu z dołączonym podpisem wiążącym jako atrybucją w stylu `prov:hadPrimarySource`. Wybierz JSON-LD, gdy musisz interoperować z toolingiem W3C PROV, katalogami evidence lub platformami legaltech, które już konsumują PROV-O.

Weryfikacja

Wspierane są dwie ścieżki weryfikacji. Server-side: `POST /v1/evidence/{evidence_id}/provenance/verify` z `{transcript: <obiekt transkrypcji>}` ponownie wykonuje kanonikalizację, przelicza każdy podpis rekordu, waliduje Merkle root i zwraca `{ valid, broken_links, checked_records, merkle_root_verified }`. Endpoint nigdy nie zwraca 4xx dla zmanipulowanej, lecz poprawnie sformowanej transkrypcji: 400 jest zarezerwowany dla wejścia malformed, a zmanipulowana transkrypcja zwraca 200 z `valid: false` i wypełnioną tablicą `broken_links`. Lokalnie/offline: SDK dostarczają `evidence.verifyProvenanceLocal(transcript)` (TypeScript) i `evidence.verify_provenance_local(transcript)` (Python). Helper offline wykonuje te same kontrole bez round-tripu sieciowego; dla ML-DSA-65 odczytuje publiczny klucz weryfikacyjny z `/v1/.well-known/provenance-keys/ml-dsa-65`. Weryfikacja HMAC pozostaje server-side, ponieważ sekret per tenant nigdy nie jest publikowany.

Słownik PROV-O i interoperacyjność prawna

PROV-O to W3C Recommendation do reprezentowania provenance: kto zrobił co, kiedy i na podstawie jakiego wcześniejszego stanu. Zmapowanie naszych rekordów na `prov:Activity`, `prov:Agent` i `prov:Entity` pozwala audytorowi wyznaczonemu przez sąd, regulatorowi lub partnerowi wczytać transkrypcję standardowym toolingiem zamiast parserem specyficznym dla Knogina. Forma JSON-LD utrzymuje wszystkie pola specyficzne dla Argusa (`tenant_id`, `signature_alg`, `merkle_root`, `trace_id`, `job_id`) w przestrzeni nazw `argus:` obok terminów PROV-O, więc audytor widzi najpierw graf standardowy, a dopiero potem detale implementacyjne. Ta separacja ma znaczenie dla dopuszczalności prawnej: audytor może rozumować o łańcuchu w języku standardu W3C, a następnie zagłębić się w pola przestrzeni `argus:`, gdy pytanie dowodowe wymaga kontekstu specyficznego dla platformy.

Wykorzystanie w postępowaniach prawnych

Transkrypcja jest zaprojektowana tak, by zaliczyć test chain of custody. Każdy rekord nazywa aktora (`actor_id`, `actor_kind` z `user`, `service`, `job`, `system`), operację, content hash evidence w danym momencie, poprzedni rekord, z którego się wywodzi, oraz `signed_at` zegara ściennego pobrany z transakcji bazodanowej. Transkrypcja niesie ten sam `trace_id`, który płynie przez powierzchnię observability G2, oraz, gdy ma zastosowanie, `job_id` z G3 - dzięki czemu śledczy może skorelować wpis provenance z otaczającą aktywnością systemową, która go wytworzyła. Integralność kryptograficzna (HMAC lub ML-DSA-65) plus Merkle root oznaczają, że pojedynczy zmieniony bajt unieważnia transkrypcję przy weryfikacji. ML-DSA-65 to rekomendowany algorytm dla pakietów evidence o wysokim poziomie pewności, ponieważ jego publiczne klucze weryfikacyjne czynią transkrypcję niezależnie weryfikowalną lata po otwarciu sprawy, nawet jeśli Knogin nie jest już depozytariuszem.

Izolacja per tenant

Każdy odczyt filtruje na `tenant_id` ORAZ `organization_id` z bearer tokena. Dostępy cross-tenant zwracają 404 (nigdy 403), więc istnienie łańcucha provenance w innym tenancie nigdy nie jest ujawniane. SecrecyLevel evidence ma zastosowanie: operator poniżej poziomu poufności evidence nie może odczytać jej provenance nawet w obrębie własnego tenanta. Sekret HMAC jest per tenant (HKDF-wyprowadzony z platformowego klucza master nad etykietą `argus.provenance.tenant.{tenant_id}`), więc transkrypcja podpisana dla jednego tenanta nie może być rewalidowana przeciwko innemu. Oba endpointy zapisują wpis audytowy `provenance.read` z `resource_id = evidence_id` oraz `metadata = { record_count, format, algorithm, valid }`, więc sam akt pobrania lub weryfikacji transkrypcji jest również zapisywany w ścieżce audytu.

Wspierane algorytmy

HMAC-SHA256 (domyślny, tenant-scoped) oraz ML-DSA-65 (post-kwantowy, FIPS 204; opt-in przez ?algorithm=ml-dsa-65).

Pobranie transkrypcji 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"

Pobranie transkrypcji 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
#     }
#   ]
# }

Weryfikacja transkrypcji po stronie serwera

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

Weryfikacja 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");

Weryfikacja 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}")

Potrzebujesz provenance dla regulowanego workflow?

Otwórz referencję API dla publicznego kontraktu lub skontaktuj się z Knogin, jeśli potrzebujesz kluczy ML-DSA-65 udostępnionych dla zewnętrznego audytora.