Phase 11 creates a portable archival envelope around finalized PROVENANCE evidence without changing the identities of the enclosed evidence.
PACKAGE_CONTRACT=provenance.forensic-package.v1
PACKAGE_STATE=FINALIZED
EMBEDDED_BUNDLE_CONTRACT=provenance.bundle.v1
VERIFIER=provenance_verify.verify_forensic_package
Phase 2 defines the independently verifiable evidence bundle:
manifest.json
events/
artifact_records/
artifacts/
Phase 11 does not replace that contract. It embeds one verified Phase 2 bundle under evidence/ and adds the archival material needed to move the evidence away from the live store.
package/
├── package.json
├── evidence/
│ └── <unchanged provenance.bundle.v1 contents>
├── custody/
│ └── sha256/
│ └── <custody-digest>.json
├── schemas.json
├── verification.json
└── gaps.json
package.json binds every file except itself by:
path
sha256 content identity
byte count
The package envelope uses domain-separated identity:
PROVENANCE/FORENSIC-PACKAGE/v1
with:
self_hash_exclusion = package_identity
These are deliberately separate:
package_state = FINALIZED
evidence_scope = closed | open
A finalized package may therefore preserve an open evidence collection exactly as it existed. The package must not relabel an open collection as closed merely because the archival envelope has been finalized.
schemas.json records the exact evidence/package contract identifiers and canonicalization version.
verification.json stores the independently recomputed Phase 2 bundle report and custody verification report for the bytes actually placed in the package.
gaps.json is recomputed from the embedded evidence and records explicit archival gaps such as:
OPEN_COLLECTION
MISSING_ARTIFACT
DIGEST_ONLY_ARTIFACT
EVENT_COLLECTION_STATUS
CUSTODY_NOT_PRESENT
PARTIAL_CUSTODY_COVERAGE
The package verifier recomputes this file. Gap metadata therefore cannot be edited into a more flattering story without invalidating verification.
The producer takes a stable read-only snapshot of immutable custody records.
It enumerates the custody record set, reads and independently verifies the records, re-enumerates the directory, and retries if the set changed during capture.
This does not claim a global transaction across a concurrently changing external system. It creates a stable custody snapshot from the local append-only ledger.
The CLI/MCP wrappers append a later EXPORTED custody event to the live custody ledger after successful package publication. That post-publication record is intentionally not retroactively inserted into the already finalized package.
The producer:
verify source snapshot
→ obtain stable custody snapshot
→ build hidden staging directory
→ copy evidence without following symlinks
→ write custody + metadata
→ bind all members in package.json
→ independently verify staged package
→ atomic rename to final destination
→ independently verify published package
A failed build removes its staging/output directory rather than leaving a partially published package.
Publication uses kernel-enforced renameat2(RENAME_NOREPLACE) semantics. A destination created by another process after the initial absence check is never replaced. After publication the producer verifies the final directory by descriptor and requires its filesystem identity to match the staged package. Platforms without the no-replace primitive fail closed rather than falling back to overwrite-capable rename behavior.
Python:
from provenance_verify import verify_forensic_package
report = verify_forensic_package("/path/to/package")
print(report.integrity_verified)
print(report.package_identity)The verifier rejects:
missing declared files
undeclared files
undeclared directories
symbolic links
special filesystem objects
member hash mismatches
member byte-count mismatches
invalid embedded evidence
invalid custody chains
schema metadata drift
verification metadata drift
gap metadata drift
package identity mismatch
provenance package \
--store /path/to/store \
--custody /path/to/custody \
--destination /path/to/forensic-packageThe older provenance export command remains the Phase 7/8 snapshot-copy operation. Phase 11 does not silently change that historical interface.
provenance.package
takes one destination argument and uses the same Phase 11 producer/verifier contract as the CLI.
The Phase 11 test suite creates a package under one simulated machine directory, copies it to a second machine directory, removes the original package, and independently verifies the moved package with the same package identity and evidence manifest identity.
python3 -m unittest discover -s tests -p 'test_package.py' -vSignatures and external anchors do not become members of the Phase 11 package.
They are detached sidecars that bind the finalized package.json / package identity without changing package closure or package identity.
See TRUST.md.
The default forensic-package verifier uses bounded parallel member hashing/counting and the optimized Phase 2 embedded-bundle verifier.
A serial reference entry point remains available:
from provenance_verify import verify_forensic_package_referenceThe optimized/reference package reports must compare exactly equal for stable inputs.
See PERFORMANCE.md.
A finalized Phase 11 package may be used as the source for a detached Phase 14 selective disclosure.
The source package itself is not rewritten. The privacy producer verifies the package and reads the retained source artifact through the same open directory descriptor.
The resulting disclosure intentionally omits source content bytes while binding the source package identity, source artifact digest/record metadata, redaction specification, derivative bytes, and DERIVED lineage event.
See PRIVACY.md.
The Phase 15 distributed-custody protocol transfers an unchanged finalized Phase 11 package.
The package identity is the remote subject identity:
sender package identity
== transfer subject identity
== received package identity
== receipt subject identity
The transfer bundle does not rewrite or repackage the evidence into a new evidence identity. It wraps the existing package with signed handoff metadata.
See TRANSFER.md.