Confidential copy and targeted release
The scenario
Section titled “The scenario”A sender posts a document to one recipient. The relay carrying it reads it on the way and makes a partial copy, and the original still reaches the recipient. Later the relay releases the copy to two listed readers, then withdraws the release. When one of those readers tries to open the copy afterwards, the read is refused.
The question the example answers: how do you record a copy, a limited release, and a withdrawal so that each holder learns only what reached them, and a withdrawal stops later reads while earlier reads stay on record?
All holders, times, and content codes are synthetic test values.
What this example shows
Section titled “What this example shows”canwu-information, the information domain extension. It stores who produced, sent, read, and released a piece of information as typed domain records.InformationLifecycle::plan, which validates oneLifecycleRequestagainst the current records and returns anInformationMutationPlan: record mutations plus knowledge publications, each addressed to one holder (holder-relative knowledge).- Copy lineage through
ContentDerivationandRepresentationSourceEdge. - An audience fixed by its member list, count, and membership root
(
AudiencePayload), and a release (ReleasePayload) that moves fromActivetoWithdrawn. InformationPluginapplying two of those requests, the audience-release activation and the withdrawal, insideCanwu, each followed by snapshot restore, exact replay, and compact checkpoint restore. The other steps run only in the detached ledger.
Run it
Section titled “Run it”cargo run -p canwu-information --example confidential_copy_releaseThe program prints one line once every check has passed; a failed check stops it earlier:
confidential_copy_release: hidden-operation holder isolation, derived copy, restricted release, withdrawal, retained knowledge, authoritative save/load, exact replay, and compact reconstruction verifiedEach phrase names a group of checks in the source. “Hidden-operation holder
isolation” means that each read publishes a fact only to its reader, so no one
else learns of the relay’s hidden read, and that in the seeded run only
H-404 and H-505 gain knowledge from the release.
Walkthrough
Section titled “Walkthrough”The scenario is in
confidential_copy_release.rs;
shared helpers are in
support/mod.rs.
Holders are KnowledgeHolderRef::Person values, and times come from
SimTime::from_minutes.
| Holder | Role |
|---|---|
H-101 |
sender |
H-202 |
addressed recipient |
H-303 |
relay; reads and copies the original, then publishes the copy |
H-404, H-505 |
audience members |
H-606 |
unrelated holder, used to check that no fact reaches it |
1. Plan and apply each request
Section titled “1. Plan and apply each request”DetachedCaseLedger::plan_and_apply keeps records in a plain map outside
Canwu, so each step is easy to inspect:
let plan = InformationLifecycle::plan( &self.record_set()?, request, InformationLimitsV1::canonical(),)?;self.apply(&plan)?;self.requests.push(request.clone());Ok(plan)plan alone validates without applying. The example uses it for requests
that must fail.
2. Create the original and start the delivery (minutes 0–7)
Section titled “2. Create the original and start the delivery (minutes 0–7)”The sender creates a Content record with code SYN-C4-17, a
Representation of it in format sealed_text_v1, and an Instance, the
physical carrier. A Channel with AddressedDelivery carries a Dispatch
aimed at the recipient:
target: DispatchTarget::Addressed(vec![destination_holder.clone()]),The dispatch moves from Prepared to Active, and its first
DeliveryAttempt names H-303 under the relay reference role.
3. The relay reads and copies (minutes 12–14)
Section titled “3. The relay reads and copies (minutes 12–14)”The relay’s RecordAccess cites the carrier instance and the original
representation. The copy is new content whose derivation points back at the
original:
derivation: Some(ContentDerivation { operation: "select_and_copy".to_owned(), sources: vec![ContentSourceEdge { source: content, role: ContentSourceRole::Quotation, completeness_per_mille: 700, fidelity_per_mille: 970, }],}),The copy’s representation uses ContentRelation::DerivedContent with a
RepresentationSourceEdge to the original representation. The lifecycle
requires the source_content and parent_representation references to match
these edges exactly, and derived content to name the parent’s content as a
source (validate_content_lineage and validate_representation_lineage in
lifecycle.rs).
4. The original reaches the recipient (minutes 25–27)
Section titled “4. The original reaches the recipient (minutes 25–27)”The delivery attempt moves to Delivered, which publishes
RepresentationAvailable to the recipient. The recipient’s access cites the
delivery_attempt and dispatch, and the dispatch then moves to Completed.
An addressed dispatch can complete only after each recipient’s latest attempt
has a terminal status (validate_dispatch_completion). The copy from step 3
is a separate record chain.
5. Freeze an audience and release the copy (minutes 40–43)
Section titled “5. Freeze an audience and release the copy (minutes 40–43)”payload: AudiencePayload { membership: AudienceMembership::ExplicitMembers, resolved_at: minute(40), resolution_version: 1, resolved_boundary: None, member_count: 2, membership_root: audience_membership_root_v1( &[audience_member_a.clone(), audience_member_b.clone()], InformationLimitsV1::canonical(), )?,},The listed members must match member_count and membership_root. A
Release with ReleaseScope::Audience points at this audience and the copied
representation, with the relay as publisher. Moving it to Active publishes
one ReleaseAvailable fact per member. Member A then reads the copy at minute
43, citing the release and AudienceAccessEvidence::ListedMember.
The activation also runs inside Canwu through
verify_authoritative_operation_roundtrip. The helper seeds the ledger’s
records into a Scenario, sends the request to InformationPlugin as a plugin
command, settles boundaries until the operation is terminal, and compares
every record with the ledger. Then it reads each holder’s knowledge with
Canwu::admin_query_knowledge:
assert_authoritative_knowledge(canwu, &audience_member_a, &["release_available"])?;assert_authoritative_knowledge(canwu, &audience_member_b, &["release_available"])?;assert_authoritative_knowledge(canwu, &origin_holder, &[])?;assert_authoritative_knowledge(canwu, &unrelated_holder, &[])?;assert_authoritative_knowledge(canwu, &relay_holder, &[])The seeded simulation starts with the ledger’s records and an empty knowledge
store, so these checks see only the facts this one operation publishes. That is
why H-303, who read the original at minute 12, holds nothing here. The relay
published the release but is outside the audience, so this activation gives it
no fact either. The helper finishes by restoring a snapshot, replaying the
journal with Canwu::replay_from_journal, and restoring a compact checkpoint,
and compares each result with the original snapshot.
6. Withdraw and refuse a late read
Section titled “6. Withdraw and refuse a late read”proposed: ReleasePayload { status: ReleaseStatus::Withdrawn, active_at: Some(minute(42)), ..prepared_release},The withdrawal also runs through verify_authoritative_operation_roundtrip.
It updates only the release record and publishes nothing, so no holder has
knowledge in its seeded run. The check that matters is that the three earlier
access records still match the ledger. Withdrawn is terminal,
and a read through a release needs an Active release, so member B’s read at
minute 51 fails:
assert!(ledger.plan(&access_after_withdrawal).is_err());What to notice
Section titled “What to notice”- Reading (
Access) and copying are separate records. The copy is new content with its own lineage, and the original content keeps its record. - Every publication has one holder: the reader, the recipient, or each listed audience member.
- A release’s publisher receives a fact from it only as a listed audience member.
- Withdrawal controls later reads. Earlier access records, and the facts that cite them, stay in place.
- The detached ledger and
InformationPluginboth callInformationLifecycle::plan, so the same request yields the same records.
Try changing
Section titled “Try changing”- In the minute-43 read, replace
audience_member_awithunrelated_holder. Planning fails withaccess holder is not an explicit audience member. - Remove the
source_contentreference from the copied content’s binding. Planning fails withcontent source references must exactly match derivation source edges. - After the withdrawal, plan a
TransitionReleaseback toReleaseStatus::Activewithexpected_version: 3. It fails withterminal release cannot reopen.
Beyond the example
Section titled “Beyond the example”- Labels such as
sealed_text_v1,bounded_carrier,select_and_copy, andtemporary_readare application strings; the extension checks only that they are non-empty trimmed text. Timing, channel behavior, and consequences belong in your own plugin. - An audience resolved from groups uses
AudienceMembership::ResolvedGroupSnapshot; a reader can then prove membership against the root withAudienceAccessEvidence::MembershipProof. A release can also useReleaseScope::OpenAvailability, and an active release can end asExpired. - Routed correspondence builds letters that travel over routes on the same dispatch and delivery-attempt records.
Source
Section titled “Source”Open the runnable example
Read the shared example helpers
Read the lifecycle tests