Provenance & Cryptographic Attribution

How Xudanu answers "who wrote this?" at the character level — Ed25519 span signing, the tamper-evident attribution log, derivation chains, attestation reports, and federation cluster consensus with W3C-PROV export.

1. The Problem: Trust in a Collaborative System 2. The Three Layers of Provenance 3. Cryptographic Prerequisites 4. The Provenance Struct: Who, When, Where 5. Content Fingerprints: What Was Signed 6. sign_span: Signing a Run of Characters 7. verify_span_provenance: Checking a Signature 8. Domain Separation: Why It Matters 9. SpanProvenance: Positions + Signature 10. ElementProvenance: Rich Author Metadata 11. Author Types: Human, LLM, Historical 12. The Materialization Pipeline 13. Transclusion Attribution: Following Content Across Documents 14. Historical Attestation: Signing for Deceased Authors 15. The Attribution Log: Tamper-Evident Audit Trail 16. The Chained Hash: How Tampering Is Detected 17. Provenance Ancestry: Walking the Derivation Chain 18. Attestation Reports: Exportable Proof 19. Federation: Cross-Server Signatures 20. Cluster Consensus: Unanimous, Majority, Supermajority 21. W3C PROV-JSON Export 22. The Wire Protocol 23. Source Code Map

1. The Problem: Trust in a Collaborative System

In any collaborative document system, the question "who wrote this?" is fundamental. In a wiki, you trust the server. In Git, you trust the commit signature. In Google Docs, you trust... whoever has the account.

Xudanu makes a stronger guarantee: every run of characters is cryptographically signed by its author's Ed25519 key. Not just "user X edited this page" but "user X's key signed this exact sequence of characters at this timestamp on this server." The signature covers the content fingerprint, so changing even one character invalidates the signature.

This matters for:

2. The Three Layers of Provenance

SIGNING LAYER AUDIT LAYER FEDERATION LAYER Provenance author_pk + signature + timestamp + server_id (edition/provenance.rs) SpanProvenance start + end positions + Provenance (edition/provenance.rs) ElementProvenance rich metadata author type, LLM model (edition/provenance.rs) Attribution Log SHA-256 chained append-only log (transport/attribution_log.rs) Attestation Report JSON export with chain of custody (server.rs: generate_attestation_report) CrossServerSignature peer server vouches for provenance ClusterConsensus unanimous / majority / super W3C PROV-JSON Bundle standards-compliant export

Three layers: signing (purple) creates the cryptographic proof, audit (amber) makes it tamper-evident, federation (cyan) extends trust across servers.

3. Cryptographic Prerequisites

Xudanu's provenance system uses three cryptographic building blocks:

PrimitiveAlgorithmUsed for
SigningEd25519Author signs content fingerprint; 64-byte signature, 32-byte key
Content hashBLAKE3Fingerprint of each RangeElement; 32-byte hash
Chain hashSHA-256Tamper-evident log chaining; each entry links to the previous
Why Ed25519? It's fast (sign in microseconds), compact (64-byte signatures), and deterministic (same key + message always produces the same signature). No random nonce means no nonce-reuse vulnerability. Each club (user) gets an Ed25519 signing key when they log in.

4. The Provenance Struct: Who, When, Where

The fundamental unit of attribution is the Provenance struct:

pub struct Provenance {
    pub author_public_key: [u8; 32],  // WHO: Ed25519 verifying key
    pub signature: [u8; 64],          // PROOF: Ed25519 signature
    pub timestamp: u64,               // WHEN: Unix epoch seconds
    pub server_id: [u8; 32],          // WHERE: which server witnessed the signing
}

This is intentionally minimal. It contains everything needed to independently verify attribution without trusting the server, and nothing more. The author_public_key identifies the signer; the signature proves they signed something; the timestamp and server_id are part of the signed payload so they can't be forged after the fact.

5. Content Fingerprints: What Was Signed

Before signing, the content is reduced to a 32-byte BLAKE3 fingerprint. Each RangeElement in the O-tree has a content_fingerprint() method that hashes its text content. A span fingerprint combines multiple element fingerprints:

fn compute_span_fingerprint(fingerprints: &[[u8; 32]]) -> [u8; 32] {
    let mut hasher = Hasher::new();
    hasher.update(PROVENANCE_DOMAIN);  // domain separation
    for fp in fingerprints {
        hasher.update(fp);
    }
    hasher.finalize().into()
}
Key property: The span fingerprint covers the exact sequence of elements. Adding, removing, or reordering elements produces a different fingerprint. Changing a single character changes its element fingerprint, which changes the span fingerprint, which invalidates the signature.

