Skip to main content

supercode_interchange/ontology/
surface.rs

1//! The conversation-on-a-surface nouns (ORCH-3 / ORCH-6): where a
2//! conversation is reached, why it exists, which job it belongs to, where it
3//! moved. They are the vocabulary a [`crate::ontology::Binding`] is made of and
4//! the wire block every discovery row carries.
5
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9/// ORCH-3: why a session exists (the trigger noun; `docs/HERMES-IDEAL-SUPPORT-DESIGN.md` §3).
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
11#[serde(rename_all = "snake_case")]
12pub enum Trigger {
13    /// A person started it on a terminal, IDE, or chat surface.
14    Human,
15    /// A message on a channel opened it.
16    Channel,
17    /// A scheduled job fired it.
18    Cron,
19    /// A periodic main-session turn.
20    Heartbeat,
21    /// An inbound webhook / mapped hook.
22    Webhook,
23    /// Spawned by a parent session (delegate / subagent).
24    Parent,
25    /// An HTTP API / bridge client.
26    Api,
27    /// The source does not say.
28    #[default]
29    Unknown,
30}
31
32/// ORCH-3: the conversation identity on a surface. Full tuple on a channel;
33/// degenerate (all `None`) on a terminal.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
35pub struct SurfaceKey {
36    /// The harness's own key string, verbatim.
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub key: Option<String>,
39    /// Transport / platform (`telegram`, `slack`, `acp`, …).
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub platform: Option<String>,
42    /// `dm` | `group` | `channel` | `thread` | `main` (harness vocabulary, verbatim).
43    /// `chat_type` is the orchestrator IR's spelling of the same field.
44    #[serde(default, skip_serializing_if = "Option::is_none", alias = "chat_type")]
45    pub kind: Option<String>,
46    #[serde(default, skip_serializing_if = "Option::is_none")]
47    /// The chat / group / channel id on the platform.
48    pub chat_id: Option<String>,
49    #[serde(default, skip_serializing_if = "Option::is_none")]
50    /// The thread or topic within the chat.
51    pub thread_id: Option<String>,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    /// The participant, when the surface is per person.
54    pub participant_id: Option<String>,
55}
56
57impl SurfaceKey {
58    /// A key with no platform is not a channel surface.
59    pub fn is_channel(&self) -> bool {
60        self.platform.is_some()
61    }
62}
63
64/// ORCH-3: the job a recurring session belongs to.
65#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
66pub struct Recurrence {
67    /// The job id.
68    pub job_id: String,
69    /// `cron` | `heartbeat`.
70    #[serde(default = "recurrence_kind_cron")]
71    pub kind: String,
72}
73
74/// ORCH-3: a conversation moved to another surface (Hermes `handoff_*`).
75#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
76pub struct CrossSurface {
77    /// `pending` | `done` | `failed` (the source's own word).
78    pub state: String,
79    /// The destination platform.
80    #[serde(default, skip_serializing_if = "Option::is_none")]
81    pub platform: Option<String>,
82    /// The failure, when `state` is `failed`.
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub error: Option<String>,
85}
86
87/// ORCH-3 / UNI-9: the typed workspace, derived — never stored.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
89#[serde(rename_all = "snake_case")]
90pub enum WorkspaceKind {
91    /// A repository checkout (a cwd).
92    Repo,
93    /// A chat surface with no checkout.
94    Channel,
95    /// Neither.
96    None,
97}
98
99/// ORCH-6: the wire form of [`SessionMeta::workspace`] — a typed workspace
100/// carried on a discovered or loaded row. The D2 precedence that produces it
101/// lives in `workspace()` (ORCH-3's contract); this only names the result.
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
103pub struct WorkspaceRef {
104    /// `repo` | `channel` | `none`.
105    pub kind: WorkspaceKind,
106    /// Path for `repo`, `<platform>:<chat_id>` for `channel`, `None` for `none`.
107    #[serde(default, skip_serializing_if = "Option::is_none")]
108    pub value: Option<String>,
109}
110
111fn recurrence_kind_cron() -> String {
112    "cron".to_string()
113}