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    /// A board's dispatcher spawned it to work a task (the workflow layer).
28    Task,
29    /// The source does not say.
30    #[default]
31    Unknown,
32}
33
34/// ORCH-3: the conversation identity on a surface. Full tuple on a channel;
35/// degenerate (all `None`) on a terminal.
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default, JsonSchema)]
37pub struct SurfaceKey {
38    /// The harness's own key string, verbatim.
39    #[serde(default, skip_serializing_if = "Option::is_none")]
40    pub key: Option<String>,
41    /// Transport / platform (`telegram`, `slack`, `acp`, …).
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub platform: Option<String>,
44    /// `dm` | `group` | `channel` | `thread` | `main` (harness vocabulary, verbatim).
45    /// `chat_type` is the orchestrator IR's spelling of the same field.
46    #[serde(default, skip_serializing_if = "Option::is_none", alias = "chat_type")]
47    pub kind: Option<String>,
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    /// The chat / group / channel id on the platform.
50    pub chat_id: Option<String>,
51    #[serde(default, skip_serializing_if = "Option::is_none")]
52    /// The thread or topic within the chat.
53    pub thread_id: Option<String>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    /// The participant, when the surface is per person.
56    pub participant_id: Option<String>,
57}
58
59impl SurfaceKey {
60    /// A key with no platform is not a channel surface.
61    pub fn is_channel(&self) -> bool {
62        self.platform.is_some()
63    }
64}
65
66/// ORCH-3: the job a recurring session belongs to.
67#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
68pub struct Recurrence {
69    /// The job id.
70    pub job_id: String,
71    /// `cron` | `heartbeat`.
72    #[serde(default = "recurrence_kind_cron")]
73    pub kind: String,
74}
75
76/// ORCH-3: a conversation moved to another surface (Hermes `handoff_*`).
77#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
78pub struct CrossSurface {
79    /// `pending` | `done` | `failed` (the source's own word).
80    pub state: String,
81    /// The destination platform.
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub platform: Option<String>,
84    /// The failure, when `state` is `failed`.
85    #[serde(default, skip_serializing_if = "Option::is_none")]
86    pub error: Option<String>,
87}
88
89/// ORCH-3 / UNI-9: the typed workspace, derived — never stored.
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
91#[serde(rename_all = "snake_case")]
92pub enum WorkspaceKind {
93    /// A repository checkout (a cwd).
94    Repo,
95    /// A chat surface with no checkout.
96    Channel,
97    /// Neither.
98    None,
99}
100
101/// ORCH-6: the wire form of [`SessionMeta::workspace`] — a typed workspace
102/// carried on a discovered or loaded row. The D2 precedence that produces it
103/// lives in `workspace()` (ORCH-3's contract); this only names the result.
104#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
105pub struct WorkspaceRef {
106    /// `repo` | `channel` | `none`.
107    pub kind: WorkspaceKind,
108    /// Path for `repo`, `<platform>:<chat_id>` for `channel`, `None` for `none`.
109    #[serde(default, skip_serializing_if = "Option::is_none")]
110    pub value: Option<String>,
111}
112
113fn recurrence_kind_cron() -> String {
114    "cron".to_string()
115}