6. sign_span: Signing a Run of Characters

The sign_span function is the core signing operation:

pub fn sign_span(
    signing_key: &SigningKey,          // author's Ed25519 private key
    element_fingerprints: &[[u8; 32]],  // one per character/element in the span
    timestamp: u64,
    server_id: &[u8; 32],
) -> Provenance {
    // Step 1: Hash all element fingerprints into one span fingerprint
    let span_fp = compute_span_fingerprint(element_fingerprints);

    // Step 2: Build the signing payload (domain || span_fp || pub_key || ts || server)
    let payload = compute_signing_payload(
        &span_fp,
        &signing_key.verifying_key().to_bytes(),
        timestamp,
        server_id,
    );

    // Step 3: Ed25519 sign the payload
    let signature = sign_bytes(signing_key, &payload);

    Provenance {
        author_public_key: signing_key.verifying_key().to_bytes(),
        signature: signature.to_bytes(),
        timestamp,
        server_id: *server_id,
    }
}
H e l l o BLAKE3 fp_1 fp_2 fp_3 fp_4 fp_5 combine with BLAKE3 span_fp = [32 bytes] domain || span_fp || author_pub || timestamp || server_id signing payload (32 bytes after BLAKE3) Ed25519 sign Provenance { pk, sig, ts, srv } Verify Later 1. Recompute span_fp from content 2. Recompute signing payload 3. Ed25519 verify(pub_key, payload, sig)

Signing pipeline: each character is BLAKE3-fingerprinted, combined into a span fingerprint, mixed with metadata, and Ed25519-signed.

7. verify_span_provenance: Checking a Signature

Verification reconstructs the payload and checks the Ed25519 signature:

pub fn verify_span_provenance(
    provenance: &Provenance,
    element_fingerprints: &[[u8; 32]],
) -> bool {
    let span_fp = compute_span_fingerprint(element_fingerprints);
    verify_span_provenance_with_span_fp(provenance, &span_fp)
}

pub fn verify_span_provenance_with_span_fp(
    provenance: &Provenance,
    span_fingerprint: &[u8; 32],
) -> bool {
    let verifying_key = VerifyingKey::from_bytes(&provenance.author_public_key);
    let payload = compute_signing_payload(
        span_fingerprint,
        &provenance.author_public_key,
        provenance.timestamp,
        &provenance.server_id,
    );
    let signature = Signature::from_bytes(&provenance.signature);
    verify_signature(&verifying_key, &payload, &signature).is_ok()
}

The test suite verifies that the following attacks all fail:

8. Domain Separation: Why It Matters

Every signing function begins with a domain separation tag:

const PROVENANCE_DOMAIN:           &[u8] = b"xudanu/v1/provenance";
const ELEMENT_PROVENANCE_DOMAIN:   &[u8] = b"xudanu/v1/element-provenance";
const HISTORICAL_ATTESTATION_DOMAIN: &[u8] = b"xudanu/v1/historical-attestation";
const FEDERATION_PROVENANCE_DOMAIN:  &[u8] = b"xudanu/v1/federation-provenance";
Why? Without domain separation, a signature intended for one purpose could be replayed in another. For example, a span provenance signature could be presented as a federation attestation. Domain separation ensures the BLAKE3 payload is always different across signing contexts, so a signature from one domain cannot be reused in another.

9. SpanProvenance: Positions + Signature

SpanProvenance attaches position information to a Provenance, saying "this signature covers characters from position start to end":

pub struct SpanProvenance {
    pub start: i64,            // first position (inclusive)
    pub end: i64,              // last position + 1 (exclusive)
    pub provenance: Provenance // who signed it
}

An Edition stores a Vec<SpanProvenance> — a list of non-overlapping runs, each signed by its author. When you query attribution, you get back the spans that cover the character range you asked about.

10. ElementProvenance: Rich Author Metadata

At the element (per-character) level, Xudanu tracks richer metadata:

pub struct ElementProvenance {
    pub author_public_key: [u8; 32],
    pub author_display_name: String,
    pub author_club_id: BeId,
    pub timestamp: u64,
    pub author_type: AuthorType,
    pub llm_model: Option<String>,           // if LLM-generated
    pub historical_author_id: Option<BeId>,    // if imported historical text
    pub source_work_id: Option<BeId>,           // if transcluded
    pub transcluded_by: Option<TransclusionInfo>,
    pub derived_by: Option<DerivationInfo>,
}

