Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,20 @@
without making CAS semantic authority, reports missing blobs as typed import
obstructions, and keeps equal bytes under different semantic coordinates as
distinct retained material references.
- `warp-core` WAL WSC exports now carry retained material and reading-reference
envelopes through ref-only, self-contained, and CAS-addressed profiles.
Self-contained exports can embed retained payload bytes and validate them
against the WAL-retained material digest, while CAS-addressed imports require
the referenced retained blobs to be present before reporting success.
- `warp-core` now includes a filesystem-backed WSC store adapter that persists
envelope material separately from commit markers, hides staged material until
marker publication, reopens committed envelopes in deterministic order, and
reports torn envelope or marker files as typed WSC store obstructions.
- `echo-cli` now exposes read-only `wsc causal-history` commands that export
ref-only and self-contained WAL WSC bundles from filesystem WAL roots, inspect
bundle envelope metadata, verify self-contained bundles without the original
WAL root, and report unavailable ref-only segment bytes as typed material
obstructions in JSON output.
bundle envelope metadata including retained evidence envelopes, verify
self-contained bundles without the original WAL root, and report unavailable
ref-only segment bytes as typed material obstructions in JSON output.
- `echo-cas` now exposes a fallible filesystem-backed `DiskTier` for durable
retained blobs, preserving content-only BLAKE3 hash semantics across process
reconstruction while keeping missing blobs as explicit absence.
Expand All @@ -63,6 +68,10 @@
filesystem failure atomicity, CLI submission posture JSON, stale-claim, and
generated man-page checks while leaving `runtime-wal-ack` as the fast
semantic gate.
- `cargo xtask test-slice durability-release` now includes the exact
`wsc_retained_evidence_export_modes` witness, keeping retained-evidence WSC
export coverage in the release gate without using Cargo's slow package-level
name filter.
- `warp-core` trusted runtime hosts now configure runtime WAL through
`TrustedRuntimeWalConfig`, including in-memory and filesystem-backed
adapters. `TrustedRuntimeWalStoreKind` exposes the configured adapter kind as
Expand Down Expand Up @@ -695,6 +704,9 @@ Applied, Rejected, Obstructed}` with receipt evidence and typed contract

- Echo 1.0 planning references now point at the cross-repository Continuum
Stack Convergence Project instead of the retired Echo-only Project.
- Local `scripts/verify-local.sh full` now treats broad Cargo test lanes as
GitHub Actions-owned by default, while keeping a maintainer opt-in through
`VERIFY_LOCAL_FULL_TESTS=1` for intentional local full-suite runs.
- `warp-core` renamed the generated contract package host API from
`install_contract_package(...)` to `register_contract_package(...)` so the
trusted-runtime boundary reads as explicit runtime-owned registration instead
Expand Down
9 changes: 7 additions & 2 deletions crates/warp-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ history into Echo. Export commands inspect a filesystem WAL root read-only and
require explicit writer-epoch projection evidence as JSON.

```sh
# Export graph facts plus external segment-byte dependencies
# Export graph facts plus retained evidence refs and external segment-byte dependencies
echo-cli wsc causal-history export-ref-only runtime.wal \
--writer-epochs writer-epochs.json \
--out causal-history.ref-only

# Export graph facts plus embedded WAL segment bytes
# Export graph facts plus retained evidence refs and embedded WAL segment bytes
echo-cli wsc causal-history export-self-contained runtime.wal \
--writer-epochs writer-epochs.json \
--out causal-history.self-contained
Expand All @@ -98,6 +98,11 @@ Ref-only verification keeps the bundle observable but reports unavailable
segment bytes as typed JSON material obstructions until external WAL material is
provided by a future adapter.

Bundle manifests include `retained-evidence.ecwsc` for retained material and
reading references. Self-contained bundles also include `retained-payloads.ecwsc`
when retained payload bytes are embedded for import-time reveal and digest
validation.

## Global Flags

