cadrumo.core.corpus_manifest.manifest module¶
Directory-level integrity manifest for CORPUS-class on-disk data.
The substrate’s SensitivityClass.CORPUS policy mandates
SHA-256 integrity tracking for plaintext-at-rest reference material.
This module is the canonical implementation: a single manifest model
covers every file under a corpus root with a per-file SHA-256 + size
record, plus a self-attesting manifest_sha256 so a manifest-only
tamper is detectable.
Manifests are plaintext JSON on disk (corpus material is plaintext; the manifest is the integrity gate, not the secrecy gate). Per-record fields are validated against path-traversal at construction.
There is no human CLI for corpus verification. Consumers import manifest and bundle operations directly from this defining module. The same API owns manifest regeneration after an intentional corpus update.
This module also builds and verifies distributable corpus bundles: a
single .zip archive carrying every corpus file plus an embedded
CorpusManifest (see build_corpus_bundle() and
verify_corpus_bundle()), for offline installation of the bundled
corpus checksummed against its own manifest.
- class CorpusEntry(**data)[source]¶
Bases:
BaseModelOne file’s integrity record under a corpus root.
- Variables:
relative_path – POSIX-style path from the corpus root. Validated against path-traversal at construction.
sha256 – Lower-case hex SHA-256 of the file’s bytes.
content_length – Size of the file in bytes (>= 0; the empty file is permitted).
- Parameters:
data (Any)
- relative_path: str¶
- sha256: Hex64Str¶
- content_length: int¶
- class CorpusManifest(**data)[source]¶
Bases:
BaseModelSelf-attesting manifest covering every file under a corpus root.
The
manifest_sha256field is a SHA-256 over the canonical JSON serialisation of(manifest_version, corpus_root_name, generated_at, entries)with sorted keys. On load, the same digest is re-derived and compared; a mismatch raisesCorpusManifestTamperError.- Variables:
manifest_version – Wire-format version. Higher than the consumer supports raises
CorpusManifestErrorat load time.corpus_root_name – Stable identifier for the corpus (
"manuals","legal", etc.).generated_at – UTC timestamp the manifest was built.
entries – Frozen tuple of
CorpusEntryrecords, sorted byrelative_pathfor deterministicmanifest_sha256.manifest_sha256 – Self-attesting digest. Re-computed on load.
- Parameters:
data (Any)
- manifest_version: int¶
- corpus_root_name: str¶
- generated_at: datetime¶
- entries: tuple[CorpusEntry, ...]¶
- manifest_sha256: Hex64Str¶
- build_corpus_manifest(corpus_root, *, corpus_root_name, generated_at=None)[source]¶
Walk
corpus_rootand build a freshly self-signed manifest.- Parameters:
corpus_root (
Path) – The corpus directory to walk.corpus_root_name (
str) – Stable identifier for this corpus (logged in the manifest body so an operator can grep for it).generated_at (
datetime|None) – Optional override for the timestamp. WhenNonethe canonical clockcore.time.clock.now()is consulted, so the deterministic-output seam (core.time.clock.frozen_clock()) pins it under replay; an explicit value still overrides.
- Return type:
- Returns:
A
CorpusManifestcovering every regular file undercorpus_root.- Raises:
FileNotFoundError – If
corpus_rootdoes not exist.NotADirectoryError – If
corpus_rootis not a directory.
- class CorpusBundleVerification(**data)[source]¶
Bases:
BaseModelResult of verifying a bundle’s archived files against its embedded manifest.
An empty
missing/unexpected/mismatchedtriple means the bundle is clean and safe to extract.missingis a file the manifest declares but the archive does not carry;unexpectedis an archive member the manifest does not declare;mismatchedis an archived file whose SHA-256 or byte length disagrees with its manifest record.- Parameters:
data (Any)
- manifest: CorpusManifest¶
- missing: tuple[str, ...]¶
- unexpected: tuple[str, ...]¶
- mismatched: tuple[str, ...]¶
- build_corpus_bundle(corpus_root, *, corpus_root_name, output_path, generated_at=None)[source]¶
Pack
corpus_rootinto a checksummed zip bundle atoutput_path.Walks
corpus_rootwith the same file-selection rules asbuild_corpus_manifest()(hidden files and the manifest sidecar itself are excluded), builds the manifest, then writes a new zip archive containing the manifest under_BUNDLE_MANIFEST_MEMBERplus every corpus file under its POSIX-relative path. The archive is assembled fully in memory, then persisted throughatomic_write_bytes()(standard tier: tempfile sibling, fsync,os.replace(), best-effort parent-directory fsync), so a failure mid-write never leaves a partial bundle atoutput_path.- Parameters:
corpus_root (
Path) – The corpus directory to bundle.corpus_root_name (
str) – Stable identifier recorded in the manifest (mirrorsbuild_corpus_manifest()).output_path (
Path) – Destination.zippath. Parent directories are created as needed; an existing file at this path is overwritten.generated_at (
datetime|None) – Optional override for the manifest timestamp; seebuild_corpus_manifest().
- Return type:
- Returns:
The
CorpusManifestembedded in the written bundle.- Raises:
FileNotFoundError – If
corpus_rootdoes not exist.NotADirectoryError – If
corpus_rootis not a directory.
- verify_corpus_bundle(bundle_path)[source]¶
Verify a zip bundle’s archived files against its embedded manifest.
Loads the manifest member (
_BUNDLE_MANIFEST_MEMBER), re-derives its self-attesting digest (a mismatch means the manifest member itself was tampered with, raisingCorpusManifestTamperError), then re-hashes every other archive member and compares against the manifest’s per-file records.- Parameters:
bundle_path (
Path) – Path to the.zipbundle to verify.- Return type:
- Returns:
A
CorpusBundleVerificationenumerating any missing, unexpected, or hash-mismatched files. An empty result means the bundle is clean.- Raises:
FileNotFoundError – If
bundle_pathdoes not exist.CorpusBundleError – If
bundle_pathis not a valid zip archive, or the embedded manifest is absent, structurally invalid, or at an unsupported version.CorpusManifestTamperError – If the manifest member’s recorded
manifest_sha256does not match its own body.