@@ -108,24 +108,64 @@ defmodule StackLab.Examples.GnTenDistributedStack.RouterModelReceipt do
108108 }
109109end
110110
111+ defmodule StackLab.Examples.GnTenDistributedStack.FaultRecoveryReceipt do
112+ @ moduledoc "Distributed gn-ten fault and recovery proof receipt."
113+
114+ @ enforce_keys [
115+ :receipt_ref ,
116+ :schema_version ,
117+ :status ,
118+ :profile ,
119+ :topology_ref ,
120+ :baseline_receipt_ref ,
121+ :fault_receipts ,
122+ :owner_recovery_evidence ,
123+ :trace_refs ,
124+ :node_lab_run ,
125+ :does_not_prove
126+ ]
127+ defstruct @ enforce_keys
128+
129+ @ type t :: % __MODULE__ {
130+ receipt_ref: String . t ( ) ,
131+ schema_version: String . t ( ) ,
132+ status: :pass | :open_defect ,
133+ profile: String . t ( ) ,
134+ topology_ref: String . t ( ) ,
135+ baseline_receipt_ref: String . t ( ) ,
136+ fault_receipts: [ map ( ) ] ,
137+ owner_recovery_evidence: [ map ( ) ] ,
138+ trace_refs: [ String . t ( ) ] ,
139+ node_lab_run: map ( ) ,
140+ does_not_prove: [ String . t ( ) ]
141+ }
142+ end
143+
111144defmodule StackLab.Examples.GnTenDistributedStack do
112145 @ moduledoc """
113146 Local distributed gn-ten proof scenarios.
114147 """
115148
116- alias StackLab.Examples.GnTenDistributedStack . { Receipt , RouterModelReceipt }
149+ alias StackLab.Examples.GnTenDistributedStack . {
150+ FaultRecoveryReceipt ,
151+ Receipt ,
152+ RouterModelReceipt
153+ }
117154
118155 @ context_schema_version "stack_lab.gn_ten_distributed_stack.context_6_node.v1"
119156 @ router_model_schema_version "stack_lab.gn_ten_distributed_stack.router_model_6_node.v1"
157+ @ fault_recovery_schema_version "stack_lab.gn_ten_distributed_stack.partition_recovery.v1"
120158 @ context_profile "context_6_node"
121159 @ router_model_profile "router_model_6_node"
160+ @ fault_recovery_profile "partition_recovery"
122161 @ envelope_schema_version "stack_lab.distributed_envelope.v1"
123162 @ context_roundtrip Module . concat ( [ StackLab , Examples , ContextABIRoundtrip ] )
124163 @ router_roundtrip Module . concat ( [ StackLab , Examples , NSHKRRouterFabricRoundtrip ] )
125164 @ aitrace_evidence Module . concat ( [ AITrace , RemoteFacade , Evidence ] )
126165 @ aitrace_fixture_transport Module . concat ( [ AITrace , NSHKR , ExportTransport , Fixture ] )
127166 @ replay_bundle Module . concat ( [ AITrace , Trace , ReplayBundle ] )
128167 @ envelope_scanner Module . concat ( [ StackLab , GnTenNodeLab , EnvelopeScanner ] )
168+ @ fault_drill Module . concat ( [ StackLab , GnTenNodeLab , FaultDrill ] )
129169 @ runner Module . concat ( [ StackLab , GnTenNodeLab , Runner ] )
130170 @ json Module . concat ( [ Jason ] )
131171
@@ -252,11 +292,41 @@ defmodule StackLab.Examples.GnTenDistributedStack do
252292 end
253293 end
254294
255- @ spec to_map ( Receipt . t ( ) | RouterModelReceipt . t ( ) ) :: map ( )
295+ @ spec run_partition_recovery ( keyword ( ) ) ::
296+ { :ok , FaultRecoveryReceipt . t ( ) } | { :error , term ( ) }
297+ def run_partition_recovery ( opts \\ [ ] ) when is_list ( opts ) do
298+ with { :ok , baseline } <- run_router_model_6_node ( opts ) do
299+ fault_receipts = fault_receipts ( baseline )
300+
301+ { :ok ,
302+ % FaultRecoveryReceipt {
303+ receipt_ref: fault_recovery_receipt_ref ( baseline ) ,
304+ schema_version: @ fault_recovery_schema_version ,
305+ status: fault_recovery_status ( fault_receipts ) ,
306+ profile: @ fault_recovery_profile ,
307+ topology_ref: baseline . topology_ref ,
308+ baseline_receipt_ref: baseline . receipt_ref ,
309+ fault_receipts: fault_receipts ,
310+ owner_recovery_evidence: owner_recovery_evidence ( ) ,
311+ trace_refs: baseline . trace_refs ,
312+ node_lab_run: baseline . node_lab_run ,
313+ does_not_prove: [
314+ "WAN partition behavior" ,
315+ "production service discovery" ,
316+ "release artifact boot" ,
317+ "live provider retry semantics" ,
318+ "Execution Plane lower-lane partition behavior"
319+ ]
320+ } }
321+ end
322+ end
323+
324+ @ spec to_map ( Receipt . t ( ) | RouterModelReceipt . t ( ) | FaultRecoveryReceipt . t ( ) ) :: map ( )
256325 def to_map ( % Receipt { } = receipt ) , do: json_safe ( receipt )
257326 def to_map ( % RouterModelReceipt { } = receipt ) , do: json_safe ( receipt )
327+ def to_map ( % FaultRecoveryReceipt { } = receipt ) , do: json_safe ( receipt )
258328
259- @ spec to_json! ( Receipt . t ( ) | RouterModelReceipt . t ( ) ) :: String . t ( )
329+ @ spec to_json! ( Receipt . t ( ) | RouterModelReceipt . t ( ) | FaultRecoveryReceipt . t ( ) ) :: String . t ( )
260330 def to_json! ( % Receipt { } = receipt ) do
261331 call ( @ json , :encode! , [ to_map ( receipt ) , [ pretty: true ] ] )
262332 end
@@ -265,6 +335,10 @@ defmodule StackLab.Examples.GnTenDistributedStack do
265335 call ( @ json , :encode! , [ to_map ( receipt ) , [ pretty: true ] ] )
266336 end
267337
338+ def to_json! ( % FaultRecoveryReceipt { } = receipt ) do
339+ call ( @ json , :encode! , [ to_map ( receipt ) , [ pretty: true ] ] )
340+ end
341+
268342 defp envelopes ( baseline , node_lab_run , scenario \\ :context ) do
269343 node_lab_run
270344 |> Map . fetch! ( "boot_receipts" )
@@ -376,6 +450,12 @@ defmodule StackLab.Examples.GnTenDistributedStack do
376450 "gn-ten-distributed-router-model://#{ suffix } "
377451 end
378452
453+ defp fault_recovery_receipt_ref ( baseline ) do
454+ baseline . receipt_ref
455+ |> String . replace_prefix ( "gn-ten-distributed-router-model://" , "" )
456+ |> then ( & "gn-ten-distributed-partition-recovery://#{ & 1 } " )
457+ end
458+
379459 defp default_context_topology_path do
380460 Path . expand ( "../../../priv/topologies/context_6_node.exs" , __DIR__ )
381461 end
@@ -483,6 +563,69 @@ defmodule StackLab.Examples.GnTenDistributedStack do
483563 defp maybe_put ( map , _key , nil ) , do: map
484564 defp maybe_put ( map , key , value ) , do: Map . put ( map , key , value )
485565
566+ defp fault_receipts ( baseline ) do
567+ run = baseline . node_lab_run
568+
569+ [
570+ call ( @ fault_drill , :crash_node! , [ run , "jido_model_runtime_0" ] ) ,
571+ call ( @ fault_drill , :disconnect_nodes! , [
572+ run ,
573+ "mezzanine_workflow_0" ,
574+ "jido_model_runtime_0"
575+ ] ) ,
576+ call ( @ fault_drill , :heal_nodes! , [ run , "mezzanine_workflow_0" , "jido_model_runtime_0" ] ) ,
577+ call ( @ fault_drill , :delay_facade! , [
578+ run ,
579+ "jido_model_runtime_0" ,
580+ "JidoIntegration.RemoteFacade.ModelRuntime" ,
581+ 5_001
582+ ] ) ,
583+ call ( @ fault_drill , :inject_stale_dto! , [
584+ run ,
585+ "seam://mezzanine/jido/model-invocation" ,
586+ "fixture://stack_lab/partition_recovery/stale-model-invocation"
587+ ] ) ,
588+ call ( @ fault_drill , :duplicate_submit! , [ run , baseline . model_invocation_ref ] ) ,
589+ call ( @ fault_drill , :kill_exporter! , [ run , :aitrace_evidence ] )
590+ ]
591+ end
592+
593+ defp fault_recovery_status ( fault_receipts ) do
594+ if Enum . all? ( fault_receipts , & ( Map . get ( & 1 , "status" ) == "pass" ) ) ,
595+ do: :pass ,
596+ else: :open_defect
597+ end
598+
599+ defp owner_recovery_evidence do
600+ [
601+ % {
602+ "owner" => "citadel" ,
603+ "package" => "surfaces/citadel_domain_surface" ,
604+ "evidence_ref" => "citadel_domain_surface_fault_injection_and_operability_test" ,
605+ "safe_action" => "bounded duplicate, timeout, and dead-letter posture"
606+ } ,
607+ % {
608+ "owner" => "mezzanine" ,
609+ "package" => "core/workflow_runtime" ,
610+ "evidence_ref" => "Mezzanine.WorkflowRuntime.TemporalDispatchContract" ,
611+ "safe_action" => "workflow start outbox and retry visibility contracts"
612+ } ,
613+ % {
614+ "owner" => "stack_lab" ,
615+ "package" => "examples/pressure_failover_drill" ,
616+ "evidence_ref" => "PressureFailoverDrill duplicate_delivery and transport_interruption" ,
617+ "safe_action" =>
618+ "duplicate delivery converges and transport interruption remains pending until replay"
619+ } ,
620+ % {
621+ "owner" => "aitrace" ,
622+ "package" => "AITrace" ,
623+ "evidence_ref" => "AITrace.RemoteFacade.Evidence" ,
624+ "safe_action" => "export unavailable posture remains bounded"
625+ }
626+ ]
627+ end
628+
486629 defp json_safe ( % _struct { } = value ) , do: value |> Map . from_struct ( ) |> json_safe ( )
487630
488631 defp json_safe ( value ) when is_map ( value ) do
0 commit comments