- `--format text|json` — Output format (default: `text`). Can appear before or after the subcommand.
Expand Down
75 changes: 58 additions & 17 deletions crates/warp-cli/src/wsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ use std::path::{Path, PathBuf};
use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use warp_core::causal_wal::{
canonical_segment_path, project_filesystem_wal_recovery, recover_filesystem_store, Lsn,
RecoveryAccessMode, RecoveryCertificateRef, RecoveryTailPosture, SubmissionAcceptanceRecord,
TickReceiptRecord, WalCommitAnchor, WalProjectionGraphObservationPosture,
WalReceiptCorrelationRecord, WalRecordKind, WalRecoveryProjectionPosture, WalRoot,
WalSegmentId, WalSegmentSealPosture, WalSegmentStorageLocator, WalTransactionId,
WalWriterEpoch, WriterEpochId,
canonical_segment_path, project_filesystem_wal_recovery, recover_filesystem_store,
recover_retention_index, Lsn, ReadingRefRecord, RecoveryAccessMode, RecoveryCertificateRef,
RecoveryTailPosture, RetainedMaterialRecord, SubmissionAcceptanceRecord, TickReceiptRecord,
WalCommitAnchor, WalProjectionGraphObservationPosture, WalReceiptCorrelationRecord,
WalRecordKind, WalRecoveryProjectionPosture, WalRoot, WalSegmentId, WalSegmentSealPosture,
WalSegmentStorageLocator, WalTransactionId, WalWriterEpoch, WriterEpochId,
};
use warp_core::wsc::{
validate_wsc_cas_addressed_wal_export, validate_wsc_ref_only_wal_export,
validate_wsc_self_contained_wal_export, wsc_ref_only_wal_export, wsc_self_contained_wal_export,
WscCasAddressedWalExport, WscCasBlobStorePort, WscCausalHistoryExportProfileKind,
WscRefOnlyWalExport, WscSelfContainedWalExport, WscSelfContainedWalSegmentMaterial,
WscStoreEnvelope,
WscStoreEnvelope, WscWalCausalHistoryRecords,
};
use warp_core::Hash;

Expand All @@ -31,6 +31,8 @@ const BUNDLE_MANIFEST: &str = "bundle.json";
const PROJECTION_ENVELOPE: &str = "projection.ecwsc";
const ACCEPTED_SUBMISSIONS_ENVELOPE: &str = "accepted-submissions.ecwsc";
const RECEIPT_CORRELATIONS_ENVELOPE: &str = "receipt-correlations.ecwsc";
const RETAINED_EVIDENCE_ENVELOPE: &str = "retained-evidence.ecwsc";
const RETAINED_PAYLOADS_ENVELOPE: &str = "retained-payloads.ecwsc";
const SEGMENT_MATERIAL_ENVELOPE: &str = "segment-material.ecwsc";

