@@ -75,6 +75,131 @@ impl StepDomain {
7575 _ => None ,
7676 }
7777 }
78+
79+ /// Per-domain orchestration profile (E5 from PR #278 outlook).
80+ ///
81+ /// `StepDomain` is the seam for vertical-specific orchestration:
82+ /// verb taxonomy, calibration thresholds, retention windows, and
83+ /// escalation defaults are picked HERE so downstream code does not
84+ /// hard-code Medcare-vs-SMB conditionals at every call site.
85+ ///
86+ /// Profiles are STATIC defaults — the runtime can override via the
87+ /// membrane registry without changing the enum. Tune empirically
88+ /// per deployment; the values below are conservative starters.
89+ pub fn profile ( & self ) -> DomainProfile {
90+ match self {
91+ Self :: Smb => DomainProfile {
92+ audit_retention_days : 90 ,
93+ auto_action_confidence : 0.75 ,
94+ escalation : Escalation :: Llm ,
95+ requires_fail_closed : false ,
96+ verb_taxonomy : VerbTaxonomyId :: Smb ,
97+ } ,
98+ Self :: Medcare => DomainProfile {
99+ // 6 years (HIPAA §164.316(b)(2)(i)) — starter, tune empirically.
100+ audit_retention_days : 2190 ,
101+ auto_action_confidence : 0.92 ,
102+ escalation : Escalation :: Human ,
103+ requires_fail_closed : true ,
104+ verb_taxonomy : VerbTaxonomyId :: Medcare ,
105+ } ,
106+ // Generic defaults for infrastructure / orchestration domains.
107+ // These are NOT vertical-facing; they execute the cycle, not
108+ // the policy. Starter values — tune empirically.
109+ Self :: Crew
110+ | Self :: Ladybug
111+ | Self :: N8n
112+ | Self :: LanceGraph
113+ | Self :: Ndarray => DomainProfile {
114+ audit_retention_days : 30 ,
115+ auto_action_confidence : 0.70 ,
116+ escalation : Escalation :: Llm ,
117+ requires_fail_closed : false ,
118+ verb_taxonomy : VerbTaxonomyId :: Generic ,
119+ } ,
120+ }
121+ }
122+ }
123+
124+ impl core:: fmt:: Display for StepDomain {
125+ /// Lowercase form mirroring `from_step_type` keys exactly.
126+ /// `from_step_type(&domain.to_string()) == Some(domain)` for every variant.
127+ fn fmt ( & self , f : & mut core:: fmt:: Formatter < ' _ > ) -> core:: fmt:: Result {
128+ let s = match self {
129+ Self :: Crew => "crew" ,
130+ Self :: Ladybug => "lb" ,
131+ Self :: N8n => "n8n" ,
132+ Self :: LanceGraph => "lg" ,
133+ Self :: Ndarray => "nd" ,
134+ Self :: Smb => "smb" ,
135+ Self :: Medcare => "medcare" ,
136+ } ;
137+ f. write_str ( s)
138+ }
139+ }
140+
141+ /// Per-domain orchestration profile. Carries calibration thresholds,
142+ /// retention windows, escalation defaults, and verb-taxonomy markers.
143+ ///
144+ /// Profiles are STATIC defaults — runtime can override via the membrane
145+ /// registry without changing the enum.
146+ #[ derive( Debug , Clone , Copy ) ]
147+ pub struct DomainProfile {
148+ /// Audit retention in days. Medcare (HIPAA) = 6 years (2190); SMB = 90.
149+ pub audit_retention_days : u32 ,
150+ /// Confidence threshold above which automated actions are allowed
151+ /// without human review. Medcare requires higher threshold.
152+ pub auto_action_confidence : f32 ,
153+ /// Escalation target on uncertainty: Llm = degrade to LLM tail;
154+ /// Human = require human-in-the-loop; Reject = fail closed.
155+ pub escalation : Escalation ,
156+ /// Whether this domain demands fail-closed access control.
157+ /// Medcare = true (HIPAA); SMB = false (commerce).
158+ pub requires_fail_closed : bool ,
159+ /// Verb taxonomy id — picks which 144-cell verb table to consult.
160+ pub verb_taxonomy : VerbTaxonomyId ,
161+ }
162+
163+ /// Escalation target on uncertainty.
164+ #[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
165+ pub enum Escalation {
166+ /// Degrade to LLM tail (best-effort, no human in loop).
167+ Llm ,
168+ /// Require human-in-the-loop (HIPAA-grade verticals default here).
169+ Human ,
170+ /// Fail closed — reject the step rather than guess.
171+ Reject ,
172+ }
173+
174+ /// Verb taxonomy identifier — selects the per-domain 144-cell verb table.
175+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Hash ) ]
176+ pub enum VerbTaxonomyId {
177+ /// 12 generic semantic families (BECOMES, CAUSES, SUPPORTS, ...).
178+ Generic ,
179+ /// SMB-specific: invoice, quote, dispatch, fulfill, return, refund, ...
180+ Smb ,
181+ /// Medcare-specific: prescribe, refer, discharge, admit, treat, diagnose, ...
182+ Medcare ,
183+ }
184+
185+ /// Compute a Trajectory-aware audit hash for a step within this domain.
186+ ///
187+ /// This is the cross-PR bridge between PR #279's grammar substrate and
188+ /// PR #278's audit log: the trajectory becomes the audit key, replacing
189+ /// the syntactic statement_hash.
190+ ///
191+ /// PR #279 epiphany E4. Implementation lands in the bridge PR.
192+ ///
193+ /// META-AGENT: feature-gated stub. Do NOT call until the bridge PR
194+ /// implements it; signature is locked here so callers can compile-test
195+ /// against the trajectory-audit feature flag.
196+ #[ cfg( feature = "trajectory-audit" ) ]
197+ pub fn step_trajectory_hash (
198+ _domain : StepDomain ,
199+ _step : & UnifiedStep ,
200+ _trajectory : & [ u64 ; 256 ] ,
201+ ) -> u64 {
202+ unimplemented ! ( "see PR #279 outlook E4" )
78203}
79204
80205#[ cfg( test) ]
@@ -100,6 +225,55 @@ mod tests {
100225 ) ;
101226 assert_eq ! ( StepDomain :: from_step_type( "unknown.foo" ) , None ) ;
102227 }
228+
229+ #[ test]
230+ fn display_round_trips_through_from_step_type ( ) {
231+ // Every variant must serialize to a string that `from_step_type`
232+ // accepts and round-trips back. Keeps the Display impl honest.
233+ let all = [
234+ StepDomain :: Crew ,
235+ StepDomain :: Ladybug ,
236+ StepDomain :: N8n ,
237+ StepDomain :: LanceGraph ,
238+ StepDomain :: Ndarray ,
239+ StepDomain :: Smb ,
240+ StepDomain :: Medcare ,
241+ ] ;
242+ for domain in all {
243+ let s = domain. to_string ( ) ;
244+ assert_eq ! (
245+ StepDomain :: from_step_type( & s) ,
246+ Some ( domain) ,
247+ "Display→from_step_type round-trip failed for {domain:?} (got {s:?})" ,
248+ ) ;
249+ }
250+ }
251+
252+ #[ test]
253+ fn medcare_requires_fail_closed ( ) {
254+ assert ! ( StepDomain :: Medcare . profile( ) . requires_fail_closed) ;
255+ }
256+
257+ #[ test]
258+ fn medcare_auto_action_threshold_higher_than_smb ( ) {
259+ let medcare = StepDomain :: Medcare . profile ( ) ;
260+ let smb = StepDomain :: Smb . profile ( ) ;
261+ assert ! (
262+ medcare. auto_action_confidence > smb. auto_action_confidence,
263+ "medcare ({}) must demand a higher auto-action confidence than smb ({})" ,
264+ medcare. auto_action_confidence,
265+ smb. auto_action_confidence,
266+ ) ;
267+ }
268+
269+ #[ test]
270+ fn medcare_audit_retention_is_hipaa_grade ( ) {
271+ // HIPAA §164.316(b)(2)(i) = 6 years = 2190 days.
272+ assert ! (
273+ StepDomain :: Medcare . profile( ) . audit_retention_days >= 2190 ,
274+ "medcare audit retention must be >= 2190 days (HIPAA 6 years)" ,
275+ ) ;
276+ }
103277}
104278
105279/// Unified step — the unit of work crossing system boundaries.
0 commit comments