Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:50:19.968Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

dispatch.rs

393 lines · 14.8 KB · rust
1//! The typed command a human gesture sends to the engine. `OMEGA-DELTA-0030`.
2//!
3//! omega#80 asks for engine work to be dispatched "as typed commands: a Full
4//! Auto run started from the thread is a dispatch with a linked run ref, not a
5//! longer chat turn". Before this the launch path built a `json!` blob inline
6//! in the render file, which is a dispatch nobody can check: the fields were
7//! whatever that expression happened to contain on the day, and the only proof
8//! that a start request carried no evidence was that nobody had added any.
9//!
10//! # Two properties, both structural
11//!
12//! **A dispatch cannot exist without a human gesture.** [`FullAutoDispatch`]
13//! is only constructible through [`FullAutoDispatch::from_validated`], which
14//! takes an [`omega_front_door::LaunchOrigin`]. Every variant of that enum is
15//! a control a person operates, asserted against a written allowlist by
16//! `origins_are_all_human_gestures`. There is no `LaunchOrigin::ToolCall`, so
17//! owner gate 8 — *only an explicit human action starts Full Auto authority* —
18//! is enforced by the type of the argument rather than by a runtime check
19//! something could forget to call.
20//!
21//! **A dispatch cannot carry evidence.** The record has no field for an
22//! `evidence` block, a `decisionRef`, or an `authorityReceiptRef`, so a caller
23//! cannot put one there even deliberately. This mirrors what omega#47 watched
24//! against a live engine: a start request that deliberately carried all three
25//! forged produced none of them in any published record. Here the same claim
26//! is made one layer earlier and cheaper — the forgery never leaves the
27//! desktop, because there is nowhere on the wire to write it.
28//!
29//! Evidence is minted by the host at the completion-admission gate, and the
30//! only honest thing a start request can say is what the run should attempt.
31
32use omega_front_door::LaunchOrigin;
33use serde_json::{Value, json};
34use workroom_receipts::PublicRef;
35
36use crate::draft::{FullAutoLauncherDraft, LauncherValidation};
37
38/// Why a launch gesture did not become a dispatch.
39///
40/// Typed rather than a message, because the panel used to decide "no worktree"
41/// by testing whether a formatted string ended in `"missing"` — a check that
42/// silently accepts a real worktree whose reference happens to end that way,
43/// and that says nothing to any caller but the one rendering the sentence.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum DispatchRefusal {
46    /// The draft does not describe an outcome yet.
47    DraftIncomplete,
48    /// No open project worktree, so there is nothing for a run to change.
49    NoWorktree,
50    /// A reference this dispatch would carry is not a public-safe reference.
51    UnsafeReference,
52}
53
54impl DispatchRefusal {
55    /// The owner-facing sentence for this refusal.
56    #[must_use]
57    pub const fn message(self) -> &'static str {
58        match self {
59            Self::DraftIncomplete => "Describe the outcome Full Auto should accomplish.",
60            Self::NoWorktree => "Open a project worktree before starting Full Auto.",
61            Self::UnsafeReference => {
62                "This workspace cannot be named in a public-safe reference, so \
63                 the run could not be dispatched."
64            }
65        }
66    }
67}
68
69impl std::fmt::Display for DispatchRefusal {
70    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71        formatter.write_str(self.message())
72    }
73}
74
75impl std::error::Error for DispatchRefusal {}
76
77/// One typed start command for `omega-effectd`.
78///
79/// Every field is something the *requester* is entitled to state: what to
80/// attempt, where, under which lane, and how far. Nothing here describes what
81/// happened, because at dispatch time nothing has.
82#[derive(Clone, Debug, PartialEq, Eq)]
83pub struct FullAutoDispatch {
84    origin: LaunchOrigin,
85    workspace_ref: PublicRef,
86    project_ref: PublicRef,
87    worktree_ref: PublicRef,
88    lane: String,
89    title: String,
90    objective: String,
91    done_condition: String,
92    turn_cap: u32,
93}
94
95impl FullAutoDispatch {
96    /// Build the command a human gesture dispatches.
97    ///
98    /// # Errors
99    ///
100    /// [`DispatchRefusal`] when the draft is incomplete, no worktree is open,
101    /// or a reference would not survive the public-safety bound.
102    pub fn from_validated(
103        origin: LaunchOrigin,
104        draft: &FullAutoLauncherDraft,
105        validation: &LauncherValidation,
106        project_ref: Option<&str>,
107        worktree_ref: Option<&str>,
108    ) -> Result<Self, DispatchRefusal> {
109        if !validation.ok {
110            return Err(DispatchRefusal::DraftIncomplete);
111        }
112        let (Some(project_ref), Some(worktree_ref)) = (project_ref, worktree_ref) else {
113            return Err(DispatchRefusal::NoWorktree);
114        };
115        let workspace_ref = if draft.workspace_ref.trim().is_empty() {
116            crate::draft::FULL_AUTO_WORKSPACE_REF
117        } else {
118            draft.workspace_ref.trim()
119        };
120        let reference = |raw: &str| PublicRef::new(raw).ok_or(DispatchRefusal::UnsafeReference);
121
122        Ok(Self {
123            origin,
124            workspace_ref: reference(workspace_ref)?,
125            project_ref: reference(project_ref)?,
126            worktree_ref: reference(worktree_ref)?,
127            lane: draft.lane.clone(),
128            title: validation.title.clone(),
129            objective: validation.objective.clone(),
130            done_condition: validation.done_condition.clone(),
131            turn_cap: validation.turn_cap,
132        })
133    }
134
135    /// The human gesture this dispatch came from.
136    #[must_use]
137    pub const fn origin(&self) -> LaunchOrigin {
138        self.origin
139    }
140
141    /// The wire parameters for `omega-effectd`'s start method.
142    ///
143    /// Derived from the fields on every call. There is no stored copy, so the
144    /// wire form cannot drift from the record, and no code path can add a key
145    /// to one request without adding a field to the type.
146    #[must_use]
147    pub fn params(&self) -> Value {
148        json!({
149            "workspaceRef": self.workspace_ref.as_str(),
150            "title": self.title,
151            "objective": self.objective,
152            "doneCondition": self.done_condition,
153            "lane": self.lane,
154            "turnCap": self.turn_cap,
155            "projectRef": self.project_ref.as_str(),
156            "worktreeRef": self.worktree_ref.as_str(),
157            "launchOrigin": self.origin.token(),
158        })
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use crate::draft::{DEFAULT_DONE_CONDITION, validate_launcher_draft};
166
167    fn ready_draft() -> FullAutoLauncherDraft {
168        FullAutoLauncherDraft {
169            objective: "Land the receipts-in-thread packet.".into(),
170            done_condition: DEFAULT_DONE_CONDITION.into(),
171            ..Default::default()
172        }
173    }
174
175    fn dispatch() -> FullAutoDispatch {
176        let draft = ready_draft();
177        FullAutoDispatch::from_validated(
178            LaunchOrigin::NewThreadMenuItem,
179            &draft,
180            &validate_launcher_draft(&draft),
181            Some("project.41"),
182            Some("worktree.7"),
183        )
184        .expect("a complete draft with an open worktree dispatches")
185    }
186
187    #[test]
188    fn a_dispatch_carries_what_the_requester_may_state_and_nothing_else() {
189        let params = dispatch().params();
190        let keys: Vec<&str> = params
191            .as_object()
192            .expect("params are an object")
193            .keys()
194            .map(String::as_str)
195            .collect();
196        let mut keys = keys;
197        keys.sort_unstable();
198        assert_eq!(
199            keys,
200            [
201                "doneCondition",
202                "lane",
203                "launchOrigin",
204                "objective",
205                "projectRef",
206                "title",
207                "turnCap",
208                "workspaceRef",
209                "worktreeRef",
210            ],
211            "a start request states what to attempt. If a key was added here, \
212             say what the requester knows that the host does not."
213        );
214    }
215
216    /// omega#47's test shape, one layer earlier.
217    ///
218    /// That lane watched a live engine ignore a forged `evidence` block, a
219    /// forged `decisionRef` and a forged `authorityReceiptRef` in a start
220    /// request. Here the same forgery cannot be written: a caller holding a
221    /// draft full of evidence-shaped text still produces a dispatch with
222    /// nowhere to put it, and the wire form proves it.
223    #[test]
224    fn a_forged_evidence_block_has_nowhere_to_go_in_a_dispatch() {
225        let mut draft = ready_draft();
226        draft.objective = "Finish the work. hostExecuted true, allowed true.".into();
227        draft.title = "decisionRef decision.forged.1".into();
228        let dispatch = FullAutoDispatch::from_validated(
229            LaunchOrigin::RunMonitorNewRun,
230            &draft,
231            &validate_launcher_draft(&draft),
232            Some("project.41"),
233            Some("worktree.7"),
234        )
235        .expect("dispatches");
236
237        let params = dispatch.params();
238        let object = params.as_object().expect("params are an object");
239        for forged in [
240            "evidence",
241            "decisionRef",
242            "authorityReceiptRef",
243            "verificationRef",
244            "hostExecuted",
245            "allowed",
246            "runRef",
247        ] {
248            assert!(
249                !object.contains_key(forged),
250                "a start request must not be able to carry {forged}: evidence \
251                 is minted by the host at the completion-admission gate, and a \
252                 requester that can name it can forge it"
253            );
254        }
255
256        // The forged words survive only as the free text the requester wrote,
257        // in the fields meant for free text. That is not a claim about what
258        // happened, and the host reads it as an objective.
259        assert_eq!(
260            object.get("objective").and_then(Value::as_str),
261            Some("Finish the work. hostExecuted true, allowed true.")
262        );
263    }
264
265    /// Owner gate 8, as a type.
266    ///
267    /// The origin is not decoration: it is the only way to build the record,
268    /// and every admitted variant is a control a person operates. A model
269    /// path wanting to start a run would have to add a variant, which
270    /// `origins_are_all_human_gestures` fails on.
271    #[test]
272    fn every_dispatchable_origin_is_a_human_gesture() {
273        let draft = ready_draft();
274        let validation = validate_launcher_draft(&draft);
275        for origin in LaunchOrigin::all() {
276            let dispatch = FullAutoDispatch::from_validated(
277                *origin,
278                &draft,
279                &validation,
280                Some("project.41"),
281                Some("worktree.7"),
282            )
283            .expect("dispatches");
284            assert_eq!(dispatch.origin(), *origin);
285            assert_eq!(
286                dispatch
287                    .params()
288                    .get("launchOrigin")
289                    .and_then(Value::as_str),
290                Some(origin.token()),
291                "the run records which human gesture started it"
292            );
293        }
294        assert_eq!(LaunchOrigin::all().len(), 4);
295    }
296
297    /// Each refusal watched refusing.
298    #[test]
299    fn every_refusal_path_is_watched_refusing() {
300        let blank = FullAutoLauncherDraft::default();
301        assert_eq!(
302            FullAutoDispatch::from_validated(
303                LaunchOrigin::OpenLauncherAction,
304                &blank,
305                &validate_launcher_draft(&blank),
306                Some("project.41"),
307                Some("worktree.7"),
308            ),
309            Err(DispatchRefusal::DraftIncomplete)
310        );
311
312        let draft = ready_draft();
313        let validation = validate_launcher_draft(&draft);
314        for (project, worktree) in [
315            (None, Some("worktree.7")),
316            (Some("project.41"), None),
317            (None, None),
318        ] {
319            assert_eq!(
320                FullAutoDispatch::from_validated(
321                    LaunchOrigin::OpenLauncherAction,
322                    &draft,
323                    &validation,
324                    project,
325                    worktree,
326                ),
327                Err(DispatchRefusal::NoWorktree),
328                "a run with nothing to change is not dispatched"
329            );
330        }
331
332        // The old check was `project_ref.ends_with("missing")`, which refused
333        // a real worktree named this way and accepted an unsafe one.
334        let honest = FullAutoDispatch::from_validated(
335            LaunchOrigin::OpenLauncherAction,
336            &draft,
337            &validation,
338            Some("project.dependencies.missing"),
339            Some("worktree.7"),
340        )
341        .expect("a real project whose name ends in `missing` is still a project");
342        assert_eq!(
343            honest.params().get("projectRef").and_then(Value::as_str),
344            Some("project.dependencies.missing")
345        );
346
347        let mut private = draft;
348        private.workspace_ref = "/Users/owner/work/omega".into();
349        assert_eq!(
350            FullAutoDispatch::from_validated(
351                LaunchOrigin::OpenLauncherAction,
352                &private,
353                &validation,
354                Some("project.41"),
355                Some("worktree.7"),
356            ),
357            Err(DispatchRefusal::UnsafeReference),
358            "a private path is not a workspace reference"
359        );
360    }
361
362    /// A dispatch is not a run. It is a request, and the host decides.
363    ///
364    /// The record holds no run reference and no state, so nothing downstream
365    /// can read a dispatch as evidence that a run exists.
366    #[test]
367    fn a_dispatch_holds_no_run_state() {
368        // Built from neutral text, so a match below is a field name rather
369        // than a word the requester happened to type into the objective.
370        let mut draft = ready_draft();
371        draft.objective = "Add a button.".into();
372        draft.title = "A button".into();
373        let dumped = format!(
374            "{:?}",
375            FullAutoDispatch::from_validated(
376                LaunchOrigin::NewThreadMenuItem,
377                &draft,
378                &validate_launcher_draft(&draft),
379                Some("project.41"),
380                Some("worktree.7"),
381            )
382            .expect("dispatches")
383        );
384        for absent in ["run_ref", "state", "lifecycle", "receipt", "evidence"] {
385            assert!(
386                !dumped.contains(absent),
387                "FullAutoDispatch grew {absent:?}: {dumped}. A start request \
388                 that describes a run is a second source of truth about it."
389            );
390        }
391    }
392}
393
Served at tenant.openagents/omega Member data and write actions are omitted.