/// Exports a ref-only WAL causal-history WSC bundle.
Expand All @@ -41,12 +43,7 @@ pub(crate) fn export_ref_only(
format: &OutputFormat,
) -> Result<()> {
let projected = project_wal_root(wal_root, writer_epochs)?;
let export = wsc_ref_only_wal_export(
&projected.root,
&projected.accepted_submissions,
&projected.receipts,
&projected.correlations,
)?;
let export = wsc_ref_only_wal_export(&projected.root, projected.records())?;

let manifest = write_ref_only_bundle(out, &projected.root, &export)?;
emit_export_report("export-ref-only", out, &manifest, format)
Expand All @@ -64,9 +61,8 @@ pub(crate) fn export_self_contained(
let export = wsc_self_contained_wal_export(
&projected.root,
&segment_materials,
&projected.accepted_submissions,
&projected.receipts,
&projected.correlations,
&[],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass retained payloads to self-contained export

When the recovered WAL contains any Present retained material record, wsc_self_contained_wal_export now requires an embedded WscSelfContainedRetainedMaterial for that digest. This CLI path always passes &[], so export-self-contained fails with MissingRetainedMaterial for exactly the WALs containing retained evidence it now advertises, instead of producing a bundle with retained-payloads.ecwsc.

Useful? React with 👍 / 👎.

projected.records(),
)?;

let manifest = write_self_contained_bundle(out, &projected.root, &export)?;
Expand Down Expand Up @@ -122,6 +118,20 @@ struct ProjectedWal {
accepted_submissions: Vec<SubmissionAcceptanceRecord>,
receipts: Vec<TickReceiptRecord>,
correlations: Vec<WalReceiptCorrelationRecord>,
retained_materials: Vec<RetainedMaterialRecord>,
reading_refs: Vec<ReadingRefRecord>,
}

impl ProjectedWal {
fn records(&self) -> WscWalCausalHistoryRecords<'_> {
WscWalCausalHistoryRecords {
retained_materials: &self.retained_materials,
reading_refs: &self.reading_refs,
accepted_submissions: &self.accepted_submissions,
receipts: &self.receipts,
correlations: &self.correlations,
}
}
}

fn project_wal_root(wal_root: &Path, writer_epochs: &Path) -> Result<ProjectedWal> {
Expand All @@ -145,11 +155,14 @@ fn project_wal_root(wal_root: &Path, writer_epochs: &Path) -> Result<ProjectedWa
.ok_or_else(|| anyhow::anyhow!("WAL recovery projection did not produce a root"))?;
let (accepted_submissions, receipts, correlations) =
causal_history_records_from_report(&report)?;
let retention = recover_retention_index(&report)?;
Ok(ProjectedWal {
root,
accepted_submissions,
receipts,
correlations,
retained_materials: retention.material_by_digest.into_values().collect(),
reading_refs: retention.reading_by_id.into_values().collect(),
Comment on lines +164 to +165

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid dropping conflicting retained WAL records

For WALs that contain conflicting retained-material digests or reading ids, recover_retention_index collapses them into maps before export, so these into_values() calls emit only one record and the WSC bundle no longer witnesses the conflict. Since retained evidence export is supposed to carry admitted WAL evidence, collect the raw retained frames or run the conflict-checking retention-record constructor instead of exporting the lossy recovered index.

Useful? React with 👍 / 👎.

})
}

Expand Down Expand Up @@ -231,6 +244,7 @@ fn write_ref_only_bundle(
RECEIPT_CORRELATIONS_ENVELOPE,
&export.receipt_correlation_envelope,
)?;
write_envelope(out, RETAINED_EVIDENCE_ENVELOPE, &export.retention_envelope)?;
let manifest = BundleManifest {
schema_version: 1,
profile: "ref-only".to_owned(),
Expand All @@ -239,7 +253,9 @@ fn write_ref_only_bundle(
projection: PROJECTION_ENVELOPE.to_owned(),
accepted_submissions: ACCEPTED_SUBMISSIONS_ENVELOPE.to_owned(),
receipt_correlations: RECEIPT_CORRELATIONS_ENVELOPE.to_owned(),
retained_evidence: RETAINED_EVIDENCE_ENVELOPE.to_owned(),
segment_material: None,
retained_payloads: None,
cas_references: None,
},
};
Expand All @@ -260,6 +276,11 @@ fn write_self_contained_bundle(
SEGMENT_MATERIAL_ENVELOPE,
&export.segment_material_envelope,
)?;
write_envelope(
out,
RETAINED_PAYLOADS_ENVELOPE,
&export.retained_material_envelope,
)?;
write_envelope(
out,
ACCEPTED_SUBMISSIONS_ENVELOPE,
Expand All @@ -270,6 +291,7 @@ fn write_self_contained_bundle(
RECEIPT_CORRELATIONS_ENVELOPE,
&export.receipt_correlation_envelope,
)?;
write_envelope(out, RETAINED_EVIDENCE_ENVELOPE, &export.retention_envelope)?;
let manifest = BundleManifest {
schema_version: 1,
profile: "self-contained".to_owned(),
Expand All @@ -278,7 +300,9 @@ fn write_self_contained_bundle(
projection: PROJECTION_ENVELOPE.to_owned(),
accepted_submissions: ACCEPTED_SUBMISSIONS_ENVELOPE.to_owned(),
receipt_correlations: RECEIPT_CORRELATIONS_ENVELOPE.to_owned(),
retained_evidence: RETAINED_EVIDENCE_ENVELOPE.to_owned(),
segment_material: Some(SEGMENT_MATERIAL_ENVELOPE.to_owned()),
retained_payloads: Some(RETAINED_PAYLOADS_ENVELOPE.to_owned()),
cas_references: None,
},
};
Expand All @@ -291,7 +315,8 @@ fn verify_ref_only(
manifest: &BundleManifest,
root: &WalRoot,
) -> Result<BundleVerifyOutput> {
let expected_dependencies = wsc_ref_only_wal_export(root, &[], &[], &[])?.segment_dependencies;
let expected_dependencies =
wsc_ref_only_wal_export(root, WscWalCausalHistoryRecords::empty())?.segment_dependencies;
let export = WscRefOnlyWalExport {
profile: WscCausalHistoryExportProfileKind::RefOnly,
projection_envelope: read_envelope(bundle, &manifest.envelopes.projection)?,
Expand All @@ -303,6 +328,7 @@ fn verify_ref_only(
bundle,
&manifest.envelopes.receipt_correlations,
)?,
retention_envelope: read_envelope(bundle, &manifest.envelopes.retained_evidence)?,
segment_dependencies: expected_dependencies,
};
let imported = validate_wsc_ref_only_wal_export(&export, root)?;
Expand Down Expand Up @@ -341,10 +367,16 @@ fn verify_self_contained(
.segment_material
.as_deref()
.ok_or_else(|| anyhow::anyhow!("self-contained bundle is missing segment material"))?;
let retained_payloads = manifest
.envelopes
.retained_payloads
.as_deref()
.ok_or_else(|| anyhow::anyhow!("self-contained bundle is missing retained payloads"))?;
let export = WscSelfContainedWalExport {
profile: WscCausalHistoryExportProfileKind::SelfContained,
projection_envelope: read_envelope(bundle, &manifest.envelopes.projection)?,
segment_material_envelope: read_envelope(bundle, segment_material)?,
retained_material_envelope: read_envelope(bundle, retained_payloads)?,
accepted_submission_envelope: read_envelope(
bundle,
&manifest.envelopes.accepted_submissions,
Expand All @@ -353,6 +385,7 @@ fn verify_self_contained(
bundle,
&manifest.envelopes.receipt_correlations,
)?,
retention_envelope: read_envelope(bundle, &manifest.envelopes.retained_evidence)?,
};
let imported = validate_wsc_self_contained_wal_export(&export, root)?;
Ok(verify_output(
Expand Down Expand Up @@ -392,6 +425,7 @@ fn verify_cas_addressed(
bundle,
&manifest.envelopes.receipt_correlations,
)?,
retention_envelope: read_envelope(bundle, &manifest.envelopes.retained_evidence)?,
};
match validate_wsc_cas_addressed_wal_export(&export, root, &UnavailableCasStore) {
Ok(imported) => Ok(verify_output(
Expand Down Expand Up @@ -586,9 +620,12 @@ struct BundleEnvelopePaths {
projection: String,
accepted_submissions: String,
receipt_correlations: String,
retained_evidence: String,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve v1 bundle manifest compatibility

Because this new manifest field is a required String with no serde default while schema_version remains 1, read_manifest now fails to deserialize self-contained or ref-only v1 bundles created before this change, even when they simply had no retained evidence. That breaks inspect/verify for previously exported empty-retention bundles; make the field optional/defaultable or bump the manifest schema version.

Useful? React with 👍 / 👎.

#[serde(skip_serializing_if = "Option::is_none")]
segment_material: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
retained_payloads: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
cas_references: Option<String>,
}

Expand All @@ -598,10 +635,14 @@ impl BundleEnvelopePaths {
("projection", self.projection.as_str()),
("accepted_submissions", self.accepted_submissions.as_str()),
("receipt_correlations", self.receipt_correlations.as_str()),
("retained_evidence", self.retained_evidence.as_str()),
];
if let Some(path) = self.segment_material.as_deref() {
paths.push(("segment_material", path));
}
if let Some(path) = self.retained_payloads.as_deref() {
paths.push(("retained_payloads", path));
}
if let Some(path) = self.cas_references.as_deref() {
paths.push(("cas_references", path));
}
Expand Down
10 changes: 9 additions & 1 deletion crates/warp-cli/tests/cli_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,15 @@ fn wsc_causal_history_exports_and_verifies_profiles() -> TestResult {
.success();
let inspect_json: serde_json::Value = serde_json::from_slice(&inspect.get_output().stdout)?;
assert_eq!(inspect_json["profile"], "self-contained");
assert_eq!(inspect_json["envelope_count"], 4);
assert_eq!(inspect_json["envelope_count"], 6);
let envelope_roles = inspect_json["envelopes"]
.as_array()
.ok_or("expected envelope summaries")?
.iter()
.map(|envelope| envelope["role"].as_str().ok_or("expected envelope role"))
.collect::<Result<Vec<_>, _>>()?;
assert!(envelope_roles.contains(&"retained_evidence"));
assert!(envelope_roles.contains(&"retained_payloads"));

drop(fixture);

Expand Down
10 changes: 5 additions & 5 deletions crates/warp-core/src/wsc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ pub use store::{
WscReceiptCorrelationRecords, WscRefOnlyWalExport, WscRefOnlyWalExportError,
WscRefOnlyWalImport, WscRefOnlyWalImportError, WscRefOnlyWalLocatorPosture,
WscRefOnlyWalMaterialDependency, WscRefOnlyWalSegmentDependency, WscRetentionRecords,
WscSelfContainedWalExport, WscSelfContainedWalExportError, WscSelfContainedWalImport,
WscSelfContainedWalImportError, WscSelfContainedWalSegmentMaterial, WscStoreEnvelope,
WscStoreEnvelopeId, WscStoreObstruction, WscStoreObstructionKind, WscStorePort,
WscStoreRecordKind, WscStoreSubject, WscStoreWriteReceipt, WscTopologyRecords,
WSC_CAUSAL_HISTORY_EXPORT_PROFILE_VERSION,
WscSelfContainedRetainedMaterial, WscSelfContainedWalExport, WscSelfContainedWalExportError,
WscSelfContainedWalImport, WscSelfContainedWalImportError, WscSelfContainedWalSegmentMaterial,
WscStoreEnvelope, WscStoreEnvelopeId, WscStoreObstruction, WscStoreObstructionKind,
WscStorePort, WscStoreRecordKind, WscStoreSubject, WscStoreWriteReceipt, WscTopologyRecords,
WscWalCausalHistoryRecords, WSC_CAUSAL_HISTORY_EXPORT_PROFILE_VERSION,
};
pub use validate::validate_wsc;
pub use view::{AttachmentRef, WarpView, WscFile};
Expand Down
Loading
Loading