This metadata is stored per-element in the O-tree and used during materialization to build span-level provenance. The transcluded_by and derived_by fields trace content back through transclusion and merge operations.

11. Author Types: Human, LLM, Historical

Xudanu distinguishes three kinds of author, each with different trust semantics:

TypeSigned byUse caseDisplay
HumanAuthor's Ed25519 keyNormal collaborative editingName + colored avatar
LlmServer's key (on behalf of LLM)AI-assisted narration, auto-title, writing feedbackName + "AI" badge + model name
HistoricalServer's key (historical attestation)Imported public-domain texts (Project Gutenberg, etc.)Author name + "historical" badge
The signing key matters. For Human content, the author's own key signs. For LLM content, the server's key signs (because the LLM doesn't have its own Ed25519 key). For Historical content, the server signs a historical_attestation with a different domain tag that includes the historical author's ID. The build_edition_provenance function in otree_crdt.rs groups adjacent elements by author and type, signing each run as a single span.

12. The Materialization Pipeline

When a CRDT document is materialized into an Edition for storage or query, the server walks the O-tree and builds span provenance:

// otree_crdt.rs: build_edition_provenance (simplified)
fn build_edition_provenance(edition, fallback_key, server_id, timestamp, author_keys) {
    let entries = edition.all_entries();

    // Check if elements have per-element provenance
    let has_element_prov = entries.iter().any(|(_, c)| c.provenance.is_some());

    if !has_element_prov {
        // No per-element provenance: sign the whole edition as one span
        return vec![SpanProvenance {
            start: first_pos, end: last_pos + 1,
            provenance: sign_span(fallback_key, all_fingerprints, timestamp, server_id),
        }];
    }

    // Group adjacent elements by (author, type) and sign each group
    let mut spans = Vec::new();
    while i < entries.len() {
        let author_key = carrier.provenance.author_club_id;
        let author_type = carrier.provenance.author_type;

        // Collect adjacent elements with the same author
        while j < entries.len()
            && same_author(entries[j], author_key, author_type)
        {
            fingerprints.push(entries[j].content_fingerprint());
            j += 1;
        }

        // Sign this run
        let signing_key = match author_type {
            AuthorType::Llm => fallback_key,      // server signs for LLM
            _ => author_keys.get(&author_key)     // author signs
                .unwrap_or(fallback_key),
        };

        spans.push(SpanProvenance {
            start: entries[i].pos, end: entries[j-1].pos + 1,
            provenance: sign_span(signing_key, &fingerprints, timestamp, server_id),
        });

        i = j;
    }
    spans
}
A l i c e w r o t e ! Span 1: Alice signs Span 2: Bob signs AI sign each span independently edition.span_provenance = [ SpanProvenance(0..5, Alice's signature), SpanProvenance(5..10, Bob's signature), SpanProvenance(10..11, server's signature) ]

Materialization groups adjacent elements by author and signs each run as a separate SpanProvenance. Different authors get different signatures.

13. Transclusion Attribution: Following Content Across Documents

When content is transcluded (copied with attribution) from one document to another, Xudanu preserves the chain of custody. The apply_transclusion_attribution method:

  1. Identifies the origin work, destination work, and excerpt text
  2. Finds the excerpt within the origin's edition
  3. Copies the origin's ElementProvenance for the matched elements
  4. Wraps it in TransclusionInfo noting who performed the transclusion
  5. Attaches it to the destination edition's elements
pub struct TransclusionInfo {
    pub club_id: BeId,              // who performed the transclusion
    pub display_name: String,
    pub public_key: [u8; 32],
    pub timestamp: u64,
}

This means if Alice writes a paragraph in document A, and Bob transcludes it into document B, the elements in B carry both Alice's original authorship (via author_public_key) and Bob's transclusion action (via transcluded_by). The derivation chain is preserved.

Pending attributions: If the origin work isn't available yet (e.g., it's being edited), the transclusion attribution is stored as a PendingAttribution and re-applied when the origin work is next revised. This handles the case where transclusion links are created before the origin content is materialized.

14. Historical Attestation: Signing for Deceased Authors

When importing public-domain texts (e.g., from Project Gutenberg), the original author is dead and cannot sign. Instead, the server signs on their behalf using a special historical attestation domain:

pub fn sign_historical_attestation(
    server_signing_key: &SigningKey,
    element_fingerprints: &[[u8; 32]],
    historical_author_id: BeId,
    timestamp: u64,
    server_id: &[u8; 32],
) -> Provenance {
    // Uses HISTORICAL_ATTESTATION_DOMAIN, not PROVENANCE_DOMAIN
    let payload = blake3(
        "xudanu/v1/historical-attestation"
        || span_fp
        || server_pub_key
        || historical_author_id    // <-- which historical author
        || timestamp
        || server_id
    );
    let signature = ed25519_sign(server_signing_key, payload);
    Provenance { ... }
}

The verification function checks the same payload. Because it uses a different domain tag, a historical attestation signature cannot be confused with a direct author signature. The historical_author_id is part of the signed payload, so the server is committing to "this content is by author X" — it cannot later reassign it.

15. The Attribution Log: Tamper-Evident Audit Trail

Every time span provenance is stamped on an edition revision, the server also appends an entry to the attribution log — a tamper-evident, chained, append-only file stored at data/attribution/attribution.log.

pub struct AttributionEntry {
    pub sequence: u64,
    pub timestamp: u64,
    pub author_pk_hex: String,      // who signed
    pub span_fp_hex: String,        // what they signed (span fingerprint)
    pub signature_hex: String,      // the Ed25519 signature
    pub server_id_hex: String,      // which server witnessed it
    pub work_id: u64,
    pub revision: u64,
}

Each entry is a JSON line. The log uses the same chaining technique as the security log (see Section 16).

16. The Chained Hash: How Tampering Is Detected

Each log line includes a chain= field that is the SHA-256 hash of the previous line's chain hash concatenated with the current line's content. This creates a chain where altering any entry invalidates all subsequent entries:

// Append:
let line = format!(
    "{{\"seq\":{},\"ts\":{},\"author\":\"{}\",...,\"rev\":{}}}",
    entry.sequence, entry.timestamp, entry.author_pk_hex, ..., entry.revision
);
let chain_input = format!("{}{}", self.prev_hash, line);
let chain_hash = sha256_hex(chain_input);
let chained_line = format!("{} chain={}\n", line, chain_hash);
write_to_file(chained_line);
self.prev_hash = chain_hash;
seed hash Entry 0 { seq:0, author:Alice... } chain=hash(s+e0) Entry 1 { seq:1, author:Bob... } chain=hash(c1+e1) feeds into Entry 2 { seq:2, author:Eve... } TAMPERED: work:42 → work:99 chain=MISMATCH! Entry 3 { seq:3, ... } also MISMATCH!

Tampering with Entry 2 changes its chain hash. Entry 3 was computed from the original Entry 2 hash, so the chain breaks at that point and all subsequent entries fail verification.

The verify_attribution_log function walks the entire log from the seed and checks each chain hash:

pub fn verify_attribution_log(content: &str, seed: &str) -> Result<(usize, String), ChainVerifyError> {
    let mut prev_hash = seed;
    for line in content.lines() {
        let chain_value = extract_chain_hash(line);
        let expected = sha256_hex(format!("{}{}", prev_hash, line_without_chain));
        if chain_value != expected {
            return Err(ChainVerifyError { line_number, expected, found: chain_value, ... });
        }
        prev_hash = chain_value;  // advance the chain
    }
    Ok((line_count, prev_hash)
}

The test suite verifies three attack scenarios:

17. Provenance Ancestry: Walking the Derivation Chain

For transcluded content, Xudanu can walk the provenance chain — the sequence of links that brought content to the current document. The provenance_ancestry method does a breadth-first walk of incoming transclusion links:

pub fn provenance_ancestry(&self, work_id: BeId) -> Vec<ProvenanceHop> {
    let mut chain = Vec::new();
    let mut queue = vec![work_id];

    while let Some(current) = queue.pop() {
        for (link_id, origin, dest) in self.list_links_for_work(current) {
            if dest != current { continue; }

            // Add embedded provenance hops from the link itself
            for hop in link.end_at("LeftEnd").provenance_chain() {
                chain.push(hop);
            }

            // Add this link as a hop
            chain.push(ProvenanceHop::new(origin, link_id));

            // Continue walking backward
            queue.push(origin);
        }
    }
    chain
}

Each ProvenanceHop identifies a (source_work, link) pair. The enrich_provenance_hops method then resolves these into human-readable payloads with titles and author names.

18. Attestation Reports: Exportable Proof

The generate_attestation_report method produces a standalone JSON document that proves the provenance of a work at a point in time:

{
  "type": "xudanu-attestation-report",
  "version": 1,
  "generated_at": 1738246800,
  "document": {
    "work_id": "04a2",
    "title": "Chapter 1: The Beginning",
    "revision": 7,
    "character_count": 15234,
    "content_hash_blake3": "a3b1c2..."
  },
  "server_identity": {
    "server_id": "f4e2...",
    "verifying_key_ed25519": "9a8b..."
  },
  "attribution": {
    "span_count": 3,
    "spans": [
      {
        "range": [0, 5000],
        "author": "Alice",
        "author_type": "human",
        "signature_valid": true,
        "timestamp": 1738240000,
        "provenance_chain": [
          { "source_work_id": "03b1", "source_title": "Draft", "source_author": "Alice" }
        ]
      },
      {
        "range": [5000, 12000],
        "author": "Bob",
        "author_type": "human",
        "signature_valid": true,
        ...
      },
      {
        "range": [12000, 15234],
        "author": "Project Gutenberg",
        "author_type": "historical",
        "signature_valid": true,
        ...
      }
    ]
  },
  "attribution_log": {
    "entry_count": 42,
    "chain_valid": true,
    "last_sequence": 42,
    "has_log": true
  }
}
The report is independently verifiable. Anyone with the BLAKE3 content hash, the span fingerprints, and the Ed25519 public keys can verify every signature without trusting the server. The chain_valid field proves the attribution log hasn't been tampered with since the signatures were recorded.

19. Federation: Cross-Server Signatures

In a federated cluster, content authored on one server can be replicated to others. To extend provenance trust across server boundaries, Xudanu uses cross-server signatures. When server A vouches for a piece of provenance from server B, A signs a payload derived from B's base provenance:

pub fn sign_cross_server(
    signing_key: &SigningKey,       // server A's signing key
    base_provenance: &Provenance,    // the original provenance from server B
    server_id: &[u8; 32],            // server A's ID
    verifying_key: &[u8; 32],
    timestamp: u64,
) -> CrossServerSignature {
    let payload = compute_federation_payload(base_provenance, server_id, timestamp);
    // payload = blake3(FEDERATION_PROVENANCE_DOMAIN || base_pk || base_sig
    //                  || base_ts || base_server || this_server || timestamp)
    let signature = sign_bytes(signing_key, &payload);
    CrossServerSignature { server_id, verifying_key, signature, timestamp }
}
pub struct CrossServerSignature {
    pub server_id: [u8; 32],      // which server is vouching
    pub verifying_key: [u8; 32],  // that server's public key
    pub signature: [u8; 64],      // their Ed25519 signature
    pub timestamp: u64,
}

A FederatedProvenance bundles the base provenance with all cross-server signatures and the cluster consensus:

pub struct FederatedProvenance {
    pub base_provenance: Provenance,
    pub cross_server_signatures: Vec<CrossServerSignature>,
    pub consensus: ClusterConsensus,
}
Base Provenance Alice signs "Hello world" on Server B Server A signs cross-server signature "I verify B's provenance" Server C signs cross-server signature "I verify B's provenance" Server D signs cross-server signature "I verify B's provenance" FederatedProvenance base + 3 cross-server signatures + ClusterConsensus verify_federation_provenance() checks all signatures

Three peer servers each sign a cross-server signature vouching for the base provenance. Together they form a FederatedProvenance with cluster consensus.

20. Cluster Consensus: Unanimous, Majority, Supermajority

ClusterConsensus records how many servers verified the provenance and whether the threshold was met:

pub struct ClusterConsensus {
    pub consensus_type: ConsensusType,
    pub verifications: Vec<ServerVerification>,
    pub threshold_met: bool,
    pub total_servers: u32,
    pub approving_servers: u32,
    pub timestamp: u64,
}

pub enum ConsensusType {
    Unanimous,     // all servers must sign
    Majority,      // > 50%
    Supermajority, // ≥ 67%
}

The verify_federation_provenance function checks all cross-server signatures and then verifies the consensus threshold:

pub fn verify_federation_provenance(federated: &FederatedProvenance, ...) -> bool {
    // 1. Verify the base provenance signature
    if !verify_base(&federated.base_provenance) { return false; }

    // 2. Verify each cross-server signature
    for cross_sig in &federated.cross_server_signatures {
        if !verify_cross_sig(cross_sig) { return false; }
    }

    // 3. Check the consensus threshold
    match federated.consensus.consensus_type {
        ConsensusType::Unanimous => {
            federated.cross_server_signatures.len() as u32
                == federated.consensus.total_servers
        }
        ConsensusType::Majority => {
            federated.cross_server_signatures.len() as u32
                > federated.consensus.total_servers / 2
        }
        ConsensusType::Supermajority => {
            let threshold = (total as f64 * 0.67).ceil() as u32;
            federated.cross_server_signatures.len() as u32 >= threshold
        }
    }
}

21. W3C PROV-JSON Export

Xudanu can export provenance as W3C PROV-JSON — the standard provenance interchange format from the World Wide Web Consortium. This allows interoperability with provenance tools, academic attribution systems, and audit frameworks.

The PROV model maps Xudanu concepts as follows:

Xudanu ConceptPROV ConceptExample
Author (club)prov:Agent (prov:Person or xudanu:LLMAgent)Alice's club as an agent
Serverprov:Agent (xudanu:FederationServer)Server B as an agent
Content revisionprov:Activity"Alice edited revision 7"
Transclusionprov:wasDerivedFrom (xudanu:Transclusion)Document B derived from A
Mergeprov:wasDerivedFrom (xudanu:Merge)Revision 8 merged from revisions 6 and 7
Importprov:wasDerivedFrom (prov:Revision)Gutenberg import
Cross-server signatureprov:Entity (xudanu:CrossServerSignature)Server A's signature entity
Cluster verificationprov:Activity (xudanu:ClusterVerification)"3 servers verified"

The FederationProvenanceBundle assembles all these into a PROV-JSON document with proper prefixes (xudanu: namespace), agents, activities, associations, and entities. The to_prov_bundle() method generates the complete document.

Why PROV-JSON? It's an international standard (W3C Recommendation) for provenance interchange. Academic institutions, government audit systems, and research data management platforms can ingest Xudanu's PROV-JSON export directly, without understanding Xudanu's internal types. This bridges the gap between Xudanu's hypertext provenance and the broader provenance research community.

22. The Wire Protocol

Provenance and attribution operations are exposed via the WebSocket protocol:

OpcodeOperationDescription
0x0D01AttributionQueryQuery attribution spans for a work (optionally a character range)
0x0D02AttributionVerifyVerify a signature against a span fingerprint
0x0D03AttributionLogStatusCheck attribution log chain validity and entry count
0x0D11WorkApplySourceAttributionApply source import attribution (historical author)
0x0D12WorkApplyTransclusionAttributionApply transclusion attribution for a link
0x1E04AttributionQueryResolvedQuery with resolved author names and types
(via dispatch)ProvenanceAncestryWalk the transclusion derivation chain
(via dispatch)GenerateAttestationReportGenerate the full JSON attestation report

23. Source Code Map

FilePathKey Types
Signing & verification
provenance.rssrc/edition/provenance.rsProvenance, SpanProvenance, ElementProvenance, AuthorType, DerivationInfo, TransclusionInfo, sign_span, verify_span_provenance, sign_historical_attestation, sign_cross_server
Audit log
attribution_log.rssrc/server/transport/attribution_log.rsAttributionLog, AttributionEntry, FileAttributionLog, verify_attribution_log, ChainVerifyError
Federation provenance
federation.rssrc/server/federation.rsCrossServerSignature, ServerVerification, ClusterConsensus, ConsensusType, FederatedProvenance, export_cross_server_signatures_prov, create_federation_verification_bundle
provenance.rssrc/edition/provenance.rsFederationMetadata, FederationServerAgent, FederationAttestation, ClusterVerificationActivity, FederationProvenanceBundle, ProvEntity/Activity/Association/Agent/Bundle/Value/JsonDocument
Server integration
server.rssrc/server/server.rsmaterialize_with_provenance, build_edition_provenance, attribution_query, attribution_verify, attribution_log_status, generate_attestation_report, apply_transclusion_attribution, provenance_ancestry, compute_provenance_chain
otree_crdt.rssrc/server/otree_crdt.rsmaterialize_edition_with_provenance, build_edition_provenance
chained_log.rssrc/server/transport/chained_log.rsChainedLogWriter, ChainVerifyError (the security log, same pattern as attribution log)
Signing & PROV
Audit log
Federation
Server integration
Security log