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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
sealed-member salt effects. The vector metadata marks these identities as
E1 scaffolding identity, and API docs now state that deterministic member
blinding defaults are reproducibility tools, not unlinkability boundaries.
- `warp-core` now begins the third braids/strands roadmap goalpost with
append-only braid membership history. `BraidMembershipEntry` and
`Braid::membership_history()` expose accepted `MemberWoven` facts as a
read-only projection over the braid event log, while `frontier()` remains the
current membership projection.
- `warp-core` now enforces the v1 single-writer-head strand invariant through
both `Strand::new(...)` and `StrandRegistry::insert(...)`, and runtime strand
forking constructs the registered relation through the same constructor
Expand Down
40 changes: 40 additions & 0 deletions crates/warp-core/src/braid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,19 @@ pub enum BraidEvent {
},
}

/// One accepted braid membership fact, projected from append-only event history.
///
/// This is a read model, not an admission token. Constructing this value does
/// not weave a member into a braid; only [`Braid::apply`] can admit
/// [`BraidEvent::MemberWoven`] into braid history.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BraidMembershipEntry {
/// Reference to the strand, which may be revealed or sealed.
pub member_ref: BraidMemberRef,
/// Monotonically increasing sequence number from the accepted weave event.
pub sequence_num: u64,
}

/// Evolving state of a coordination braid reconstructed from its event log.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Braid {
Expand Down Expand Up @@ -304,7 +317,34 @@ impl Braid {
self.status
}

/// Returns the accepted append-only membership history.
///
/// The event log is the source of truth. This projection includes only
/// accepted [`BraidEvent::MemberWoven`] facts, so rejected duplicate,
/// incoherent, or late member events are never visible here.
#[must_use]
pub fn membership_history(&self) -> Vec<BraidMembershipEntry> {
self.events
.iter()
.filter_map(|event| match event {
BraidEvent::MemberWoven {
member_ref,
sequence_num,
} => Some(BraidMembershipEntry {
member_ref: *member_ref,
sequence_num: *sequence_num,
}),
BraidEvent::BraidCreated { .. }
| BraidEvent::SettlementFinalized { .. }
| BraidEvent::BraidCollapsed { .. } => None,
})
.collect()
}

