diff --git a/CHANGELOG.md b/CHANGELOG.md index 98cb3a29..eb55d6ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,21 @@ `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 exposes historical braid membership views through + `BraidMembershipCursor`, `Braid::current_membership_cursor()`, and + `Braid::membership_at(...)`. The cursor is a half-open event-log interval, so + later woven members do not appear in earlier membership views. +- `warp-core` now exposes `BraidMembershipDiff` through + `Braid::diff_membership(...)`, reporting deterministic added and ended + membership projection facts between historical cursors while reserving + revealed/concealed fact slots for future lawful disclosure evidence. +- `warp-core` now exposes retained braid shell replay/audit facts through + `audit_braid_shell(...)` and `BraidShellAudit`, including member verdicts, + support/frontier digests, posture floor, proof binding, and explicit + self-witness integrity-only posture. +- The braids/strands hardening docs now define the Braid Flight Recorder and + Causal X-Ray lower-mode output target over historical membership, diff, shell + audit, proof-binding, and witness-posture facts. - `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 diff --git a/crates/warp-core/src/braid.rs b/crates/warp-core/src/braid.rs index e81ef73b..98c8eec6 100644 --- a/crates/warp-core/src/braid.rs +++ b/crates/warp-core/src/braid.rs @@ -2,7 +2,7 @@ // © James Ross Ω FLYING•ROBOTS //! Evolving coordination log ("Braid") representation. -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use thiserror::Error; use crate::braid_shell::BraidMemberRef; @@ -140,6 +140,78 @@ pub struct BraidMembershipEntry { pub sequence_num: u64, } +/// Half-open cursor over accepted braid membership events. +/// +/// A cursor names the membership interval `[0, next_sequence_num)`. +/// `BraidMembershipCursor::new(0)` is the post-creation interval before any +/// member weave. `BraidMembershipCursor::new(2)` includes accepted member +/// weave sequence numbers `0` and `1`, but excludes sequence `2`. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct BraidMembershipCursor { + next_sequence_num: u64, +} + +impl BraidMembershipCursor { + /// Creates a membership cursor from the next weave sequence number. + #[must_use] + pub const fn new(next_sequence_num: u64) -> Self { + Self { next_sequence_num } + } + + /// Returns the first member weave sequence number excluded by this cursor. + #[must_use] + pub const fn next_sequence_num(self) -> u64 { + self.next_sequence_num + } +} + +/// One disclosure-posture change detected between membership projections. +/// +/// The current append-only braid event model cannot infer that one sealed +/// reference and one revealed reference identify the same member without +/// external unsealing material. This type reserves the fact vocabulary for +/// future lawful reveal/conceal events; current diffs leave these vectors +/// empty rather than guessing. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BraidMembershipDisclosureChange { + /// Membership fact before the disclosure posture changed. + pub from: BraidMembershipEntry, + /// Membership fact after the disclosure posture changed. + pub to: BraidMembershipEntry, +} + +/// Difference between two historical braid membership projections. +/// +/// This is a read model over accepted braid events, not an admission token and +/// not a mutation. `ended` means present at `from` and absent at `to`; it does +/// not imply that append-only history was rewritten. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BraidMembershipDiff { + /// Source cursor for the diff. + pub from: BraidMembershipCursor, + /// Target cursor for the diff. + pub to: BraidMembershipCursor, + /// Members present at `to` and absent at `from`. + pub added: Vec, + /// Members present at `from` and absent at `to`. + pub ended: Vec, + /// Members whose reference posture became revealed, when lawfully known. + pub revealed: Vec, + /// Members whose reference posture became concealed, when lawfully known. + pub concealed: Vec, +} + +impl BraidMembershipDiff { + /// Returns whether the diff carries no membership or disclosure changes. + #[must_use] + pub fn is_empty(&self) -> bool { + self.added.is_empty() + && self.ended.is_empty() + && self.revealed.is_empty() + && self.concealed.is_empty() + } +} + /// Evolving state of a coordination braid reconstructed from its event log. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Braid { @@ -341,6 +413,77 @@ impl Braid { .collect() } + /// Returns the cursor for the current membership projection. + /// + /// Saving this value before later member weaving lets callers request the + /// historical membership view that was current at that event-log interval. + #[must_use] + pub fn current_membership_cursor(&self) -> BraidMembershipCursor { + BraidMembershipCursor::new(self.next_sequence_num) + } + + /// Returns accepted membership facts visible at the requested cursor. + /// + /// This projection is derived from accepted [`BraidEvent::MemberWoven`] + /// facts. It does not read the current frontier, so later members do not + /// appear in earlier historical views. + #[must_use] + pub fn membership_at(&self, cursor: BraidMembershipCursor) -> Vec { + self.events + .iter() + .filter_map(|event| match event { + BraidEvent::MemberWoven { + member_ref, + sequence_num, + } if *sequence_num < cursor.next_sequence_num() => Some(BraidMembershipEntry { + member_ref: *member_ref, + sequence_num: *sequence_num, + }), + BraidEvent::BraidCreated { .. } + | BraidEvent::MemberWoven { .. } + | BraidEvent::SettlementFinalized { .. } + | BraidEvent::BraidCollapsed { .. } => None, + }) + .collect() + } + + /// Returns membership change facts between two historical cursors. + /// + /// The diff compares projections derived from accepted event-log facts. + /// It does not mutate braid state and does not infer sealed/revealed + /// equivalence without explicit disclosure evidence. + #[must_use] + pub fn diff_membership( + &self, + from: BraidMembershipCursor, + to: BraidMembershipCursor, + ) -> BraidMembershipDiff { + let from_membership = self.membership_at(from); + let to_membership = self.membership_at(to); + let from_index = membership_index(&from_membership); + let to_index = membership_index(&to_membership); + + let added = to_membership + .iter() + .copied() + .filter(|entry| !from_index.contains_key(&entry.member_ref)) + .collect(); + let ended = from_membership + .iter() + .copied() + .filter(|entry| !to_index.contains_key(&entry.member_ref)) + .collect(); + + BraidMembershipDiff { + from, + to, + added, + ended, + revealed: Vec::new(), + concealed: Vec::new(), + } + } + /// Returns the current coordination frontier (active woven members). /// /// This is the current projection over [`Self::membership_history`], not a @@ -351,6 +494,16 @@ impl Braid { } } +fn membership_index( + entries: &[BraidMembershipEntry], +) -> BTreeMap { + entries + .iter() + .copied() + .map(|entry| (entry.member_ref, entry)) + .collect() +} + const fn member_ref_is_sealed(member_ref: &BraidMemberRef) -> bool { matches!(member_ref, BraidMemberRef::Sealed { .. }) } diff --git a/crates/warp-core/src/braid_shell.rs b/crates/warp-core/src/braid_shell.rs index fd02ef60..ec38302e 100644 --- a/crates/warp-core/src/braid_shell.rs +++ b/crates/warp-core/src/braid_shell.rs @@ -928,6 +928,82 @@ pub struct BraidShellReplay { pub posture: CausalPosture, } +/// Stable proof-binding fact for braid shell audit output. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BraidProofBinding { + /// The audited shell carries no proof envelope. + Absent, + /// The audited shell carries a proof envelope that validated against the + /// shell witness digest. + Matched { + /// Proof envelope kind. + kind: crate::proof::ProofKind, + /// Canonical proof-envelope digest. + envelope_digest: Hash, + /// Public inputs hash carried by the proof envelope. + public_inputs_hash: Hash, + }, +} + +/// Witness posture surfaced by braid shell audit output. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum BraidWitnessPosture { + /// Current E1 self-witness: integrity-only local evidence, not independent + /// attestation. + SelfWitnessIntegrityOnly { + /// Witness digest bound into the shell. + digest: Hash, + }, +} + +/// One replay/audit fact for a braid shell member. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct BraidShellMemberAuditFact { + /// Reference to the member strand, revealed or sealed. + pub member_ref: BraidMemberRef, + /// Settlement verdict reproduced for this member. + pub verdict: MemberVerdict, + /// Member-level causal posture. + pub posture: CausalPosture, + /// Digest over the member's support-pin set. + pub support_pin_digest: Hash, + /// Digest over the member's fork basis facts. + pub basis_digest: Hash, + /// Digest over the realized parent frontier judged for this member. + pub frontier_digest: Hash, + /// Digest over contended footprint slots. + pub footprint_digest: Hash, + /// Digest over ordered claim identities. + pub claim_digest: Hash, + /// Digest over ordered per-claim decisions. + pub verdict_digest: Hash, +} + +/// Replay/audit facts reproduced from a retained braid shell. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BraidShellAudit { + /// Canonical shell digest audited. + pub shell_digest: Hash, + /// Canonical braid coordinate this shell lives at. + pub coordinate: BraidCoordinate, + /// Outcome arm reproduced from the shell. + pub outcome_kind: AdmissionOutcomeKind, + /// Settlement policy identity the act ran under. + pub policy_id: Hash, + /// Least-revealed member posture across the audited shell. + pub posture_floor: CausalPosture, + /// Revelation posture claimed by the shell itself. + pub shell_posture: CausalPosture, + /// Member refs in canonical settlement frontier order. + pub settlement_frontier: Vec, + /// Per-member replay/audit facts in canonical member order. + pub member_facts: Vec, + /// Proof binding status for the shell. + pub proof_binding: BraidProofBinding, + /// Witness posture for the shell. + pub witness_posture: BraidWitnessPosture, +} + /// Replays a braid-scope settlement outcome from retained shell records. /// /// Verifies digest integrity (member digests, witness digest, shell digest) @@ -944,6 +1020,91 @@ pub fn replay_braid_shell( digest: &Hash, records: &dyn BraidShellRecords, ) -> Result { + let shell = validated_shell_for_replay(digest, records)?; + Ok(BraidShellReplay { + outcome_kind: shell.outcome_kind(), + member_verdicts: shell + .members + .iter() + .map(|member| (member.member_ref, member.verdict)) + .collect(), + policy_id: shell.policy_id, + witness_digest: shell.witness_digest, + posture: shell.posture, + }) +} + +/// Returns stable replay/audit facts from a retained braid shell. +/// +/// The audit view validates the same retained shell and collapse-lineage +/// constraints as [`replay_braid_shell`]. It exposes support, frontier, proof, +/// posture, member-verdict, and witness facts without reopening member strand +/// histories. +/// +/// # Errors +/// +/// Returns [`BraidShellError`] when the shell is missing, fails +/// self-validation, or names a collapse parent that is absent or not plural. +pub fn audit_braid_shell( + digest: &Hash, + records: &dyn BraidShellRecords, +) -> Result { + let shell = validated_shell_for_replay(digest, records)?; + let posture_floor = shell + .members + .iter() + .map(|member| member.posture) + .min() + .unwrap_or(shell.posture); + let proof_binding = shell + .proof + .as_ref() + .map_or(BraidProofBinding::Absent, |proof| { + BraidProofBinding::Matched { + kind: proof.kind, + envelope_digest: proof.digest(), + public_inputs_hash: proof.public_inputs_hash, + } + }); + + Ok(BraidShellAudit { + shell_digest: shell.digest, + coordinate: shell.coordinate, + outcome_kind: shell.outcome_kind(), + policy_id: shell.policy_id, + posture_floor, + shell_posture: shell.posture, + settlement_frontier: shell + .members + .iter() + .map(|member| member.member_ref) + .collect(), + member_facts: shell + .members + .iter() + .map(|member| BraidShellMemberAuditFact { + member_ref: member.member_ref, + verdict: member.verdict, + posture: member.posture, + support_pin_digest: member.support_pin_digest, + basis_digest: member.basis_digest, + frontier_digest: member.frontier_digest, + footprint_digest: member.footprint_digest, + claim_digest: member.claim_digest, + verdict_digest: member.verdict_digest, + }) + .collect(), + proof_binding, + witness_posture: BraidWitnessPosture::SelfWitnessIntegrityOnly { + digest: shell.witness_digest, + }, + }) +} + +fn validated_shell_for_replay<'a>( + digest: &Hash, + records: &'a dyn BraidShellRecords, +) -> Result<&'a BraidShell, BraidShellError> { let shell = records .shell(digest) .ok_or(BraidShellError::ShellNotFound { digest: *digest })?; @@ -979,17 +1140,7 @@ pub fn replay_braid_shell( }); } } - Ok(BraidShellReplay { - outcome_kind: shell.outcome_kind(), - member_verdicts: shell - .members - .iter() - .map(|member| (member.member_ref, member.verdict)) - .collect(), - policy_id: shell.policy_id, - witness_digest: shell.witness_digest, - posture: shell.posture, - }) + Ok(shell) } /// Deterministic reason code: collapse attempted without a named policy. @@ -1409,6 +1560,82 @@ mod tests { assert_eq!(replay.posture, CausalPosture::AuthorOnly); } + #[test] + fn replay_audit_reports_member_proof_support_frontier_and_witness_facts() { + use crate::proof::{ProofEnvelope, ProofKind}; + + let members = vec![member("audit-member", MemberVerdict::Plural)]; + let temp_shell = BraidShell::assemble( + wl(1), + basis_ref(), + members.clone(), + [0x5E; 32], + BraidShellOutcome::Plural { + alternative_ids: vec![[0x31; 32]], + }, + CausalPosture::AuthorOnly, + ) + .unwrap(); + let proof = ProofEnvelope { + kind: ProofKind::ReplayTrace, + proof_bytes: vec![1, 2, 3], + public_inputs_hash: temp_shell.witness_digest, + }; + let shell = BraidShell::assemble_with_proof( + wl(1), + basis_ref(), + members, + [0x5E; 32], + BraidShellOutcome::Plural { + alternative_ids: vec![[0x31; 32]], + }, + CausalPosture::AuthorOnly, + Some(proof), + ) + .unwrap(); + let digest = shell.digest; + let expected_member = shell.members[0].clone(); + let expected_proof_digest = shell.proof.as_ref().unwrap().digest(); + let expected_witness = shell.witness_digest; + let records = Records::with([shell]); + + let audit = audit_braid_shell(&digest, &records).unwrap(); + + assert_eq!(audit.shell_digest, digest); + assert_eq!(audit.outcome_kind, AdmissionOutcomeKind::Plural); + assert_eq!(audit.posture_floor, CausalPosture::AuthorOnly); + assert_eq!(audit.shell_posture, CausalPosture::AuthorOnly); + assert_eq!(audit.settlement_frontier, vec![expected_member.member_ref]); + assert_eq!( + audit.member_facts, + vec![BraidShellMemberAuditFact { + member_ref: expected_member.member_ref, + verdict: expected_member.verdict, + posture: expected_member.posture, + support_pin_digest: expected_member.support_pin_digest, + basis_digest: expected_member.basis_digest, + frontier_digest: expected_member.frontier_digest, + footprint_digest: expected_member.footprint_digest, + claim_digest: expected_member.claim_digest, + verdict_digest: expected_member.verdict_digest, + }] + ); + assert_eq!( + audit.proof_binding, + BraidProofBinding::Matched { + kind: ProofKind::ReplayTrace, + envelope_digest: expected_proof_digest, + public_inputs_hash: expected_witness, + } + ); + assert_eq!( + audit.witness_posture, + BraidWitnessPosture::SelfWitnessIntegrityOnly { + digest: expected_witness, + } + ); + } + #[test] fn tampering_with_policy_posture_or_verdict_fails_replay() { let shell = plural_shell(vec![member("member-a", MemberVerdict::Plural)]); diff --git a/crates/warp-core/src/lib.rs b/crates/warp-core/src/lib.rs index 9c346385..34c9154e 100644 --- a/crates/warp-core/src/lib.rs +++ b/crates/warp-core/src/lib.rs @@ -261,14 +261,16 @@ pub use proof::{ }; // --- Braid Log types --- pub use braid::{ - Braid, BraidError, BraidEvent, BraidMembershipEntry, BraidStatus, BraidTransitionKind, + Braid, BraidError, BraidEvent, BraidMembershipCursor, BraidMembershipDiff, + BraidMembershipDisclosureChange, BraidMembershipEntry, BraidStatus, BraidTransitionKind, }; // --- Retained boundary shell family (θ_tick, θ_braid) --- pub use braid_shell::{ - collapse_braid_shell, replay_braid_shell, BraidCoordinate, BraidMemberRef, BraidShell, - BraidShellError, BraidShellMember, BraidShellMemberQuery, BraidShellOutcome, BraidShellQuery, - BraidShellRecords, BraidShellReplay, CollapsePolicy, CollapseResult, MemberVerdict, - RetainedBoundaryKind, RetainedBoundaryRecord, BRAID_SHELL_VERSION, + audit_braid_shell, collapse_braid_shell, replay_braid_shell, BraidCoordinate, BraidMemberRef, + BraidProofBinding, BraidShell, BraidShellAudit, BraidShellError, BraidShellMember, + BraidShellMemberAuditFact, BraidShellMemberQuery, BraidShellOutcome, BraidShellQuery, + BraidShellRecords, BraidShellReplay, BraidWitnessPosture, CollapsePolicy, CollapseResult, + MemberVerdict, RetainedBoundaryKind, RetainedBoundaryRecord, BRAID_SHELL_VERSION, COLLAPSE_WITHOUT_POLICY_REASON, }; pub use neighborhood::{ diff --git a/crates/warp-core/tests/braid_public_api_tests.rs b/crates/warp-core/tests/braid_public_api_tests.rs index 1bcf9cf7..68d0e1f2 100644 --- a/crates/warp-core/tests/braid_public_api_tests.rs +++ b/crates/warp-core/tests/braid_public_api_tests.rs @@ -5,8 +5,8 @@ use warp_core::{ make_strand_id, AuthorityDomainId, AuthorityDomainRef, Braid, BraidError, BraidEvent, - BraidMemberRef, BraidMembershipEntry, BraidStatus, BraidTransitionKind, OriginId, - ProofEnvelope, ProofError, ProofKind, + BraidMemberRef, BraidMembershipCursor, BraidMembershipDiff, BraidMembershipEntry, BraidStatus, + BraidTransitionKind, OriginId, ProofEnvelope, ProofError, ProofKind, }; fn authority_ref() -> AuthorityDomainRef { @@ -130,6 +130,130 @@ fn public_braid_membership_history_is_append_only_event_history() -> Result<(), Ok(()) } +#[test] +fn public_braid_membership_views_are_historical_by_event_cursor() -> Result<(), BraidError> { + let first = BraidMemberRef::Revealed(make_strand_id("cursor-member-a")); + let second = BraidMemberRef::Revealed(make_strand_id("cursor-member-b")); + let third = BraidMemberRef::Revealed(make_strand_id("cursor-member-c")); + let mut braid = Braid::new([0xAE; 32], authority_ref()); + + assert_eq!( + braid.membership_at(BraidMembershipCursor::new(0)), + Vec::::new() + ); + + braid.apply(BraidEvent::MemberWoven { + member_ref: first, + sequence_num: 0, + })?; + let after_first = braid.current_membership_cursor(); + + braid.apply(BraidEvent::MemberWoven { + member_ref: second, + sequence_num: 1, + })?; + let before_third = braid.current_membership_cursor(); + + braid.apply(BraidEvent::MemberWoven { + member_ref: third, + sequence_num: 2, + })?; + + assert_eq!( + braid.membership_at(after_first), + vec![BraidMembershipEntry { + member_ref: first, + sequence_num: 0, + }] + ); + assert_eq!( + braid.membership_at(before_third), + vec![ + BraidMembershipEntry { + member_ref: first, + sequence_num: 0, + }, + BraidMembershipEntry { + member_ref: second, + sequence_num: 1, + }, + ] + ); + assert_eq!(braid.frontier(), &[first, second, third]); + assert_eq!( + braid.membership_at(braid.current_membership_cursor()), + braid.membership_history() + ); + Ok(()) +} + +#[test] +fn public_braid_membership_diff_reports_projection_facts() -> Result<(), BraidError> { + let first = BraidMemberRef::Revealed(make_strand_id("diff-member-a")); + let second = BraidMemberRef::Revealed(make_strand_id("diff-member-b")); + let third = BraidMemberRef::Revealed(make_strand_id("diff-member-c")); + let mut braid = Braid::new([0xAF; 32], authority_ref()); + + braid.apply(BraidEvent::MemberWoven { + member_ref: first, + sequence_num: 0, + })?; + let after_first = braid.current_membership_cursor(); + braid.apply(BraidEvent::MemberWoven { + member_ref: second, + sequence_num: 1, + })?; + braid.apply(BraidEvent::MemberWoven { + member_ref: third, + sequence_num: 2, + })?; + let after_third = braid.current_membership_cursor(); + + assert_eq!( + braid.diff_membership(after_first, after_third), + BraidMembershipDiff { + from: after_first, + to: after_third, + added: vec![ + BraidMembershipEntry { + member_ref: second, + sequence_num: 1, + }, + BraidMembershipEntry { + member_ref: third, + sequence_num: 2, + }, + ], + ended: Vec::new(), + revealed: Vec::new(), + concealed: Vec::new(), + } + ); + + assert_eq!( + braid.diff_membership(after_third, after_first), + BraidMembershipDiff { + from: after_third, + to: after_first, + added: Vec::new(), + ended: vec![ + BraidMembershipEntry { + member_ref: second, + sequence_num: 1, + }, + BraidMembershipEntry { + member_ref: third, + sequence_num: 2, + }, + ], + revealed: Vec::new(), + concealed: Vec::new(), + } + ); + assert_eq!(braid.frontier(), &[first, second, third]); + Ok(()) +} + #[test] fn public_proof_validation_failures_are_typed() { let expected = [0x42; 32]; diff --git a/docs/design/braids-and-strands-hardening/goalpost-03-historical-membership-and-replay.md b/docs/design/braids-and-strands-hardening/goalpost-03-historical-membership-and-replay.md index 85889b95..c97f8a43 100644 --- a/docs/design/braids-and-strands-hardening/goalpost-03-historical-membership-and-replay.md +++ b/docs/design/braids-and-strands-hardening/goalpost-03-historical-membership-and-replay.md @@ -3,7 +3,7 @@ # Goalpost 3: Historical Membership And Replay -Status: active. GP3-S1 implemented. +Status: implemented. Roadmap: [`../braids-and-strands-roadmap.md`](../braids-and-strands-roadmap.md) @@ -43,8 +43,9 @@ This goalpost includes: ## Implementation Design -GP3-S1 establishes the membership-history source of truth without implementing -the later coordinate, diff, replay, or recorder surfaces. +GP3-S1 established the membership-history source of truth. GP3-S2 through +GP3-S5 now layer historical cursors, diffs, replay facts, and recorder output +over that source without changing the admission path. The implementation boundary is: @@ -63,15 +64,137 @@ 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: +`Braid::frontier()` remains the current membership projection. +`BraidMembershipCursor` names a half-open membership interval by event sequence: +`[0, next_sequence_num)`. `Braid::current_membership_cursor()` captures the +current cursor, and `Braid::membership_at(...)` projects accepted +`MemberWoven` facts visible at that historical cursor. This intentionally does +not reuse `BraidCoordinate`, which is already the shell-identity coordinate. +`Braid::diff_membership(...)` compares two cursor projections and returns +deterministic added and ended membership facts. The diff shape reserves +revealed and concealed fact slots, but the current append-only event model +does not infer sealed/revealed equivalence without explicit disclosure +evidence. +`audit_braid_shell(...)` validates the same retained shell and lineage +constraints as replay, then emits stable replay/audit facts for member +verdicts, member support and frontier digests, posture floor, proof binding, +and the current self-witness integrity-only posture. + +The Braid Flight Recorder and Causal X-Ray lower-mode target are defined below +as stable design/output surfaces over these fact APIs. This goalpost does not +ship a CLI command. + +This preserves six 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. +4. Event-log membership cursors are distinct from braid shell coordinates. +5. Reveal/conceal facts require explicit evidence, not reference-shape guesses. +6. Audit facts do not reopen member strand histories. + +## Flight Recorder And Causal X-Ray Output + +The Braid Flight Recorder is a durable audit artifact shape, not a separate +admission path. It records the interpreted path: + +```text +event log +-> membership projection +-> membership diff +-> shell assembly +-> proof binding +-> witness reading +-> replay verdict +``` + +The recorder consumes existing fact surfaces: + +| Stage | Source API | +| --------------------- | ---------------------------------------------------- | +| event log | `Braid::events()` | +| membership projection | `Braid::membership_at(...)` | +| membership diff | `Braid::diff_membership(...)` | +| shell assembly | retained `BraidShell` | +| proof binding | `BraidShellAudit::proof_binding` | +| witness reading | `BraidShellAudit::witness_posture` | +| replay verdict | `replay_braid_shell(...)` / `audit_braid_shell(...)` | + +The lower-mode Causal X-Ray target is a stable, assertion-friendly object that +can later back a command such as: + +```text +echo braid inspect +``` + +No command ships in this slice. The current target output is: + +```json +{ + "artifact": "braid-flight-recorder", + "version": 1, + "braid": { + "id": "hex:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "status": "active", + "membership_cursor": { + "next_sequence_num": 3 + } + }, + "membership": { + "projection": [ + { + "reference": "revealed", + "sequence_num": 0 + }, + { + "reference": "revealed", + "sequence_num": 1 + }, + { + "reference": "revealed", + "sequence_num": 2 + } + ], + "diff": { + "from_next_sequence_num": 1, + "to_next_sequence_num": 3, + "added": [1, 2], + "ended": [], + "revealed": [], + "concealed": [] + } + }, + "shell": { + "digest": "hex:bsh", + "coordinate": "hex:bc", + "outcome": "plural", + "posture_floor": "author_only", + "shell_posture": "author_only", + "settlement_frontier": ["revealed:0", "revealed:1", "revealed:2"] + }, + "members": [ + { + "reference": "revealed", + "verdict": "plural", + "support_pin_digest": "hex:21", + "frontier_digest": "hex:23" + } + ], + "proof": { + "binding": "matched", + "kind": "replay_trace" + }, + "witness": { + "kind": "self_witness", + "attestation": "integrity_only" + }, + "warnings": ["self_witness_is_not_independent_attestation"] +} +``` + +Fixture fields use symbolic digest strings because this is lower-mode output, +not a golden identity vector. Golden identity changes remain governed by +Goalpost 2 vector rules. ## Non-Goals @@ -80,7 +203,8 @@ This goalpost does not include: - settlement-as-merge semantics; - exposing sealed source chains beyond the requested aperture; - external witness backend implementation; -- plurality law registry execution. +- plurality law registry execution; +- shipping a Causal X-Ray CLI command. ## Slices diff --git a/docs/design/braids-and-strands-roadmap.md b/docs/design/braids-and-strands-roadmap.md index 4b8d392d..db95af2c 100644 --- a/docs/design/braids-and-strands-roadmap.md +++ b/docs/design/braids-and-strands-roadmap.md @@ -90,12 +90,12 @@ Design: - [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 +- [x] GP3-S2: Add historical membership views by coordinate or event sequence. +- [x] GP3-S3: Add membership diff facts for added, ended, revealed, and concealed changes. -- [ ] GP3-S4: Add replay/audit facts for member verdicts, posture floors, +- [x] GP3-S4: Add replay/audit facts for member verdicts, posture floors, proof binding, retained support, frontier, and witness posture. -- [ ] GP3-S5: Define the Braid Flight Recorder and Causal X-Ray lower-mode +- [x] GP3-S5: Define the Braid Flight Recorder and Causal X-Ray lower-mode output. ### Goalpost 4: Witness Receipts And Sealed Capabilities @@ -437,8 +437,8 @@ Work: 3. Add historical membership views by braid coordinate or event sequence. 4. Preserve current membership as a projection. 5. Keep sealed member references lawful at historical coordinates. -6. Add `braid.diff_membership(from_coordinate, to_coordinate)` as a design - target for replay, UI, and audit. +6. Add `braid.diff_membership(from_cursor, to_cursor)` as a design target for + replay, UI, and audit. Acceptance: