Skip to content

Commit bb6a5eb

Browse files
committed
A-unlock-stepdomain: DomainProfile + verb taxonomy seam (E5) + Display
1 parent 99f67a0 commit bb6a5eb

1 file changed

Lines changed: 174 additions & 0 deletions

File tree

crates/lance-graph-contract/src/orchestration.rs

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)