WeaveDocs
Weft

Manifest

The WeftManifest type — fields, validation, signing, and the canonical encoding.

What this page covers

WeftManifest is the signed commitment a producer makes when publishing a payload. This page documents every field, the validation rules, and the canonical signing payload.

Schema and version

pub const WEFT_MANIFEST_SCHEMA: &str = "weave.weft.manifest.v1";
pub const WEFT_MANIFEST_VERSION: u16 = 1;

Manifests with a different schema or version are rejected by WeftManifest::validate.

Warning

Changing WEFT_MANIFEST_VERSION is a breaking change. Old consumers will reject new manifests. Plan version bumps as coordinated migrations: keep producing v1 manifests until consumers are updated.

Fields

pub struct WeftManifest {
    pub schema:       String,
    pub version:      u16,
    pub root:         WeftHash,
    pub chunk_size:   u64,
    pub chunk_count:  u64,
    pub total_len:    u64,
    pub chunks:       Vec<ChunkDescriptor>,
    pub parity:       Option<ParitySpec>,
    pub producer:     ManifestProducer,
    pub artifact:     ArtifactMetadata,
}
FieldTypeMeaning
schemaStringAlways weave.weft.manifest.v1 for this version
versionu16Always 1
rootWeftHashMerkle root over the ordered chunk ids
chunk_sizeu64Bytes per chunk (final chunk may be shorter)
chunk_countu64Number of chunks (chunks.len() as u64)
total_lenu64Original payload length in bytes
chunksVec<ChunkDescriptor>Ordered chunk descriptors
parityOption<ParitySpec>Reserved; must be None today
producerManifestProducerDID + signature
artifactArtifactMetadataMedia type, schema, optional registry pointers

ChunkDescriptor

pub struct ChunkDescriptor {
    pub index:  u64,    // 0-based position in the manifest
    pub offset: u64,    // byte offset within the original payload
    pub len:    u32,    // length of this chunk in bytes
    pub id:     WeftHash, // BLAKE3 chunk id
}

ManifestProducer

pub struct ManifestProducer {
    pub did:            String,
    pub signing_key_id: Option<String>,
    pub signature:      Vec<u8>,
}

ArtifactMetadata

pub struct ArtifactMetadata {
    pub artifact_id:          Option<String>, // optional registry id (e.g. MARS)
    pub media_type:           String,         // required, e.g. "application/octet-stream"
    pub schema:               String,         // required, semantic schema id
    pub mars_asset_id:        Option<String>, // optional MARS asset id
    pub sigil_commitment_cid: Option<String>, // optional Sigil commitment CID
}

Validation

WeftManifest::validate runs every check below and returns the first failure. Both from_bytes and from_canonical_bytes call validate before returning, so an invalid manifest is never observable.

CheckFailure
schema == WEFT_MANIFEST_SCHEMAManifest("unsupported schema ...")
version == WEFT_MANIFEST_VERSIONManifest("unsupported version ...")
0 < chunk_size <= MAX_CHUNK_SIZEManifest("chunk_size ... out of bounds")
parity.is_none()Unsupported("...erasure coding is reserved...")
chunk_count == chunks.len() as u64Manifest("chunk_count mismatch")
!producer.did.trim().is_empty()Manifest("producer DID is required")
!producer.signature.is_empty()Manifest("producer signature is required")
artifact.media_type and artifact.schema non-emptyManifest("artifact media_type and schema are required")
chunks[i].index == i for each iManifest("chunk index mismatch at ...")
chunks[i].offset == sum(chunks[0..i].len)Manifest("chunk ... offset ..., expected ...")
chunks[i].len > 0Manifest("chunk ... has zero length")
chunks[i].len <= chunk_sizeManifest("chunk ... length exceeds chunk_size")
Non-final chunks have len == chunk_sizeManifest("non-final chunk ... is short")
sum(chunks.len) == total_lenManifest("total_len ..., expected ...")
Merkle root over chunks[*].id equals rootManifest("root mismatch")

Canonical bytes

to_canonical_bytes() uses serde_json::to_vec. The encoding is stable across producers running the same crate version — the field order is fixed by the struct declaration and serde_json preserves it.

let manifest = WeftManifest::from_bytes(&payload, cfg, producer, artifact)?;
let bytes    = manifest.to_canonical_bytes()?;
let round_trip = WeftManifest::from_canonical_bytes(&bytes)?;
assert_eq!(manifest, round_trip);

The canonical bytes are what goes on the wire to peers, what is stored on disk, and what is committed on Sigil.

Signing payload

The producer signs manifest.signing_payload(), which returns the canonical bytes of the manifest with the signature field cleared:

fn signing_payload(&self) -> Result<Vec<u8>> {
    let mut unsigned = self.clone();
    unsigned.producer.signature.clear();
    unsigned.to_canonical_bytes()
}

The producer signs these bytes with their Ed25519 key, then replaces producer.signature with the 64-byte signature before publishing.

Verifying a signature

use ed25519_dalek::{Signature, VerifyingKey, Verifier};
use weft::manifest::WeftManifest;

fn verify(
    manifest: &WeftManifest,
    pubkey: &VerifyingKey,
) -> Result<(), Box<dyn std::error::Error>> {
    let payload = manifest.signing_payload()?;
    let sig = Signature::from_slice(&manifest.producer.signature)?;
    pubkey.verify(&payload, &sig)?;
    Ok(())
}

The verifier needs to obtain the public key for the producer's DID. This typically happens through Weave's identity layer (weave-identity) — see DHT Binding for the resolution flow.

Forward compatibility

The schema constant pins weave.weft.manifest.v1. Adding fields is a breaking change for verifiers; bump WEFT_MANIFEST_VERSION and gate parsing on the version.

The parity field is reserved for a future erasure-coding extension. Setting it today is a hard error; this is intentional — old verifiers cannot reconstruct from parity, so old producers must not emit it.