/// Returns the current coordination frontier (active woven members).
///
/// This is the current projection over [`Self::membership_history`], not a
/// replacement for historical membership views.
#[must_use]
pub fn frontier(&self) -> &[BraidMemberRef] {
&self.members
Expand Down
4 changes: 3 additions & 1 deletion crates/warp-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ pub use proof::{
ObserverHonestyClaim, ProofEnvelope, ProofError, ProofKind, VerificationFailureCode,
};
// --- Braid Log types ---
pub use braid::{Braid, BraidError, BraidEvent, BraidStatus, BraidTransitionKind};
pub use braid::{
Braid, BraidError, BraidEvent, BraidMembershipEntry, BraidStatus, BraidTransitionKind,
};
// --- Retained boundary shell family (θ_tick, θ_braid) ---
pub use braid_shell::{
collapse_braid_shell, replay_braid_shell, BraidCoordinate, BraidMemberRef, BraidShell,
Expand Down
78 changes: 64 additions & 14 deletions crates/warp-core/tests/braid_public_api_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

use warp_core::{
make_strand_id, AuthorityDomainId, AuthorityDomainRef, Braid, BraidError, BraidEvent,
BraidMemberRef, BraidStatus, BraidTransitionKind, OriginId, ProofEnvelope, ProofError,
ProofKind,
BraidMemberRef, BraidMembershipEntry, BraidStatus, BraidTransitionKind, OriginId,
ProofEnvelope, ProofError, ProofKind,
};

fn authority_ref() -> AuthorityDomainRef {
Expand Down Expand Up @@ -39,21 +39,17 @@ fn crate_root_exports_braid_lifecycle_error_and_member_types() -> Result<(), Bra
}

#[test]
fn public_braid_transition_failures_are_typed() {
fn public_braid_transition_failures_are_typed() -> Result<(), BraidError> {
let member_ref = BraidMemberRef::Revealed(make_strand_id("typed-transition-member"));
let mut braid = Braid::new([0xAC; 32], authority_ref());

braid
.apply(BraidEvent::MemberWoven {
member_ref,
sequence_num: 0,
})
.expect("first member weave");
braid
.apply(BraidEvent::SettlementFinalized {
settlement_digest: [0x5E; 32],
})
.expect("settlement finalization");
braid.apply(BraidEvent::MemberWoven {
member_ref,
sequence_num: 0,
})?;
braid.apply(BraidEvent::SettlementFinalized {
settlement_digest: [0x5E; 32],
})?;

assert_eq!(
braid.apply(BraidEvent::MemberWoven {
Expand All @@ -65,6 +61,7 @@ fn public_braid_transition_failures_are_typed() {
status: BraidStatus::Finalized,
})
);
Ok(())
}

#[test]
Expand All @@ -80,6 +77,59 @@ fn public_braid_transition_display_is_human_facing() {
);
}

#[test]
fn public_braid_membership_history_is_append_only_event_history() -> Result<(), BraidError> {
let first = BraidMemberRef::Revealed(make_strand_id("history-member-a"));
let second = BraidMemberRef::Revealed(make_strand_id("history-member-b"));
let late = BraidMemberRef::Revealed(make_strand_id("history-member-late"));
let mut braid = Braid::new([0xAD; 32], authority_ref());

braid.apply(BraidEvent::MemberWoven {
member_ref: first,
sequence_num: 0,
})?;
assert_eq!(
braid.apply(BraidEvent::MemberWoven {
member_ref: first,
sequence_num: 1,
}),
Err(BraidError::DuplicateMember { member_ref: first })
);
braid.apply(BraidEvent::MemberWoven {
member_ref: second,
sequence_num: 1,
})?;
braid.apply(BraidEvent::SettlementFinalized {
settlement_digest: [0x5E; 32],
})?;
assert_eq!(
braid.apply(BraidEvent::MemberWoven {
member_ref: late,
sequence_num: 2,
}),
Err(BraidError::InvalidTransition {
transition: BraidTransitionKind::WeaveMember,
status: BraidStatus::Finalized,
})
);

assert_eq!(
braid.membership_history(),
vec![
BraidMembershipEntry {
member_ref: first,
sequence_num: 0,
},
BraidMembershipEntry {
member_ref: second,
sequence_num: 1,
},
]
);
assert_eq!(braid.frontier(), &[first, second]);
Ok(())
}

#[test]
fn public_proof_validation_failures_are_typed() {
let expected = [0x42; 32];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Goalpost 3: Historical Membership And Replay

Status: planned.
Status: active. GP3-S1 implemented.

Roadmap:
[`../braids-and-strands-roadmap.md`](../braids-and-strands-roadmap.md)
Expand Down Expand Up @@ -41,6 +41,38 @@ This goalpost includes:
- Braid Flight Recorder artifact shape;
- Causal X-Ray lower-mode output target.

## Implementation Design

GP3-S1 establishes the membership-history source of truth without implementing
the later coordinate, diff, replay, or recorder surfaces.

The implementation boundary is:

```text
BraidEvent::MemberWoven
-> BraidMembershipEntry
-> Braid::membership_history()
-> Braid::frontier()
```

`BraidEvent` remains the authoritative append-only log. A
`BraidMembershipEntry` is a read projection over one accepted `MemberWoven`
event; it is not an admission token and constructing it does not weave a
member. `Braid::membership_history()` projects accepted weave events from the
log in event order. Rejected duplicate, incoherent, mixed-posture, or late
member events never enter the log and therefore never appear in membership
history.

`Braid::frontier()` remains the current membership projection. Later slices
will add coordinate-based views and diffs over the same event-log facts instead
of treating current membership as the substrate.

This preserves three boundaries:

1. Admission happens through `Braid::apply(...)`.
2. Membership history is derived from accepted events.
3. Current frontier is one projection, not the historical model.

## Non-Goals

This goalpost does not include:
Expand Down
2 changes: 1 addition & 1 deletion docs/design/braids-and-strands-roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ Design:
Design:
[`goalpost-03-historical-membership-and-replay.md`](braids-and-strands-hardening/goalpost-03-historical-membership-and-replay.md)

- [ ] GP3-S1: Promote append-only braid membership into an implementation
- [x] GP3-S1: Promote append-only braid membership into an implementation
design.
- [ ] GP3-S2: Add historical membership views by coordinate or event sequence.
- [ ] GP3-S3: Add membership diff facts for added, ended, revealed, and
Expand Down
Loading