From 36a9d2a2d4d84e36b02affe0ed0ac2396ddc0bf8 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 3 May 2026 12:44:35 -0700 Subject: [PATCH 1/4] test: red witnessed suffix posture constructors --- .../warp-core/src/witnessed_suffix_tests.rs | 96 +++++++++++++- .../design.md | 125 ++++++++++++++++++ ...RM_witnessed-suffix-admission-hardening.md | 73 ---------- 3 files changed, 219 insertions(+), 75 deletions(-) create mode 100644 docs/design/0012-witnessed-suffix-posture-canonicalization/design.md delete mode 100644 docs/method/backlog/inbox/PLATFORM_witnessed-suffix-admission-hardening.md diff --git a/crates/warp-core/src/witnessed_suffix_tests.rs b/crates/warp-core/src/witnessed_suffix_tests.rs index 6670d1ef..59b1af62 100644 --- a/crates/warp-core/src/witnessed_suffix_tests.rs +++ b/crates/warp-core/src/witnessed_suffix_tests.rs @@ -9,8 +9,8 @@ use crate::{ StrandBasisReport, StrandDivergenceFootprint, StrandOverlapRevalidation, StrandRevalidationState, WarpId, WitnessedSuffixAdmissionContext, WitnessedSuffixAdmissionOutcome, WitnessedSuffixAdmissionRequest, - WitnessedSuffixAdmissionResponse, WitnessedSuffixLocalAdmissionPosture, WitnessedSuffixShell, - WorldlineId, WorldlineTick, + WitnessedSuffixAdmissionResponse, WitnessedSuffixLocalAdmissionPosture, + WitnessedSuffixLocalAdmissionPostureError, WitnessedSuffixShell, WorldlineId, WorldlineTick, }; fn worldline(seed: u8) -> WorldlineId { @@ -257,6 +257,98 @@ fn witnessed_suffix_core_response_converts_obstructed_outcome_to_abi() { )); } +#[test] +fn witnessed_suffix_local_posture_admissible_constructor_canonicalizes_refs() { + let posture = WitnessedSuffixLocalAdmissionPosture::admissible(vec![ + provenance_ref(30, 12), + provenance_ref(30, 10), + ]) + .expect("distinct admitted refs should construct"); + + assert_eq!( + posture, + WitnessedSuffixLocalAdmissionPosture::Admissible { + admitted_refs: vec![provenance_ref(30, 10), provenance_ref(30, 12)], + } + ); +} + +#[test] +fn witnessed_suffix_local_posture_staged_constructor_canonicalizes_refs() { + let posture = WitnessedSuffixLocalAdmissionPosture::staged(vec![ + provenance_ref(32, 12), + provenance_ref(32, 11), + ]) + .expect("distinct staged refs should construct"); + + assert_eq!( + posture, + WitnessedSuffixLocalAdmissionPosture::Staged { + staged_refs: vec![provenance_ref(32, 11), provenance_ref(32, 12)], + } + ); +} + +#[test] +fn witnessed_suffix_local_posture_plural_constructor_canonicalizes_refs() { + let posture = WitnessedSuffixLocalAdmissionPosture::plural(vec![ + provenance_ref(34, 13), + provenance_ref(33, 12), + ]) + .expect("distinct plural refs should construct"); + + assert_eq!( + posture, + WitnessedSuffixLocalAdmissionPosture::Plural { + candidate_refs: vec![provenance_ref(33, 12), provenance_ref(34, 13)], + } + ); +} + +#[test] +fn witnessed_suffix_local_posture_constructors_reject_duplicate_refs() { + let duplicate_ref = provenance_ref(30, 10); + + for duplicate_result in [ + WitnessedSuffixLocalAdmissionPosture::admissible(vec![duplicate_ref, duplicate_ref]), + WitnessedSuffixLocalAdmissionPosture::staged(vec![duplicate_ref, duplicate_ref]), + WitnessedSuffixLocalAdmissionPosture::plural(vec![duplicate_ref, duplicate_ref]), + ] { + assert_eq!( + duplicate_result, + Err( + WitnessedSuffixLocalAdmissionPostureError::DuplicateProvenanceRef { + provenance_ref: duplicate_ref, + } + ) + ); + } +} + +#[test] +fn witnessed_suffix_local_posture_conflict_constructor_names_all_evidence() { + let overlap_revalidation = StrandOverlapRevalidation::Conflict { + overlapping_slots: vec![node_slot("constructor-overlap-a")], + }; + + let posture = WitnessedSuffixLocalAdmissionPosture::conflict( + ConflictReason::ParentFootprintOverlap, + provenance_ref(35, 14), + [36; 32], + Some(overlap_revalidation.clone()), + ); + + assert_eq!( + posture, + WitnessedSuffixLocalAdmissionPosture::Conflict { + reason: ConflictReason::ParentFootprintOverlap, + source_ref: provenance_ref(35, 14), + evidence_digest: [36; 32], + overlap_revalidation: Some(overlap_revalidation), + } + ); +} + #[test] fn witnessed_suffix_evaluator_admits_clean_suffix() { let request = request(); diff --git a/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md new file mode 100644 index 00000000..eba946c9 --- /dev/null +++ b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md @@ -0,0 +1,125 @@ + + + +# 0012 — Witnessed suffix posture canonicalization + +_Add a RED fence for named canonical construction of witnessed suffix local +admission postures._ + +Legend: [PLATFORM](../../method/legends/PLATFORM.md) + +Depends on: + +- [Witnessed suffix admission evaluator](../../design/witnessed-suffix-admission-evaluator.md) +- [Continuum runtime and CAS readings](../../design/continuum-runtime-and-cas-readings.md) +- [0011 — Optic and observer runtime doctrine](../0011-optic-observer-runtime-doctrine/design.md) + +Source card: + +- `docs/method/backlog/inbox/PLATFORM_witnessed-suffix-admission-hardening.md` + +## Why this cycle exists + +PR #323 made the witnessed suffix local admission evaluator deterministic and +honest around source suffix evidence, basis resolution, and output posture +ordering. The evaluator now canonicalizes posture vectors before returning ABI +visible outcomes. + +That is not enough once future call sites construct +`WitnessedSuffixLocalAdmissionPosture` directly. Raw enum construction can +reintroduce caller-order leakage or duplicate provenance refs before the +evaluator gets a chance to normalize the shape. This cycle starts by making +that missing named construction path executable as RED. + +## Human users / jobs / hills + +### Primary human users + +Maintainers reviewing witnessed suffix admission, settlement, and Continuum +runtime work. + +### Human jobs + +1. Review one focused RED and decide whether the expected posture constructor + shape is acceptable. +2. Confirm the slice does not reopen transport, sync, ABI, or broad Continuum + design. + +### Human hill + +A maintainer can see the canonical posture construction contract before any +production helper is added. + +## Agent users / jobs / hills + +### Primary agent users + +Future coding agents extending witnessed suffix admission call sites. + +### Agent jobs + +1. Use named posture constructors instead of hand-building posture vectors. +2. Programmatically determine that posture refs are canonicalized and duplicate + refs are rejected. + +### Agent hill + +An agent can run a single warp-core witnessed suffix test target and see the +missing canonical constructor contract fail for the intended reason. + +## Human playback + +1. The human reads the RED tests in + `crates/warp-core/src/witnessed_suffix_tests.rs`. +2. The tests name `admissible`, `staged`, `plural`, and `conflict` constructor + paths on `WitnessedSuffixLocalAdmissionPosture`. +3. The human can decide the next GREEN implementation without inspecting + transport or ABI code. + +## Agent playback + +1. The agent runs `cargo test -p warp-core --lib witnessed_suffix`. +2. The command fails because the named posture constructors and duplicate-ref + error type do not exist yet. +3. The agent determines the next implementation is local to warp-core + witnessed suffix posture construction. + +## Implementation outline + +1. RED only: add tests for named canonical construction of admissible, staged, + plural, and conflict local postures. +2. RED only: move the backlog card into this design packet. +3. Stop before implementing production helpers. + +## Tests to write first + +- constructor canonicalizes admissible refs +- constructor canonicalizes staged refs +- constructor canonicalizes plural candidate refs +- constructors reject duplicate refs with a named error +- conflict constructor names reason, source ref, digest, and overlap evidence + +## Risks / unknowns + +- The constructor names may change during GREEN. If so, update only the tests + and design wording in the same narrow slice. +- Duplicate handling may need a wider posture validation policy if empty ref + vectors also become invalid. This RED only demands duplicate rejection. +- Existing direct enum construction in tests remains as fixture setup until the + GREEN cycle decides whether to migrate it. + +## Postures + +- **Accessibility:** Not applicable; this is Rust API/test surface only. +- **Localization:** Not applicable; no user-facing strings are introduced. +- **Agent inspectability:** The RED names exact constructor paths and expected + failure so future agents can implement without broad repo exploration. + +## Non-goals + +- Do not implement the constructors in this RED slice. +- Do not change transport, sync, import execution, or ABI shape. +- Do not redesign witnessed suffix admission. +- Do not implement Continuum proof, IPA, or commitment machinery. +- Do not audit every admission or settlement vector in this slice. +- Do not weaken the evaluator's existing obstruction posture. diff --git a/docs/method/backlog/inbox/PLATFORM_witnessed-suffix-admission-hardening.md b/docs/method/backlog/inbox/PLATFORM_witnessed-suffix-admission-hardening.md deleted file mode 100644 index 40ce576b..00000000 --- a/docs/method/backlog/inbox/PLATFORM_witnessed-suffix-admission-hardening.md +++ /dev/null @@ -1,73 +0,0 @@ - - - -# Witnessed suffix admission hardening - -Status: inbox. - -Source: PR #323 retrospective follow-up. - -## Why now - -PR #323 hardened the witnessed suffix local admission evaluator around -deterministic posture output, canonical source entries, resolved-basis -classification, and missing local digest evidence. - -Those fixes should not turn into a wider PR-review workstream, but the review -left useful backlog fuel. Future call sites may construct -`WitnessedSuffixLocalAdmissionPosture` outside the current tests. Obstruction -digest domains may grow beyond the local evaluator family. Other -admission-shaped responses may still expose caller-order leakage at an ABI -boundary. Test fixtures that look clean can also accidentally encode malformed -causal coordinates. - -This card captures that follow-up work without reopening the current evaluator -scope. - -## What it should look like - -- Add canonical constructors or validation helpers for - `WitnessedSuffixLocalAdmissionPosture` before additional call sites build - postures directly. -- Add an explicit design paragraph for canonical source suffix invariants: - - every source entry belongs to the shell source worldline - - provenance refs are in canonical order - - entries sit inside the claimed suffix bounds - - the suffix carries either witness-backed entries or boundary witness - evidence sufficient for an honest obstruction -- Document obstruction digest domains and how their domain/version strings - relate to future Continuum evidence hashes. -- Audit admission and settlement response vectors for ABI-visible caller-order - leakage. -- Strengthen fixture discipline so positive "clean" fixtures cannot encode - malformed causal coordinates by accident. Malformed coordinates should appear - only in negative tests that name the invariant they violate. - -## Done looks like - -- Posture construction has a named canonical path, or direct construction is - intentionally constrained and documented. -- The witnessed suffix design docs name the canonical source suffix invariants - explicitly. -- Obstruction digest domain/version policy is documented before more digest - families appear. -- ABI-visible admission and settlement vectors are audited, with canonicalized - vectors fixed or caller-law ordering documented. -- Test fixture helpers make valid causal coordinates the default. - -## Repo evidence - -- `crates/warp-core/src/witnessed_suffix.rs` -- `crates/warp-core/src/witnessed_suffix_tests.rs` -- `crates/echo-wasm-abi/src/witnessed_suffix_tests.rs` -- `docs/design/witnessed-suffix-admission-evaluator.md` -- `docs/design/continuum-runtime-and-cas-readings.md` -- PR #323: `https://github.com/flyingrobots/echo/pull/323` - -## Non-goals - -- Do not reopen PR #323's evaluator implementation without a new RED. -- Do not redesign the witnessed suffix ABI. -- Do not add Continuum proof, IPA, or commitment machinery. -- Do not change transport, sync, or import execution behavior. -- Do not weaken the evaluator's existing obstruction posture. From bef04d35402d6f49c1473cb1e23ba85913193d7b Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 3 May 2026 13:20:04 -0700 Subject: [PATCH 2/4] feat: add canonical witnessed suffix posture constructors --- crates/warp-core/src/lib.rs | 3 +- crates/warp-core/src/witnessed_suffix.rs | 75 +++++++++++++++++++ .../warp-core/src/witnessed_suffix_tests.rs | 45 +++++------ 3 files changed, 95 insertions(+), 28 deletions(-) diff --git a/crates/warp-core/src/lib.rs b/crates/warp-core/src/lib.rs index c4389756..29cef2fc 100644 --- a/crates/warp-core/src/lib.rs +++ b/crates/warp-core/src/lib.rs @@ -252,7 +252,8 @@ pub use warp_state::{WarpInstance, WarpState}; pub use witnessed_suffix::{ evaluate_witnessed_suffix_admission, WitnessedSuffixAdmissionContext, WitnessedSuffixAdmissionOutcome, WitnessedSuffixAdmissionRequest, - WitnessedSuffixAdmissionResponse, WitnessedSuffixLocalAdmissionPosture, WitnessedSuffixShell, + WitnessedSuffixAdmissionResponse, WitnessedSuffixLocalAdmissionPosture, + WitnessedSuffixLocalAdmissionPostureError, WitnessedSuffixShell, }; pub use worldline::{ ApplyError, AtomWrite, AtomWriteSet, HashTriplet, OutputFrameSet, WorldlineId, diff --git a/crates/warp-core/src/witnessed_suffix.rs b/crates/warp-core/src/witnessed_suffix.rs index 42fc35e1..212816fe 100644 --- a/crates/warp-core/src/witnessed_suffix.rs +++ b/crates/warp-core/src/witnessed_suffix.rs @@ -8,6 +8,7 @@ use blake3::Hasher; use echo_wasm_abi::{encode_cbor, kernel_port as abi}; +use thiserror::Error; use crate::attachment::{AttachmentOwner, AttachmentPlane}; use crate::clock::WorldlineTick; @@ -139,6 +140,17 @@ pub trait WitnessedSuffixAdmissionContext { ) -> WitnessedSuffixLocalAdmissionPosture; } +/// Error returned when constructing a canonical local admission posture fails. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Error)] +pub enum WitnessedSuffixLocalAdmissionPostureError { + /// A provenance coordinate appeared more than once in a posture vector. + #[error("duplicate witnessed suffix local admission provenance ref: {provenance_ref:?}")] + DuplicateProvenanceRef { + /// Duplicate provenance coordinate. + provenance_ref: ProvenanceRef, + }, +} + /// Local posture reported by the read-only admission context. #[derive(Clone, Debug, PartialEq, Eq)] pub enum WitnessedSuffixLocalAdmissionPosture { @@ -170,6 +182,51 @@ pub enum WitnessedSuffixLocalAdmissionPosture { }, } +impl WitnessedSuffixLocalAdmissionPosture { + /// Builds an admissible posture with canonical admitted refs. + pub fn admissible( + admitted_refs: Vec, + ) -> Result { + Ok(Self::Admissible { + admitted_refs: canonical_unique_provenance_refs(admitted_refs)?, + }) + } + + /// Builds a staged posture with canonical staged refs. + pub fn staged( + staged_refs: Vec, + ) -> Result { + Ok(Self::Staged { + staged_refs: canonical_unique_provenance_refs(staged_refs)?, + }) + } + + /// Builds a plural posture with canonical candidate refs. + pub fn plural( + candidate_refs: Vec, + ) -> Result { + Ok(Self::Plural { + candidate_refs: canonical_unique_provenance_refs(candidate_refs)?, + }) + } + + /// Builds a conflict posture from named conflict evidence. + #[must_use] + pub fn conflict( + reason: ConflictReason, + source_ref: ProvenanceRef, + evidence_digest: Hash, + overlap_revalidation: Option, + ) -> Self { + Self::Conflict { + reason, + source_ref, + evidence_digest, + overlap_revalidation, + } + } +} + /// Evaluates one witnessed suffix admission request against local evidence. /// /// Performs deterministic local validation before returning the classified @@ -374,6 +431,24 @@ fn canonical_provenance_refs(mut refs: Vec) -> Vec refs } +fn canonical_unique_provenance_refs( + mut refs: Vec, +) -> Result, WitnessedSuffixLocalAdmissionPostureError> { + refs.sort_unstable(); + + for window in refs.windows(2) { + if window[0] == window[1] { + return Err( + WitnessedSuffixLocalAdmissionPostureError::DuplicateProvenanceRef { + provenance_ref: window[0], + }, + ); + } + } + + Ok(refs) +} + fn obstructed_response( request: &WitnessedSuffixAdmissionRequest, source_shell_digest: Hash, diff --git a/crates/warp-core/src/witnessed_suffix_tests.rs b/crates/warp-core/src/witnessed_suffix_tests.rs index 59b1af62..71c67639 100644 --- a/crates/warp-core/src/witnessed_suffix_tests.rs +++ b/crates/warp-core/src/witnessed_suffix_tests.rs @@ -259,49 +259,40 @@ fn witnessed_suffix_core_response_converts_obstructed_outcome_to_abi() { #[test] fn witnessed_suffix_local_posture_admissible_constructor_canonicalizes_refs() { - let posture = WitnessedSuffixLocalAdmissionPosture::admissible(vec![ - provenance_ref(30, 12), - provenance_ref(30, 10), - ]) - .expect("distinct admitted refs should construct"); - assert_eq!( - posture, - WitnessedSuffixLocalAdmissionPosture::Admissible { + WitnessedSuffixLocalAdmissionPosture::admissible(vec![ + provenance_ref(30, 12), + provenance_ref(30, 10), + ]), + Ok(WitnessedSuffixLocalAdmissionPosture::Admissible { admitted_refs: vec![provenance_ref(30, 10), provenance_ref(30, 12)], - } + }) ); } #[test] fn witnessed_suffix_local_posture_staged_constructor_canonicalizes_refs() { - let posture = WitnessedSuffixLocalAdmissionPosture::staged(vec![ - provenance_ref(32, 12), - provenance_ref(32, 11), - ]) - .expect("distinct staged refs should construct"); - assert_eq!( - posture, - WitnessedSuffixLocalAdmissionPosture::Staged { + WitnessedSuffixLocalAdmissionPosture::staged(vec![ + provenance_ref(32, 12), + provenance_ref(32, 11), + ]), + Ok(WitnessedSuffixLocalAdmissionPosture::Staged { staged_refs: vec![provenance_ref(32, 11), provenance_ref(32, 12)], - } + }) ); } #[test] fn witnessed_suffix_local_posture_plural_constructor_canonicalizes_refs() { - let posture = WitnessedSuffixLocalAdmissionPosture::plural(vec![ - provenance_ref(34, 13), - provenance_ref(33, 12), - ]) - .expect("distinct plural refs should construct"); - assert_eq!( - posture, - WitnessedSuffixLocalAdmissionPosture::Plural { + WitnessedSuffixLocalAdmissionPosture::plural(vec![ + provenance_ref(34, 13), + provenance_ref(33, 12), + ]), + Ok(WitnessedSuffixLocalAdmissionPosture::Plural { candidate_refs: vec![provenance_ref(33, 12), provenance_ref(34, 13)], - } + }) ); } From 2978cc96d5626523a1ac3cc4fe1ac355ea91025a Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 3 May 2026 14:00:33 -0700 Subject: [PATCH 3/4] test: use canonical witnessed suffix posture fixtures --- .../warp-core/src/witnessed_suffix_tests.rs | 218 +++++++++++------- .../design.md | 35 +-- 2 files changed, 155 insertions(+), 98 deletions(-) diff --git a/crates/warp-core/src/witnessed_suffix_tests.rs b/crates/warp-core/src/witnessed_suffix_tests.rs index 71c67639..04a0ebc4 100644 --- a/crates/warp-core/src/witnessed_suffix_tests.rs +++ b/crates/warp-core/src/witnessed_suffix_tests.rs @@ -141,6 +141,38 @@ fn clean_context(posture: WitnessedSuffixLocalAdmissionPosture) -> FakeAdmission } } +fn admissible_posture( + refs: Vec, +) -> Result { + WitnessedSuffixLocalAdmissionPosture::admissible(refs) +} + +fn staged_posture( + refs: Vec, +) -> Result { + WitnessedSuffixLocalAdmissionPosture::staged(refs) +} + +fn plural_posture( + refs: Vec, +) -> Result { + WitnessedSuffixLocalAdmissionPosture::plural(refs) +} + +fn conflict_posture( + reason: ConflictReason, + source_ref: ProvenanceRef, + evidence_digest: Hash, + overlap_revalidation: Option, +) -> WitnessedSuffixLocalAdmissionPosture { + WitnessedSuffixLocalAdmissionPosture::conflict( + reason, + source_ref, + evidence_digest, + overlap_revalidation, + ) +} + #[test] fn witnessed_suffix_core_request_converts_to_abi_shape() { let request = request(); @@ -316,6 +348,25 @@ fn witnessed_suffix_local_posture_constructors_reject_duplicate_refs() { } } +#[test] +fn witnessed_suffix_local_posture_constructors_reject_duplicates_after_sorting() { + let duplicate_ref = provenance_ref(30, 10); + let intervening_ref = provenance_ref(31, 10); + + assert_eq!( + WitnessedSuffixLocalAdmissionPosture::admissible(vec![ + duplicate_ref, + intervening_ref, + duplicate_ref, + ]), + Err( + WitnessedSuffixLocalAdmissionPostureError::DuplicateProvenanceRef { + provenance_ref: duplicate_ref, + }, + ) + ); +} + #[test] fn witnessed_suffix_local_posture_conflict_constructor_names_all_evidence() { let overlap_revalidation = StrandOverlapRevalidation::Conflict { @@ -341,11 +392,10 @@ fn witnessed_suffix_local_posture_conflict_constructor_names_all_evidence() { } #[test] -fn witnessed_suffix_evaluator_admits_clean_suffix() { +fn witnessed_suffix_evaluator_admits_clean_suffix( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = request(); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -360,19 +410,19 @@ fn witnessed_suffix_evaluator_admits_clean_suffix() { } if target_worldline_id == worldline(11) && admitted_refs == vec![provenance_ref(30, 10)] )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_stages_boundary_only_suffix() { +fn witnessed_suffix_evaluator_stages_boundary_only_suffix( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = WitnessedSuffixAdmissionRequest { source_suffix: shell_with_entries(Vec::new()), target_worldline_id: worldline(11), target_basis: provenance_ref(12, 9), basis_report: None, }; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Staged { - staged_refs: vec![provenance_ref(5, 1)], - }); + let context = clean_context(staged_posture(vec![provenance_ref(5, 1)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -381,10 +431,12 @@ fn witnessed_suffix_evaluator_stages_boundary_only_suffix() { WitnessedSuffixAdmissionOutcome::Staged { staged_refs, .. } if staged_refs == vec![provenance_ref(5, 1)] )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_empty_suffix_without_boundary_witness() { +fn witnessed_suffix_evaluator_obstructs_empty_suffix_without_boundary_witness( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = WitnessedSuffixAdmissionRequest { source_suffix: shell_with_entries(Vec::new()), target_worldline_id: worldline(11), @@ -392,9 +444,7 @@ fn witnessed_suffix_evaluator_obstructs_empty_suffix_without_boundary_witness() basis_report: None, }; request.source_suffix.boundary_witness = None; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Staged { - staged_refs: Vec::new(), - }); + let context = clean_context(staged_posture(Vec::new())?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -405,17 +455,17 @@ fn witnessed_suffix_evaluator_obstructs_empty_suffix_without_boundary_witness() .. } )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_allows_equal_start_and_end_ticks() { +fn witnessed_suffix_evaluator_allows_equal_start_and_end_ticks( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); request.source_suffix.source_suffix_end_tick = Some(request.source_suffix.source_suffix_start_tick); request.source_suffix.source_entries = vec![provenance_ref(3, 2)]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -428,10 +478,12 @@ fn witnessed_suffix_evaluator_allows_equal_start_and_end_ticks() { } if target_worldline_id == worldline(11) && admitted_refs == vec![provenance_ref(30, 10)] )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_stages_when_target_basis_is_boundary_witness() { +fn witnessed_suffix_evaluator_stages_when_target_basis_is_boundary_witness( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let boundary_witness = provenance_ref(5, 1); let request = WitnessedSuffixAdmissionRequest { source_suffix: shell_with_entries(Vec::new()), @@ -442,9 +494,7 @@ fn witnessed_suffix_evaluator_stages_when_target_basis_is_boundary_witness() { let context = FakeAdmissionContext { expected_shell_digest: Some([6; 32]), resolved_target_basis: Some(boundary_witness), - posture: WitnessedSuffixLocalAdmissionPosture::Staged { - staged_refs: vec![boundary_witness], - }, + posture: staged_posture(vec![boundary_witness])?, }; let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -455,15 +505,15 @@ fn witnessed_suffix_evaluator_stages_when_target_basis_is_boundary_witness() { WitnessedSuffixAdmissionOutcome::Staged { staged_refs, .. } if staged_refs == vec![boundary_witness] )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_preserves_plural_outcome() { +fn witnessed_suffix_evaluator_preserves_plural_outcome( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = request(); let candidates = vec![provenance_ref(33, 12), provenance_ref(34, 13)]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Plural { - candidate_refs: candidates.clone(), - }); + let context = clean_context(plural_posture(candidates.clone())?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -475,17 +525,18 @@ fn witnessed_suffix_evaluator_preserves_plural_outcome() { .. } if candidate_refs == candidates )); + Ok(()) } #[test] fn witnessed_suffix_evaluator_conflicts_with_adverse_admission_law() { let request = request(); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Conflict { - reason: ConflictReason::ParentFootprintOverlap, - source_ref: provenance_ref(35, 14), - evidence_digest: [36; 32], - overlap_revalidation: None, - }); + let context = clean_context(conflict_posture( + ConflictReason::ParentFootprintOverlap, + provenance_ref(35, 14), + [36; 32], + None, + )); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -506,12 +557,12 @@ fn witnessed_suffix_evaluator_preserves_conflict_overlap_revalidation() { let overlap_revalidation = StrandOverlapRevalidation::Conflict { overlapping_slots: vec![node_slot("overlap-a"), node_slot("overlap-b")], }; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Conflict { - reason: ConflictReason::ParentFootprintOverlap, - source_ref: provenance_ref(35, 14), - evidence_digest: [36; 32], - overlap_revalidation: Some(overlap_revalidation.clone()), - }); + let context = clean_context(conflict_posture( + ConflictReason::ParentFootprintOverlap, + provenance_ref(35, 14), + [36; 32], + Some(overlap_revalidation.clone()), + )); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -557,14 +608,13 @@ fn witnessed_suffix_evaluator_classifies_against_resolved_target_basis() { } #[test] -fn witnessed_suffix_evaluator_preserves_matching_request_basis_report() { +fn witnessed_suffix_evaluator_preserves_matching_request_basis_report( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); let report = basis_report(provenance_ref(12, 9)); request.basis_report = Some(report.clone()); request.source_suffix.basis_report = Some(basis_report(provenance_ref(12, 9))); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -575,16 +625,16 @@ fn witnessed_suffix_evaluator_preserves_matching_request_basis_report() { .. } if actual == report )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_falls_back_to_matching_source_basis_report() { +fn witnessed_suffix_evaluator_falls_back_to_matching_source_basis_report( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); let report = basis_report(provenance_ref(12, 9)); request.source_suffix.basis_report = Some(report.clone()); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Staged { - staged_refs: vec![provenance_ref(5, 1)], - }); + let context = clean_context(staged_posture(vec![provenance_ref(5, 1)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -595,15 +645,15 @@ fn witnessed_suffix_evaluator_falls_back_to_matching_source_basis_report() { .. } if actual == report )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_stale_basis_report() { +fn witnessed_suffix_evaluator_obstructs_stale_basis_report( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); request.basis_report = Some(basis_report(provenance_ref(99, 99))); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -614,10 +664,11 @@ fn witnessed_suffix_evaluator_obstructs_stale_basis_report() { .. } )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_normalizes_admitted_refs() { +fn witnessed_suffix_evaluator_normalizes_raw_admitted_refs() { let request = request(); let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { admitted_refs: vec![provenance_ref(30, 12), provenance_ref(30, 10)], @@ -633,7 +684,7 @@ fn witnessed_suffix_evaluator_normalizes_admitted_refs() { } #[test] -fn witnessed_suffix_evaluator_normalizes_staged_refs() { +fn witnessed_suffix_evaluator_normalizes_raw_staged_refs() { let request = request(); let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Staged { staged_refs: vec![provenance_ref(32, 12), provenance_ref(32, 11)], @@ -649,7 +700,7 @@ fn witnessed_suffix_evaluator_normalizes_staged_refs() { } #[test] -fn witnessed_suffix_evaluator_normalizes_plural_candidate_refs() { +fn witnessed_suffix_evaluator_normalizes_raw_plural_candidate_refs() { let request = request(); let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Plural { candidate_refs: vec![provenance_ref(34, 13), provenance_ref(33, 12)], @@ -665,14 +716,13 @@ fn witnessed_suffix_evaluator_normalizes_plural_candidate_refs() { } #[test] -fn witnessed_suffix_evaluator_obstructs_mismatched_digest() { +fn witnessed_suffix_evaluator_obstructs_mismatched_digest( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = request(); let context = FakeAdmissionContext { expected_shell_digest: Some([99; 32]), resolved_target_basis: Some(provenance_ref(12, 9)), - posture: WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }, + posture: admissible_posture(vec![provenance_ref(30, 10)])?, }; let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -684,18 +734,17 @@ fn witnessed_suffix_evaluator_obstructs_mismatched_digest() { .. } )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_missing_local_source_digest_without_reusing_request_digest() -{ +fn witnessed_suffix_evaluator_obstructs_missing_local_source_digest_without_reusing_request_digest( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = request(); let context = FakeAdmissionContext { expected_shell_digest: None, resolved_target_basis: Some(provenance_ref(12, 9)), - posture: WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }, + posture: admissible_posture(vec![provenance_ref(30, 10)])?, }; let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -712,17 +761,17 @@ fn witnessed_suffix_evaluator_obstructs_missing_local_source_digest_without_reus .. } if evidence_digest == response.source_shell_digest )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_unknown_target_basis() { +fn witnessed_suffix_evaluator_obstructs_unknown_target_basis( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let request = request(); let context = FakeAdmissionContext { expected_shell_digest: Some([6; 32]), resolved_target_basis: None, - posture: WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }, + posture: admissible_posture(vec![provenance_ref(30, 10)])?, }; let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -734,16 +783,16 @@ fn witnessed_suffix_evaluator_obstructs_unknown_target_basis() { .. } )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_inconsistent_bounds() { +fn witnessed_suffix_evaluator_obstructs_inconsistent_bounds( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); request.source_suffix.source_suffix_start_tick = tick(5); request.source_suffix.source_suffix_end_tick = Some(tick(4)); - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -754,17 +803,17 @@ fn witnessed_suffix_evaluator_obstructs_inconsistent_bounds() { .. } )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_source_entry_outside_suffix_bounds() { +fn witnessed_suffix_evaluator_obstructs_source_entry_outside_suffix_bounds( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { for outside_tick in [1, 5] { let mut request = request(); let offending_ref = provenance_ref(3, outside_tick); request.source_suffix.source_entries = vec![offending_ref]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -777,16 +826,16 @@ fn witnessed_suffix_evaluator_obstructs_source_entry_outside_suffix_bounds() { } if source_ref == offending_ref )); } + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_source_entry_from_foreign_worldline() { +fn witnessed_suffix_evaluator_obstructs_source_entry_from_foreign_worldline( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); let offending_ref = provenance_ref(4, 3); request.source_suffix.source_entries = vec![offending_ref]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -798,16 +847,16 @@ fn witnessed_suffix_evaluator_obstructs_source_entry_from_foreign_worldline() { .. } if source_ref == offending_ref )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_out_of_order_source_entries() { +fn witnessed_suffix_evaluator_obstructs_out_of_order_source_entries( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); let offending_ref = provenance_ref(3, 3); request.source_suffix.source_entries = vec![provenance_ref(3, 4), offending_ref]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -819,16 +868,16 @@ fn witnessed_suffix_evaluator_obstructs_out_of_order_source_entries() { .. } if source_ref == offending_ref )); + Ok(()) } #[test] -fn witnessed_suffix_evaluator_obstructs_duplicate_source_entries() { +fn witnessed_suffix_evaluator_obstructs_duplicate_source_entries( +) -> Result<(), WitnessedSuffixLocalAdmissionPostureError> { let mut request = request(); let duplicate_ref = provenance_ref(3, 3); request.source_suffix.source_entries = vec![duplicate_ref, duplicate_ref]; - let context = clean_context(WitnessedSuffixLocalAdmissionPosture::Admissible { - admitted_refs: vec![provenance_ref(30, 10)], - }); + let context = clean_context(admissible_posture(vec![provenance_ref(30, 10)])?); let response = evaluate_witnessed_suffix_admission(&request, &context); @@ -840,6 +889,7 @@ fn witnessed_suffix_evaluator_obstructs_duplicate_source_entries() { .. } if source_ref == duplicate_ref )); + Ok(()) } #[test] diff --git a/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md index eba946c9..4523ee77 100644 --- a/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md +++ b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md @@ -31,6 +31,10 @@ reintroduce caller-order leakage or duplicate provenance refs before the evaluator gets a chance to normalize the shape. This cycle starts by making that missing named construction path executable as RED. +GREEN 1 adds the named constructors and migrates ordinary test fixtures to use +them. Raw enum construction remains only where a test is asserting raw shape or +evaluator normalization of raw posture input. + ## Human users / jobs / hills ### Primary human users @@ -47,8 +51,8 @@ runtime work. ### Human hill -A maintainer can see the canonical posture construction contract before any -production helper is added. +A maintainer can review the canonical posture construction contract and the +small helper implementation without transport, sync, or ABI noise. ## Agent users / jobs / hills @@ -64,8 +68,8 @@ Future coding agents extending witnessed suffix admission call sites. ### Agent hill -An agent can run a single warp-core witnessed suffix test target and see the -missing canonical constructor contract fail for the intended reason. +An agent can run a single warp-core witnessed suffix test target and verify the +canonical constructor contract. ## Human playback @@ -79,17 +83,19 @@ missing canonical constructor contract fail for the intended reason. ## Agent playback 1. The agent runs `cargo test -p warp-core --lib witnessed_suffix`. -2. The command fails because the named posture constructors and duplicate-ref - error type do not exist yet. -3. The agent determines the next implementation is local to warp-core - witnessed suffix posture construction. +2. The command passes the constructor contract examples and existing evaluator + defense tests. +3. The agent determines the slice remains local to warp-core witnessed suffix + posture construction. ## Implementation outline -1. RED only: add tests for named canonical construction of admissible, staged, +1. RED: add tests for named canonical construction of admissible, staged, plural, and conflict local postures. -2. RED only: move the backlog card into this design packet. -3. Stop before implementing production helpers. +2. RED: move the backlog card into this design packet. +3. GREEN 1: add the named constructors and duplicate-ref error. +4. GREEN 1: keep ordinary fixtures on the canonical constructor path; leave raw + enum construction only for raw-shape and evaluator-defense tests. ## Tests to write first @@ -97,6 +103,7 @@ missing canonical constructor contract fail for the intended reason. - constructor canonicalizes staged refs - constructor canonicalizes plural candidate refs - constructors reject duplicate refs with a named error +- constructors reject duplicate refs after canonical sorting - conflict constructor names reason, source ref, digest, and overlap evidence ## Risks / unknowns @@ -105,8 +112,9 @@ missing canonical constructor contract fail for the intended reason. and design wording in the same narrow slice. - Duplicate handling may need a wider posture validation policy if empty ref vectors also become invalid. This RED only demands duplicate rejection. -- Existing direct enum construction in tests remains as fixture setup until the - GREEN cycle decides whether to migrate it. +- Direct enum construction remains available for tests that deliberately assert + raw shape or raw evaluator input. Ordinary clean fixtures should prefer named + constructors. ## Postures @@ -117,7 +125,6 @@ missing canonical constructor contract fail for the intended reason. ## Non-goals -- Do not implement the constructors in this RED slice. - Do not change transport, sync, import execution, or ABI shape. - Do not redesign witnessed suffix admission. - Do not implement Continuum proof, IPA, or commitment machinery. From ce38fd0340d8eb8bb71dcd15afc5f7530e49e6e5 Mon Sep 17 00:00:00 2001 From: James Ross Date: Sun, 3 May 2026 14:28:22 -0700 Subject: [PATCH 4/4] docs: clarify canonical posture construction --- crates/warp-core/src/witnessed_suffix.rs | 15 +++++++++++++++ crates/warp-core/src/witnessed_suffix_tests.rs | 2 ++ .../design.md | 18 +++++++++--------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/crates/warp-core/src/witnessed_suffix.rs b/crates/warp-core/src/witnessed_suffix.rs index 212816fe..d8b70acd 100644 --- a/crates/warp-core/src/witnessed_suffix.rs +++ b/crates/warp-core/src/witnessed_suffix.rs @@ -152,21 +152,36 @@ pub enum WitnessedSuffixLocalAdmissionPostureError { } /// Local posture reported by the read-only admission context. +/// +/// Prefer [`Self::admissible`], [`Self::staged`], or [`Self::plural`] for +/// ordinary construction so ref vectors are sorted canonically and duplicate +/// provenance refs are rejected before reaching an ABI-visible response. Direct +/// enum construction remains available for raw-shape tests and defensive +/// evaluator inputs. #[derive(Clone, Debug, PartialEq, Eq)] pub enum WitnessedSuffixLocalAdmissionPosture { /// Local evidence says the suffix is admissible. Admissible { /// Target-local provenance coordinates produced or expected by admission. + /// + /// Use [`WitnessedSuffixLocalAdmissionPosture::admissible`] for normal + /// construction. admitted_refs: Vec, }, /// Local evidence says the suffix should be retained for later judgment. Staged { /// Source or target coordinates retained while staged. + /// + /// Use [`WitnessedSuffixLocalAdmissionPosture::staged`] for normal + /// construction. staged_refs: Vec, }, /// Local evidence preserves lawful plurality. Plural { /// Candidate coordinates that remain lawful plural outcomes. + /// + /// Use [`WitnessedSuffixLocalAdmissionPosture::plural`] for normal + /// construction. candidate_refs: Vec, }, /// Local evidence reports deterministic adverse admission law. diff --git a/crates/warp-core/src/witnessed_suffix_tests.rs b/crates/warp-core/src/witnessed_suffix_tests.rs index 04a0ebc4..df977a74 100644 --- a/crates/warp-core/src/witnessed_suffix_tests.rs +++ b/crates/warp-core/src/witnessed_suffix_tests.rs @@ -127,6 +127,8 @@ impl WitnessedSuffixAdmissionContext for TargetBasisEchoAdmissionContext { &self, request: &WitnessedSuffixAdmissionRequest, ) -> WitnessedSuffixLocalAdmissionPosture { + // This trait method cannot return Result, so this fixture uses the raw + // shape only where the resolved basis is already deterministic. WitnessedSuffixLocalAdmissionPosture::Admissible { admitted_refs: vec![request.target_basis], } diff --git a/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md index 4523ee77..0d02c37c 100644 --- a/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md +++ b/docs/design/0012-witnessed-suffix-posture-canonicalization/design.md @@ -3,8 +3,8 @@ # 0012 — Witnessed suffix posture canonicalization -_Add a RED fence for named canonical construction of witnessed suffix local -admission postures._ +_Add named canonical construction for witnessed suffix local admission +postures._ Legend: [PLATFORM](../../method/legends/PLATFORM.md) @@ -44,8 +44,7 @@ runtime work. ### Human jobs -1. Review one focused RED and decide whether the expected posture constructor - shape is acceptable. +1. Review the named constructor API and duplicate-ref error contract. 2. Confirm the slice does not reopen transport, sync, ABI, or broad Continuum design. @@ -73,12 +72,12 @@ canonical constructor contract. ## Human playback -1. The human reads the RED tests in +1. The human reads the constructor contract tests in `crates/warp-core/src/witnessed_suffix_tests.rs`. 2. The tests name `admissible`, `staged`, `plural`, and `conflict` constructor paths on `WitnessedSuffixLocalAdmissionPosture`. -3. The human can decide the next GREEN implementation without inspecting - transport or ABI code. +3. The human can verify the GREEN 1 implementation without inspecting transport + or ABI code. ## Agent playback @@ -120,8 +119,9 @@ canonical constructor contract. - **Accessibility:** Not applicable; this is Rust API/test surface only. - **Localization:** Not applicable; no user-facing strings are introduced. -- **Agent inspectability:** The RED names exact constructor paths and expected - failure so future agents can implement without broad repo exploration. +- **Agent inspectability:** The design and tests name exact constructor paths, + duplicate handling, and raw-construction exceptions so future agents can + extend call sites without broad repo exploration. ## Non-goals