1use serde::Serialize;
19use std::fmt;
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
23#[serde(rename_all = "snake_case")]
24pub enum AxisCategory {
25 Cognitive,
27 Emotional,
29 Social,
31 Preferences,
33 Control,
35 Safety,
37}
38
39impl fmt::Display for AxisCategory {
40 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41 match self {
42 Self::Cognitive => write!(f, "cognitive"),
43 Self::Emotional => write!(f, "emotional"),
44 Self::Social => write!(f, "social"),
45 Self::Preferences => write!(f, "preferences"),
46 Self::Control => write!(f, "control"),
47 Self::Safety => write!(f, "safety"),
48 }
49 }
50}
51
52#[derive(Clone, Debug, Serialize)]
54pub struct DeprecationInfo {
55 pub since: &'static str,
57 pub reason: &'static str,
59 pub replacement: Option<&'static str>,
61}
62
63#[derive(Clone, Debug, Serialize)]
81pub struct AxisDefinition {
82 pub name: &'static str,
86
87 pub category: AxisCategory,
89
90 pub description: &'static str,
92
93 pub low_anchor: &'static str,
95
96 pub high_anchor: &'static str,
98
99 pub intent: &'static [&'static str],
104
105 pub forbidden_uses: &'static [&'static str],
110
111 pub since: &'static str,
113
114 pub deprecated: Option<DeprecationInfo>,
116}
117
118pub type Axis = AxisDefinition;
121
122pub const COGNITIVE_LOAD: AxisDefinition = AxisDefinition {
128 name: "cognitive_load",
129 category: AxisCategory::Cognitive,
130 description: "Current mental bandwidth consumption and available processing capacity",
131 low_anchor: "Mentally fresh; high capacity for complexity and nuance",
132 high_anchor: "Mentally taxed; needs simplification and focus",
133 intent: &[
134 "Adjust response complexity and information density",
135 "Gate multi-step suggestions and elaborate plans",
136 "Determine appropriate level of detail in explanations",
137 ],
138 forbidden_uses: &[
139 "Infer user intelligence or cognitive capability",
140 "Target users with high load for simplified 'fast-track' conversions",
141 "Bypass user autonomy when cognitive load is elevated",
142 "Withhold information the user explicitly requested",
143 ],
144 since: "0.1.0",
145 deprecated: None,
146};
147
148pub const DECISION_FATIGUE: AxisDefinition = AxisDefinition {
150 name: "decision_fatigue",
151 category: AxisCategory::Cognitive,
152 description: "Accumulated decision-making exhaustion and choice aversion",
153 low_anchor: "Ready to evaluate options and make decisions",
154 high_anchor: "Decision-averse; needs reduced choices or defaults",
155 intent: &[
156 "Reduce number of options presented",
157 "Offer sensible defaults more prominently",
158 "Defer non-urgent decisions to later",
159 ],
160 forbidden_uses: &[
161 "Push preferred options when user is fatigued",
162 "Exploit fatigue to close sales faster",
163 "Make decisions on behalf of user without consent",
164 ],
165 since: "0.1.0",
166 deprecated: None,
167};
168
169pub const TOLERANCE_FOR_COMPLEXITY: AxisDefinition = AxisDefinition {
171 name: "tolerance_for_complexity",
172 category: AxisCategory::Cognitive,
173 description: "Willingness to engage with nuanced, detailed, or complex information",
174 low_anchor: "Prefers simple, clear, binary options",
175 high_anchor: "Comfortable with nuance, tradeoffs, and detail",
176 intent: &[
177 "Adjust depth of explanations",
178 "Choose between simplified vs comprehensive responses",
179 "Determine whether to surface edge cases and caveats",
180 ],
181 forbidden_uses: &[
182 "Assume low tolerance means user cannot understand complexity",
183 "Hide important information behind 'simplification'",
184 "Use as proxy for education level or expertise",
185 ],
186 since: "0.1.0",
187 deprecated: None,
188};
189
190pub const URGENCY_SENSITIVITY: AxisDefinition = AxisDefinition {
192 name: "urgency_sensitivity",
193 category: AxisCategory::Cognitive,
194 description: "Responsiveness to time pressure and need for quick resolution",
195 low_anchor: "Patient; flexible on timing; browsing mode",
196 high_anchor: "Time-pressured; needs immediate resolution",
197 intent: &[
198 "Adjust response length and pacing",
199 "Prioritize actionable information first",
200 "Skip non-essential context when urgent",
201 ],
202 forbidden_uses: &[
203 "Create artificial urgency to pressure decisions",
204 "Exploit time pressure for upsells or conversions",
205 "Rush users past important warnings or disclosures",
206 ],
207 since: "0.1.0",
208 deprecated: None,
209};
210
211pub const EMOTIONAL_OPENNESS: AxisDefinition = AxisDefinition {
217 name: "emotional_openness",
218 category: AxisCategory::Emotional,
219 description: "Willingness to engage emotionally vs preference for detachment",
220 low_anchor: "Prefers factual, detached, unemotional interaction",
221 high_anchor: "Open to emotional engagement and empathetic responses",
222 intent: &[
223 "Calibrate empathetic language in responses",
224 "Determine whether to acknowledge emotional context",
225 "Adjust between clinical and warm communication styles",
226 ],
227 forbidden_uses: &[
228 "Exploit emotional openness for manipulation",
229 "Dismiss emotional needs when openness is low",
230 "Use emotional engagement to bypass rational evaluation",
231 ],
232 since: "0.1.0",
233 deprecated: None,
234};
235
236pub const EMOTIONAL_STABILITY: AxisDefinition = AxisDefinition {
238 name: "emotional_stability",
239 category: AxisCategory::Emotional,
240 description: "Current emotional equilibrium and groundedness",
241 low_anchor: "Emotionally volatile, vulnerable, or dysregulated",
242 high_anchor: "Emotionally grounded, stable, resilient",
243 intent: &[
244 "Add extra care and gentleness when stability is low",
245 "Avoid triggering topics when appropriate",
246 "Suggest breaks or deferrals for high-stakes decisions",
247 ],
248 forbidden_uses: &[
249 "Target emotionally unstable users for conversion",
250 "Exploit vulnerability for compliance",
251 "Diagnose or label emotional states",
252 "Override user agency 'for their own good'",
253 ],
254 since: "0.1.0",
255 deprecated: None,
256};
257
258pub const ANXIETY_LEVEL: AxisDefinition = AxisDefinition {
260 name: "anxiety_level",
261 category: AxisCategory::Emotional,
262 description: "Current anxiety, worry, or stress state",
263 low_anchor: "Calm, relaxed, low worry",
264 high_anchor: "Anxious, worried, heightened stress",
265 intent: &[
266 "Add reassurance and validation when anxiety is high",
267 "Slow down pacing and reduce pressure",
268 "Acknowledge concerns before addressing them",
269 ],
270 forbidden_uses: &[
271 "Exploit anxiety to create urgency",
272 "Dismiss or minimize anxious concerns",
273 "Use anxiety signals for fear-based marketing",
274 "Diagnose anxiety disorders",
275 ],
276 since: "0.1.0",
277 deprecated: None,
278};
279
280pub const NEED_FOR_REASSURANCE: AxisDefinition = AxisDefinition {
282 name: "need_for_reassurance",
283 category: AxisCategory::Emotional,
284 description: "Desire for validation, confirmation, and reassurance",
285 low_anchor: "Self-assured; minimal validation needed",
286 high_anchor: "Seeks frequent reassurance and confirmation",
287 intent: &[
288 "Provide more explicit confirmation of understanding",
289 "Validate choices and decisions more frequently",
290 "Offer check-ins and progress acknowledgments",
291 ],
292 forbidden_uses: &[
293 "Withhold reassurance to create dependency",
294 "Exploit reassurance-seeking for manipulation",
295 "Condition reassurance on compliance",
296 ],
297 since: "0.1.0",
298 deprecated: None,
299};
300
301pub const WARMTH: AxisDefinition = AxisDefinition {
307 name: "warmth",
308 category: AxisCategory::Social,
309 description: "Desired warmth and friendliness level in interaction",
310 low_anchor: "Prefers cool, professional, businesslike tone",
311 high_anchor: "Prefers warm, friendly, personable tone",
312 intent: &[
313 "Adjust tone between professional and friendly",
314 "Calibrate use of casual language and humor",
315 "Determine appropriate level of personal connection",
316 ],
317 forbidden_uses: &[
318 "Fake warmth to build false rapport for sales",
319 "Use warmth to lower user's critical evaluation",
320 "Withdraw warmth as punishment for non-compliance",
321 ],
322 since: "0.1.0",
323 deprecated: None,
324};
325
326pub const FORMALITY: AxisDefinition = AxisDefinition {
328 name: "formality",
329 category: AxisCategory::Social,
330 description: "Desired formality level in language and presentation",
331 low_anchor: "Casual, informal, colloquial",
332 high_anchor: "Formal, professional, proper",
333 intent: &[
334 "Match language register to user preference",
335 "Adjust between contractions and formal grammar",
336 "Calibrate professional vs casual vocabulary",
337 ],
338 forbidden_uses: &[
339 "Use formality mismatch to establish dominance",
340 "Condescend through excessive formality",
341 "Undermine credibility through forced casualness",
342 ],
343 since: "0.1.0",
344 deprecated: None,
345};
346
347pub const BOUNDARY_STRENGTH: AxisDefinition = AxisDefinition {
349 name: "boundary_strength",
350 category: AxisCategory::Social,
351 description: "Personal boundary firmness and tolerance for intrusion",
352 low_anchor: "Flexible boundaries; accommodating of intrusion",
353 high_anchor: "Firm boundaries; protective of personal space",
354 intent: &[
355 "Respect stated limits without pushing",
356 "Avoid follow-up pressure when boundaries are firm",
357 "Adjust proactive outreach frequency",
358 ],
359 forbidden_uses: &[
360 "Test boundaries to find weak points",
361 "Exploit flexible boundaries for over-engagement",
362 "Punish strong boundaries with reduced service",
363 ],
364 since: "0.1.0",
365 deprecated: None,
366};
367
368pub const ASSERTIVENESS: AxisDefinition = AxisDefinition {
370 name: "assertiveness",
371 category: AxisCategory::Social,
372 description: "Directness in expressing needs and preferences",
373 low_anchor: "Passive, indirect, hints rather than states",
374 high_anchor: "Direct, assertive, explicitly states needs",
375 intent: &[
376 "Adjust between reading implicit cues vs waiting for explicit requests",
377 "Calibrate proactive assistance level",
378 "Determine whether to ask clarifying questions",
379 ],
380 forbidden_uses: &[
381 "Exploit low assertiveness to push unwanted options",
382 "Ignore indirect refusals from less assertive users",
383 "Treat assertiveness as rudeness or hostility",
384 ],
385 since: "0.1.0",
386 deprecated: None,
387};
388
389pub const RECIPROCITY_EXPECTATION: AxisDefinition = AxisDefinition {
391 name: "reciprocity_expectation",
392 category: AxisCategory::Social,
393 description: "Expected balance and mutual exchange in interaction",
394 low_anchor: "One-way service interaction is fine",
395 high_anchor: "Expects mutual exchange and give-and-take",
396 intent: &[
397 "Acknowledge user contributions more explicitly",
398 "Balance asking and providing information",
399 "Show appreciation for user effort and input",
400 ],
401 forbidden_uses: &[
402 "Create false sense of reciprocal obligation",
403 "Guilt users into actions via 'reciprocity'",
404 "Exploit reciprocity norms for compliance",
405 ],
406 since: "0.1.0",
407 deprecated: None,
408};
409
410pub const RITUAL_NEED: AxisDefinition = AxisDefinition {
416 name: "ritual_need",
417 category: AxisCategory::Preferences,
418 description: "Desire for ceremonial gestures, pleasantries, and social rituals",
419 low_anchor: "Skip pleasantries; get straight to the point",
420 high_anchor: "Values greetings, closings, and social niceties",
421 intent: &[
422 "Include or skip greeting rituals",
423 "Determine appropriate sign-off formality",
424 "Calibrate transitional pleasantries",
425 ],
426 forbidden_uses: &[
427 "Use rituals to waste time and extend engagement",
428 "Withhold ritual acknowledgment as punishment",
429 "Force rituals on users who skip them",
430 ],
431 since: "0.1.0",
432 deprecated: None,
433};
434
435pub const TRANSACTIONAL_PREFERENCE: AxisDefinition = AxisDefinition {
437 name: "transactional_preference",
438 category: AxisCategory::Preferences,
439 description: "Preference for task-focused transactional vs relationship-building interaction",
440 low_anchor: "Relationship-oriented; values ongoing connection",
441 high_anchor: "Transaction-oriented; focused on immediate task",
442 intent: &[
443 "Adjust between building rapport and task efficiency",
444 "Determine appropriate personal context to include",
445 "Calibrate follow-up and continuity references",
446 ],
447 forbidden_uses: &[
448 "Force relationship-building on transactional users",
449 "Exploit relationship-orientation for lock-in",
450 "Dismiss transactional users as 'cold' or 'difficult'",
451 ],
452 since: "0.1.0",
453 deprecated: None,
454};
455
456pub const VERBOSITY_PREFERENCE: AxisDefinition = AxisDefinition {
458 name: "verbosity_preference",
459 category: AxisCategory::Preferences,
460 description: "Desired response length and level of detail",
461 low_anchor: "Brief, concise, minimal responses",
462 high_anchor: "Detailed, comprehensive, thorough responses",
463 intent: &[
464 "Adjust response length and completeness",
465 "Determine whether to elaborate or summarize",
466 "Calibrate example and explanation depth",
467 ],
468 forbidden_uses: &[
469 "Use excessive verbosity to overwhelm or confuse",
470 "Hide important information in verbosity",
471 "Withhold detail to force follow-up engagement",
472 ],
473 since: "0.1.0",
474 deprecated: None,
475};
476
477pub const DIRECTNESS_PREFERENCE: AxisDefinition = AxisDefinition {
479 name: "directness_preference",
480 category: AxisCategory::Preferences,
481 description: "Preference for direct vs diplomatic communication style",
482 low_anchor: "Indirect, diplomatic, cushioned",
483 high_anchor: "Direct, straightforward, unvarnished",
484 intent: &[
485 "Adjust hedging and softening language",
486 "Calibrate directness of recommendations",
487 "Determine bluntness of negative feedback",
488 ],
489 forbidden_uses: &[
490 "Use directness as excuse for rudeness",
491 "Exploit indirect preference to avoid honest answers",
492 "Weaponize directness to intimidate",
493 ],
494 since: "0.1.0",
495 deprecated: None,
496};
497
498pub const AUTONOMY_PREFERENCE: AxisDefinition = AxisDefinition {
504 name: "autonomy_preference",
505 category: AxisCategory::Control,
506 description: "Desire for self-direction and control over outcomes",
507 low_anchor: "Open to guidance and external direction",
508 high_anchor: "Strong preference for self-direction and control",
509 intent: &[
510 "Adjust between guiding and following",
511 "Present options vs make recommendations",
512 "Calibrate how much to 'take over' vs 'support'",
513 ],
514 forbidden_uses: &[
515 "Override autonomy 'for user's own good'",
516 "Exploit low autonomy preference for manipulation",
517 "Punish high autonomy with reduced support",
518 ],
519 since: "0.1.0",
520 deprecated: None,
521};
522
523pub const SUGGESTION_TOLERANCE: AxisDefinition = AxisDefinition {
525 name: "suggestion_tolerance",
526 category: AxisCategory::Control,
527 description: "Openness to unsolicited suggestions and proactive offers",
528 low_anchor: "Only respond to explicit requests",
529 high_anchor: "Welcome proactive suggestions and upsells",
530 intent: &[
531 "Determine whether to offer unrequested alternatives",
532 "Calibrate proactive recommendation frequency",
533 "Gate cross-sell and upsell behaviors",
534 ],
535 forbidden_uses: &[
536 "Ignore low tolerance and push suggestions anyway",
537 "Exploit high tolerance for aggressive upselling",
538 "Treat low tolerance as a challenge to overcome",
539 ],
540 since: "0.1.0",
541 deprecated: None,
542};
543
544pub const INTERRUPTION_TOLERANCE: AxisDefinition = AxisDefinition {
546 name: "interruption_tolerance",
547 category: AxisCategory::Control,
548 description: "Tolerance for interruptions, interjections, and notifications",
549 low_anchor: "Do not interrupt; wait until finished",
550 high_anchor: "Interruptions and interjections are acceptable",
551 intent: &[
552 "Determine whether to interject with corrections",
553 "Calibrate notification and alert frequency",
554 "Decide when to interrupt with urgent updates",
555 ],
556 forbidden_uses: &[
557 "Interrupt to break user's train of thought strategically",
558 "Exploit tolerance for attention hijacking",
559 "Use interruptions to create urgency",
560 ],
561 since: "0.1.0",
562 deprecated: None,
563};
564
565pub const REFLECTION_VS_ACTION_BIAS: AxisDefinition = AxisDefinition {
567 name: "reflection_vs_action_bias",
568 category: AxisCategory::Control,
569 description: "Preference for deliberation vs immediate action",
570 low_anchor: "Action-oriented; minimize deliberation; just do it",
571 high_anchor: "Reflection-oriented; think carefully before acting",
572 intent: &[
573 "Adjust between 'do it now' and 'let's think about it'",
574 "Calibrate pros/cons presentation depth",
575 "Determine whether to offer 'undo' vs 'confirm' patterns",
576 ],
577 forbidden_uses: &[
578 "Exploit action bias to skip important evaluation",
579 "Frustrate reflective users with action pressure",
580 "Use reflection time as an opportunity to insert persuasion",
581 ],
582 since: "0.1.0",
583 deprecated: None,
584};
585
586pub const STAKES_AWARENESS: AxisDefinition = AxisDefinition {
592 name: "stakes_awareness",
593 category: AxisCategory::Safety,
594 description: "Perceived importance and risk level of current decisions",
595 low_anchor: "Low stakes; casual; easily reversible",
596 high_anchor: "High stakes; consequential; careful handling needed",
597 intent: &[
598 "Add confirmation steps for high-stakes actions",
599 "Emphasize reversibility and guarantees when stakes are high",
600 "Adjust error tolerance and verification depth",
601 ],
602 forbidden_uses: &[
603 "Artificially inflate stakes to create pressure",
604 "Exploit low stakes awareness to sneak in consequential changes",
605 "Dismiss high stakes concerns as overreaction",
606 ],
607 since: "0.1.0",
608 deprecated: None,
609};
610
611pub const PRIVACY_SENSITIVITY: AxisDefinition = AxisDefinition {
613 name: "privacy_sensitivity",
614 category: AxisCategory::Safety,
615 description: "Concern about information privacy and data minimization",
616 low_anchor: "Low privacy concern; sharing is fine",
617 high_anchor: "High privacy concern; minimize data collection",
618 intent: &[
619 "Minimize data collection when sensitivity is high",
620 "Offer privacy-preserving alternatives",
621 "Be explicit about data usage",
622 ],
623 forbidden_uses: &[
624 "Exploit low sensitivity for excessive data collection",
625 "Dismiss privacy concerns as paranoia",
626 "Condition service quality on privacy concessions",
627 ],
628 since: "0.1.0",
629 deprecated: None,
630};
631
632pub static CANONICAL_AXES: &[AxisDefinition] = &[
652 COGNITIVE_LOAD,
654 DECISION_FATIGUE,
655 TOLERANCE_FOR_COMPLEXITY,
656 URGENCY_SENSITIVITY,
657 EMOTIONAL_OPENNESS,
659 EMOTIONAL_STABILITY,
660 ANXIETY_LEVEL,
661 NEED_FOR_REASSURANCE,
662 WARMTH,
664 FORMALITY,
665 BOUNDARY_STRENGTH,
666 ASSERTIVENESS,
667 RECIPROCITY_EXPECTATION,
668 RITUAL_NEED,
670 TRANSACTIONAL_PREFERENCE,
671 VERBOSITY_PREFERENCE,
672 DIRECTNESS_PREFERENCE,
673 AUTONOMY_PREFERENCE,
675 SUGGESTION_TOLERANCE,
676 INTERRUPTION_TOLERANCE,
677 REFLECTION_VS_ACTION_BIAS,
678 STAKES_AWARENESS,
680 PRIVACY_SENSITIVITY,
681];
682
683pub fn get_axis(name: &str) -> Option<&'static AxisDefinition> {
685 CANONICAL_AXES.iter().find(|a| a.name == name)
686}
687
688pub fn is_valid_axis_name(name: &str) -> bool {
695 !name.is_empty()
696 && name.len() <= 64
697 && name
698 .chars()
699 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
700 && !name.starts_with('_')
701 && !name.ends_with('_')
702}
703
704#[cfg(test)]
705mod tests {
706 use super::*;
707
708 #[test]
709 fn test_canonical_axes_count() {
710 assert_eq!(CANONICAL_AXES.len(), 23);
712 }
713
714 #[test]
715 fn test_all_axes_have_forbidden_uses() {
716 for axis in CANONICAL_AXES {
717 assert!(
718 !axis.forbidden_uses.is_empty(),
719 "Axis '{}' must have at least one forbidden use",
720 axis.name
721 );
722 }
723 }
724
725 #[test]
726 fn test_all_axes_have_intent() {
727 for axis in CANONICAL_AXES {
728 assert!(
729 !axis.intent.is_empty(),
730 "Axis '{}' must have at least one intent",
731 axis.name
732 );
733 }
734 }
735
736 #[test]
737 fn test_all_axes_have_valid_names() {
738 for axis in CANONICAL_AXES {
739 assert!(
740 is_valid_axis_name(axis.name),
741 "Axis '{}' has invalid name",
742 axis.name
743 );
744 }
745 }
746
747 #[test]
748 fn test_get_axis() {
749 let axis = get_axis("cognitive_load");
750 assert!(axis.is_some());
751 assert_eq!(axis.unwrap().category, AxisCategory::Cognitive);
752
753 let missing = get_axis("nonexistent");
754 assert!(missing.is_none());
755 }
756
757 #[test]
758 fn test_categories_correct() {
759 let cognitive: Vec<_> = CANONICAL_AXES
760 .iter()
761 .filter(|a| a.category == AxisCategory::Cognitive)
762 .collect();
763 assert_eq!(cognitive.len(), 4);
764
765 let emotional: Vec<_> = CANONICAL_AXES
766 .iter()
767 .filter(|a| a.category == AxisCategory::Emotional)
768 .collect();
769 assert_eq!(emotional.len(), 4);
770
771 let social: Vec<_> = CANONICAL_AXES
772 .iter()
773 .filter(|a| a.category == AxisCategory::Social)
774 .collect();
775 assert_eq!(social.len(), 5);
776
777 let preferences: Vec<_> = CANONICAL_AXES
778 .iter()
779 .filter(|a| a.category == AxisCategory::Preferences)
780 .collect();
781 assert_eq!(preferences.len(), 4);
782
783 let control: Vec<_> = CANONICAL_AXES
784 .iter()
785 .filter(|a| a.category == AxisCategory::Control)
786 .collect();
787 assert_eq!(control.len(), 4);
788
789 let safety: Vec<_> = CANONICAL_AXES
790 .iter()
791 .filter(|a| a.category == AxisCategory::Safety)
792 .collect();
793 assert_eq!(safety.len(), 2);
794 }
795
796 #[test]
797 fn test_valid_axis_names() {
798 assert!(is_valid_axis_name("cognitive_load"));
799 assert!(is_valid_axis_name("warmth"));
800 assert!(is_valid_axis_name("axis123"));
801 }
802
803 #[test]
804 fn test_invalid_axis_names() {
805 assert!(!is_valid_axis_name(""));
806 assert!(!is_valid_axis_name("_starts_underscore"));
807 assert!(!is_valid_axis_name("ends_underscore_"));
808 assert!(!is_valid_axis_name("has spaces"));
809 assert!(!is_valid_axis_name("UPPERCASE"));
810 assert!(!is_valid_axis_name("has-dashes"));
811 }
812}