88
99use blake3:: Hasher ;
1010use echo_wasm_abi:: { encode_cbor, kernel_port as abi} ;
11+ use thiserror:: Error ;
1112
1213use crate :: attachment:: { AttachmentOwner , AttachmentPlane } ;
1314use crate :: clock:: WorldlineTick ;
@@ -139,22 +140,48 @@ pub trait WitnessedSuffixAdmissionContext {
139140 ) -> WitnessedSuffixLocalAdmissionPosture ;
140141}
141142
143+ /// Error returned when constructing a canonical local admission posture fails.
144+ #[ derive( Clone , Copy , Debug , PartialEq , Eq , Error ) ]
145+ pub enum WitnessedSuffixLocalAdmissionPostureError {
146+ /// A provenance coordinate appeared more than once in a posture vector.
147+ #[ error( "duplicate witnessed suffix local admission provenance ref: {provenance_ref:?}" ) ]
148+ DuplicateProvenanceRef {
149+ /// Duplicate provenance coordinate.
150+ provenance_ref : ProvenanceRef ,
151+ } ,
152+ }
153+
142154/// Local posture reported by the read-only admission context.
155+ ///
156+ /// Prefer [`Self::admissible`], [`Self::staged`], or [`Self::plural`] for
157+ /// ordinary construction so ref vectors are sorted canonically and duplicate
158+ /// provenance refs are rejected before reaching an ABI-visible response. Direct
159+ /// enum construction remains available for raw-shape tests and defensive
160+ /// evaluator inputs.
143161#[ derive( Clone , Debug , PartialEq , Eq ) ]
144162pub enum WitnessedSuffixLocalAdmissionPosture {
145163 /// Local evidence says the suffix is admissible.
146164 Admissible {
147165 /// Target-local provenance coordinates produced or expected by admission.
166+ ///
167+ /// Use [`WitnessedSuffixLocalAdmissionPosture::admissible`] for normal
168+ /// construction.
148169 admitted_refs : Vec < ProvenanceRef > ,
149170 } ,
150171 /// Local evidence says the suffix should be retained for later judgment.
151172 Staged {
152173 /// Source or target coordinates retained while staged.
174+ ///
175+ /// Use [`WitnessedSuffixLocalAdmissionPosture::staged`] for normal
176+ /// construction.
153177 staged_refs : Vec < ProvenanceRef > ,
154178 } ,
155179 /// Local evidence preserves lawful plurality.
156180 Plural {
157181 /// Candidate coordinates that remain lawful plural outcomes.
182+ ///
183+ /// Use [`WitnessedSuffixLocalAdmissionPosture::plural`] for normal
184+ /// construction.
158185 candidate_refs : Vec < ProvenanceRef > ,
159186 } ,
160187 /// Local evidence reports deterministic adverse admission law.
@@ -170,6 +197,51 @@ pub enum WitnessedSuffixLocalAdmissionPosture {
170197 } ,
171198}
172199
200+ impl WitnessedSuffixLocalAdmissionPosture {
201+ /// Builds an admissible posture with canonical admitted refs.
202+ pub fn admissible (
203+ admitted_refs : Vec < ProvenanceRef > ,
204+ ) -> Result < Self , WitnessedSuffixLocalAdmissionPostureError > {
205+ Ok ( Self :: Admissible {
206+ admitted_refs : canonical_unique_provenance_refs ( admitted_refs) ?,
207+ } )
208+ }
209+
210+ /// Builds a staged posture with canonical staged refs.
211+ pub fn staged (
212+ staged_refs : Vec < ProvenanceRef > ,
213+ ) -> Result < Self , WitnessedSuffixLocalAdmissionPostureError > {
214+ Ok ( Self :: Staged {
215+ staged_refs : canonical_unique_provenance_refs ( staged_refs) ?,
216+ } )
217+ }
218+
219+ /// Builds a plural posture with canonical candidate refs.
220+ pub fn plural (
221+ candidate_refs : Vec < ProvenanceRef > ,
222+ ) -> Result < Self , WitnessedSuffixLocalAdmissionPostureError > {
223+ Ok ( Self :: Plural {
224+ candidate_refs : canonical_unique_provenance_refs ( candidate_refs) ?,
225+ } )
226+ }
227+
228+ /// Builds a conflict posture from named conflict evidence.
229+ #[ must_use]
230+ pub fn conflict (
231+ reason : ConflictReason ,
232+ source_ref : ProvenanceRef ,
233+ evidence_digest : Hash ,
234+ overlap_revalidation : Option < StrandOverlapRevalidation > ,
235+ ) -> Self {
236+ Self :: Conflict {
237+ reason,
238+ source_ref,
239+ evidence_digest,
240+ overlap_revalidation,
241+ }
242+ }
243+ }
244+
173245/// Evaluates one witnessed suffix admission request against local evidence.
174246///
175247/// Performs deterministic local validation before returning the classified
@@ -374,6 +446,24 @@ fn canonical_provenance_refs(mut refs: Vec<ProvenanceRef>) -> Vec<ProvenanceRef>
374446 refs
375447}
376448
449+ fn canonical_unique_provenance_refs (
450+ mut refs : Vec < ProvenanceRef > ,
451+ ) -> Result < Vec < ProvenanceRef > , WitnessedSuffixLocalAdmissionPostureError > {
452+ refs. sort_unstable ( ) ;
453+
454+ for window in refs. windows ( 2 ) {
455+ if window[ 0 ] == window[ 1 ] {
456+ return Err (
457+ WitnessedSuffixLocalAdmissionPostureError :: DuplicateProvenanceRef {
458+ provenance_ref : window[ 0 ] ,
459+ } ,
460+ ) ;
461+ }
462+ }
463+
464+ Ok ( refs)
465+ }
466+
377467fn obstructed_response (
378468 request : & WitnessedSuffixAdmissionRequest ,
379469 source_shell_digest : Hash ,
0 commit comments