Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T00:58:50.408Z 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

Revision diff

e5b422b1 1f732301
diff --git a/OMEGA_DELTAS.md b/OMEGA_DELTAS.md
index 357ddc9f18..7cb32ca55d 100644
--- a/OMEGA_DELTAS.md
+++ b/OMEGA_DELTAS.md
@@ -2175,3 +2175,141 @@ than it sounds, because the harness omega#81's acceptance sentence names —
21752175   Tier B's to fix. The lane reads Exo's durable event log after a turn and
21762176   currently only logs a failure to do so, because the turn has already run and
21772177   refusing it afterwards would describe a world that did not happen.
2178+
2179+### OMEGA-DELTA-0043 — `--uninstall` removes the installation, not one file inside it
2180+
2181+- **Upstream Zed:** `script/uninstall.sh` removes `/Applications/Zed.app` — the
2182+  bundle — along with the user-level directories. Correct for Zed.
2183+- **Omega, before this:** `OMEGA-DELTA-0036` moved the path table out of the
2184+  shell script and into `UninstallRoots`, built from the `paths::` functions
2185+  that write those directories, and that fix is real: the plan derived by the
2186+  signed `0.2.0-rc16` holds nine Omega paths and zero belonging to anybody else.
2187+  But `crates/cli/src/main.rs` handed it `app.path()`, which on macOS is
2188+  `Omega.app/Contents/MacOS/omega` — **one executable inside the bundle** — into
2189+  a field whose doc comment read "the application bundle **or executable** this
2190+  CLI belongs to". So a full uninstall run against the signed `0.2.0-rc16`
2191+  reported success and left `/Applications/Omega.app` standing with **130.9 MB
2192+  and five executables** in it: `cli`, which still carries `--uninstall`,
2193+  `omega-identity-proof`, `omega-effectd` and its **bundled Node runtime**. Zed
2194+  was safe and Omega was not removed; the user was left to drag a signed,
2195+  non-functional bundle out of `/Applications` by hand (omega#92).
2196+- **Why nothing caught it.** The end-to-end test planted the app root as a
2197+  directory holding a single marker file, and asserted the root was gone.
2198+  "Removed the bundle" and "removed the one file the plan named" are the same
2199+  observation from a root like that. And `InstalledApp` had a single `path()`
2200+  answering two different questions, shadowed on macOS by an inherent `path()`
2201+  that returned the bundle — so the same call spelled two ways returned two
2202+  different paths, and the uninstaller took the wrong one.
2203+- **Omega now:** `InstalledApp` has two methods with two names.
2204+  `path()` is the executable to run; `installation_root()` is the whole
2205+  directory a removal has to remove, and on macOS it is the `.app`. The
2206+  ambiguous inherent `path()` is gone, so there is one answer per question. The
2207+  field is `UninstallRoots::app_root`, and `from_installed_paths` runs whatever
2208+  it is given through `uninstall::installation_root`, which resolves any path
2209+  under a `.app` to the outermost `.app` — a caller holding an executable, which
2210+  every macOS caller does, cannot reintroduce the defect from outside. A loose
2211+  development build with no bundle above it is returned unchanged, because
2212+  inventing a root there would plan its parent directory for removal.
2213+- **Enforced by:** `the_uninstall_plan_names_the_installation_root` in
2214+  `crates/omega_deltas/` (the derivation exists, the constructor normalizes
2215+  through it, the call site asks for the installation, and the trait still
2216+  distinguishes the two), and by two tests in `crates/cli/src/uninstall.rs`:
2217+  `the_plan_names_the_bundle_root_and_never_an_executable_inside_it`, which
2218+  asserts both directions — the bundle is planned, and no file inside it stands
2219+  in for it — and the end-to-end
2220+  `the_script_removes_omega_and_leaves_the_other_editor_untouched`, whose
2221+  fabricated bundle now holds `omega`, `cli`, `omega-identity-proof`, the
2222+  `omega-effectd` bundle and its Node runtime, every one of which is read back
2223+  after the real script has run. The bundle path in that test is named
2224+  independently of the plan, so a plan that names the executable plants its
2225+  files somewhere else and fails rather than moving with the defect.
2226+- **Proved against the product, not only the tests.** The plan was derived by
2227+  running the built `cli --uninstall` with `OMEGA_UNINSTALL_DRY_RUN=1` under an
2228+  isolated `HOME` against a fabricated bundle, exactly as omega#92 recorded the
2229+  defect. Before: `remove: …/Omega.app/Contents/MacOS/omega`. After:
2230+  `remove: …/Omega.app`, with the other eight roots unchanged.
2231+- **Falsified.** Reverting the call site to `app.path()` reproduced the
2232+  executable in the product's own dry-run plan and failed the delta test.
2233+  Making `installation_root` return its argument unchanged failed the plan test
2234+  and left `cli` alive in the end-to-end test. Both edits were probed in the
2235+  file and in the rebuilt binary before their tests ran.
2236+- **Handled with the care this path has earned.** This is the code that
2237+  destroyed the owner's real Zed installation on 2026-07-25, when a
2238+  falsification restored the old script and one test did not override `HOME`.
2239+  Every manual exercise here ran with `OMEGA_UNINSTALL_DRY_RUN=1` and an
2240+  isolated `HOME`, against a fabricated bundle in a scratch directory. Nothing
2241+  was ever run against `/Applications`.
2242+
2243+### OMEGA-DELTA-0044 — A brand-bearing command form is user-facing text
2244+
2245+- **Upstream Zed:** the CLI's interactive open-behavior prompt shows
2246+  `zed --existing`, `zed --classic` and `zed <path>`. Correct for Zed.
2247+- **Omega, before this:** those three literals shipped verbatim in the signed
2248+  `cli` of `0.2.0-rc16`, inside a panel whose surrounding copy reads "Add to
2249+  existing **Omega** window", "You can change this later in **Omega**
2250+  settings" — naming our product twice and somebody else's binary three times.
2251+  `zed --existing` and `zed --classic` are not Omega commands at all
2252+  (omega#93).
2253+- **They are product claims by the gate's own rule.** `OMEGA-DELTA-0031` is
2254+  written around one test: substitute our own name, and if the sentence stays
2255+  true the brand was standing where our product's name belongs. "Add to existing
2256+  Omega window (`omega --existing`)" is true of Omega. All three are rewrites,
2257+  not classifications.
2258+- **Why the gate passed.** `is_prose` requires three tokens, so a two-token
2259+  command form is invisible to it. That blind spot was *known* — the
2260+  `0.2.0-rc16` release notes record "does not classify one- and two-word
2261+  labels" — and this is it biting: the three literals were in neither
2262+  `prose.classified` nor the compatibility allow-list, and
2263+  `verify-omega-brand --app` exited 0.
2264+- **Omega now:** the prompt builds every command form from
2265+  `paths::BINARY_NAME`, so it cannot drift from the binary it describes. And
2266+  the blind spot is **narrowed rather than papered over**: the inventory admits
2267+  prose *or* a command form — a brand word standing in `argv[0]` followed by
2268+  flags, placeholders or paths — in all three streams (Rust literals, doc lines,
2269+  embedded assets), on both the Python and the Rust side. Adding the three
2270+  strings to a denylist instead would have reproduced exactly the defect the
2271+  inverted inventory was built to end.
2272+- **The width was measured, not asserted.** Across the whole tree the
2273+  command-form shape adds **3** items — the three defects, and nothing else.
2274+  Admitting any two-token literal beginning with a brand word instead adds
2275+  **11** (8 more: `Zed Pro`, `Zed Agent`, `Zed AI`, `Zed Repository`,
2276+  `Zed Twitter`, `Zed Default`, and `Zed (Default)` twice), all genuine
2277+  references to somebody else's product that would need classifying. Admitting a
2278+  bare brand token adds **58**. A gate that cries wolf gets deleted, which is
2279+  how a known blind spot stays open; the shape that costs nothing is the one
2280+  that can be carried.
2281+- **Enforced by:** `a_brand_bearing_command_form_is_user_facing_text` in
2282+  `crates/omega_deltas/` (the three shipped literals are invisible to `is_prose`
2283+  and visible to `is_command_form`; the eight two-word labels and a bare brand
2284+  token are not command forms; and `script/verify-omega-brand` carries the same
2285+  rule wired into the same three streams, so the source and packaged sides
2286+  cannot drift), and `the_cli_prompt_names_our_own_binary`, which reads
2287+  `prompt_open_behavior` and fails on any brand hit or on a command form not
2288+  built from `paths::BINARY_NAME`.
2289+- **Reachability, and what was actually observed.** `resolve_open_behavior` in
2290+  `crates/zed/src/zed/open_listener.rs` sends `CliResponse::PromptOpenBehavior`
2291+  when there are existing windows, the paths are not already in a workspace, at
2292+  least one path is a directory, and the settings file has no
2293+  `cli_default_open_behavior`. omega#93 recorded a *shipped-string* result,
2294+  because the reporting lane could not get the installed CLI to render the panel
2295+  with all four conditions held. This lane rendered it: the built `cli` was run
2296+  under a pty against a stand-in for the editor that performs the real IPC
2297+  handshake and sends `PromptOpenBehavior`, and the panel came up reading
2298+
2299+  ```
2300+  Configure default behavior for omega <path>
2301+  You can change this later in Omega settings:
2302+  > Add to existing Omega window (omega --existing)
2303+    Open a new window (omega --classic)
2304+  ```
2305+
2306+  So the CLI half is live code, not dead code, and the copy it renders is ours.
2307+  What that does **not** establish is that a running Omega reaches the four send
2308+  conditions in practice — the stand-in supplied the response. That half is
2309+  still unobserved, and the panel is still worth deleting if nobody can make a
2310+  real Omega send it.
2311+- **Falsified.** Restoring `zed --existing` failed both the prompt test and the
2312+  unclassified-prose test. Restoring the three-token rule alone — leaving the
2313+  literals fixed — failed the command-form test. Removing the rule from
2314+  `script/verify-omega-brand` while keeping it in Rust failed the parity
2315+  assertion. Each edit was probed in the file before its test ran.
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index 3dfdf0cdb5..4bcec5d347 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -43,7 +43,17 @@ trait InstalledApp {
4343         ipc_url: String,
4444         user_data_dir: Option<&str>,
4545     ) -> io::Result<ExitStatus>;
46+    /// The executable to run — one file.
4647     fn path(&self) -> PathBuf;
48+    /// The whole installation — the directory `--uninstall` has to remove.
49+    ///
50+    /// Distinct from `path` because on macOS they are never the same thing:
51+    /// `path` is `Omega.app/Contents/MacOS/omega`, and the installation is
52+    /// `Omega.app`, which also holds `cli`, `omega-identity-proof`, and a
53+    /// bundled Node runtime. `0.2.0-rc16` passed `path` to the uninstaller, so
54+    /// a full uninstall left 130.9 MB and five executables in `/Applications`,
55+    /// among them a `cli` that still carried `--uninstall` (omega#92).
56+    fn installation_root(&self) -> PathBuf;
4757 }
4858 
4959 #[derive(Parser, Debug)]
@@ -581,8 +591,12 @@ fn run() -> Result<()> {
581591         use std::os::unix::fs::PermissionsExt as _;
582592         fs::set_permissions(&script_path, fs::Permissions::from_mode(0o755))?;
583593 
584-        let plan = cli::uninstall::UninstallRoots::from_installed_paths(Some(app.path()))
585-            .plan(paths::app_name());
594+        // `installation_root()`, never `path()`: on macOS the executable is one
595+        // file inside `Omega.app`, and removing it leaves the bundle, four more
596+        // executables and a bundled Node runtime behind (omega#92).
597+        let plan =
598+            cli::uninstall::UninstallRoots::from_installed_paths(Some(app.installation_root()))
599+                .plan(paths::app_name());
586600 
587601         let status = std::process::Command::new("sh")
588602             .arg(&script_path)
@@ -863,17 +877,27 @@ fn prompt_open_behavior() -> Option<cli::CliBehaviorSetting> {
863877     }
864878 
865879     let blue = console::Style::new().blue();
880+    // Every command form here is built from `paths::BINARY_NAME`, never
881+    // written out. The literals `zed --existing`, `zed --classic` and
882+    // `zed <path>` shipped in the signed `cli` of `0.2.0-rc16`, in a panel
883+    // whose surrounding copy said "Omega window" and "Omega settings", so it
884+    // named our product twice and somebody else's binary three times
885+    // (omega#93). A command form cannot drift from the binary it describes if
886+    // it is spelled by the binary's own name.
866887     let items = [
867888         format!(
868889             "Add to existing Omega window ({})",
869-            blue.apply_to("zed --existing")
890+            blue.apply_to(format!("{} --existing", paths::BINARY_NAME))
891+        ),
892+        format!(
893+            "Open a new window ({})",
894+            blue.apply_to(format!("{} --classic", paths::BINARY_NAME))
870895         ),
871-        format!("Open a new window ({})", blue.apply_to("zed --classic")),
872896     ];
873897 
874898     let prompt = format!(
875899         "Configure default behavior for {}\n{}",
876-        blue.apply_to("zed <path>"),
900+        blue.apply_to(format!("{} <path>", paths::BINARY_NAME)),
877901         console::style("You can change this later in Omega settings"),
878902     );
879903 
@@ -991,6 +1015,14 @@ mod linux {
9911015         fn path(&self) -> PathBuf {
9921016             self.0.clone()
9931017         }
1018+
1019+        fn installation_root(&self) -> PathBuf {
1020+            // A Linux install is loose files under a prefix — `libexec`,
1021+            // `bin`, `share` — with no single directory that is only Omega, so
1022+            // the editor binary is the honest answer here. macOS is where the
1023+            // installation really is one directory.
1024+            self.0.clone()
1025+        }
9941026     }
9951027 
9961028     impl App {
@@ -1256,6 +1288,11 @@ mod windows {
12561288         fn path(&self) -> PathBuf {
12571289             self.0.clone()
12581290         }
1291+
1292+        fn installation_root(&self) -> PathBuf {
1293+            // Windows has no `--uninstall`; the installer owns removal there.
1294+            self.0.clone()
1295+        }
12591296     }
12601297 
12611298     impl Detect {
@@ -1371,7 +1408,15 @@ mod mac_os {
13711408 
13721409     impl InstalledApp for Bundle {
13731410         fn app_version_string(&self) -> String {
1374-            format!("Omega {} – {}", self.version(), self.path().display(),)
1411+            // The installation, which is what a person can point at in Finder.
1412+            // This used to read `self.path()`, resolved to an inherent method
1413+            // that returned the bundle while the trait method of the same name
1414+            // returned the executable. That ambiguity is what omega#92 was.
1415+            format!(
1416+                "Omega {} – {}",
1417+                self.version(),
1418+                self.installation_root().display(),
1419+            )
13751420         }
13761421 
13771422         fn launch(&self, url: String, user_data_dir: Option<&str>) -> anyhow::Result<()> {
@@ -1466,6 +1511,15 @@ mod mac_os {
14661511                 Bundle::LocalPath { executable, .. } => executable.clone(),
14671512             }
14681513         }
1514+
1515+        fn installation_root(&self) -> PathBuf {
1516+            match self {
1517+                // The bundle, not the executable inside it. Everything Omega
1518+                // installs on macOS lives under this directory.
1519+                Bundle::App { app_bundle, .. } => app_bundle.clone(),
1520+                Bundle::LocalPath { executable, .. } => executable.clone(),
1521+            }
1522+        }
14691523     }
14701524 
14711525     impl Bundle {
@@ -1475,13 +1529,6 @@ mod mac_os {
14751529                 Self::LocalPath { .. } => "<development>".to_string(),
14761530             }
14771531         }
1478-
1479-        fn path(&self) -> &Path {
1480-            match self {
1481-                Self::App { app_bundle, .. } => app_bundle,
1482-                Self::LocalPath { executable, .. } => executable,
1483-            }
1484-        }
14851532     }
14861533 
14871534     pub(super) fn spawn_channel_cli(
diff --git a/crates/cli/src/uninstall.rs b/crates/cli/src/uninstall.rs
index 55437afec4..e5772d63d0 100644
--- a/crates/cli/src/uninstall.rs
+++ b/crates/cli/src/uninstall.rs
@@ -16,6 +16,30 @@
1616 
1717 use std::path::{Path, PathBuf};
1818 
19+/// The installation root containing `path`.
20+///
21+/// On macOS an Omega installation is one directory — `Omega.app` — and every
22+/// path a caller has to hand names something *inside* it: the running
23+/// executable, the `cli` beside it, a resource. Removing what the caller
24+/// happened to be holding is not an uninstall, so any path under a `.app`
25+/// resolves to the outermost `.app` in it. Anything else, including a
26+/// development build sitting loose in `target/debug`, is returned unchanged.
27+#[must_use]
28+pub fn installation_root(path: &Path) -> PathBuf {
29+    let mut root: Option<&Path> = None;
30+    let mut cursor = Some(path);
31+    while let Some(candidate) = cursor {
32+        if candidate
33+            .extension()
34+            .is_some_and(|extension| extension.eq_ignore_ascii_case("app"))
35+        {
36+            root = Some(candidate);
37+        }
38+        cursor = candidate.parent().filter(|parent| !parent.as_os_str().is_empty());
39+    }
40+    root.unwrap_or(path).to_path_buf()
41+}
42+
1943 /// The roots an Omega installation occupies on this machine.
2044 ///
2145 /// One field per place Omega writes. `plan` destructures this exhaustively, so
@@ -23,8 +47,18 @@ use std::path::{Path, PathBuf};
2347 /// property the old shell table could not have.
2448 #[derive(Debug, Clone, PartialEq, Eq)]
2549 pub struct UninstallRoots {
26-    /// The application bundle or executable this CLI belongs to.
27-    pub app: Option<PathBuf>,
50+    /// The installation root — the whole directory Omega occupies, never one
51+    /// executable inside it.
52+    ///
53+    /// The name and the doc comment used to read "the application bundle or
54+    /// executable", and the caller took the second reading: `0.2.0-rc16`
55+    /// planned `Omega.app/Contents/MacOS/omega`, so a completed uninstall left
56+    /// `Omega.app` in `/Applications` with 130.9 MB, four more executables and
57+    /// a bundled Node runtime in it — including a `cli` that still carried
58+    /// `--uninstall` (omega#92). `installation_root` now resolves any path
59+    /// inside a bundle to the bundle itself, so a caller that hands over an
60+    /// executable cannot reintroduce that.
61+    pub app_root: Option<PathBuf>,
2862     /// `paths::data_dir()` — database, extensions, embeddings.
2963     pub data_dir: PathBuf,
3064     /// `paths::config_dir()` — settings and keymap. Prompted for, never
@@ -62,7 +96,7 @@ impl UninstallRoots {
6296     /// Every field is read from the function that writes it. Nothing here is a
6397     /// literal path.
6498     #[cfg(any(target_os = "linux", target_os = "macos"))]
65-    pub fn from_installed_paths(app: Option<PathBuf>) -> Self {
99+    pub fn from_installed_paths(app_root: Option<PathBuf>) -> Self {
66100         let app_id = release_channel::RELEASE_CHANNEL.app_id();
67101         let home = paths::home_dir();
68102         let platform_paths = if cfg!(target_os = "macos") {
@@ -79,7 +113,9 @@ impl UninstallRoots {
79113             ]
80114         };
81115         Self {
82-            app,
116+            // Normalized here rather than trusted from the caller: the caller
117+            // holds an executable, and every macOS caller always will.
118+            app_root: app_root.as_deref().map(installation_root),
83119             data_dir: paths::data_dir().clone(),
84120             config_dir: paths::config_dir().clone(),
85121             logs_dir: paths::logs_dir().clone(),
@@ -96,7 +132,7 @@ impl UninstallRoots {
96132     /// settings and keymap, and the caller asks before removing it.
97133     pub fn plan(&self, product: &str) -> UninstallPlan {
98134         let Self {
99-            app,
135+            app_root,
100136             data_dir,
101137             config_dir,
102138             logs_dir,
@@ -113,8 +149,8 @@ impl UninstallRoots {
113149                 removals.push(path);
114150             }
115151         };
116-        if let Some(app) = app {
117-            push(app);
152+        if let Some(app_root) = app_root {
153+            push(app_root);
118154         }
119155         push(data_dir);
120156         push(logs_dir);
@@ -152,9 +188,17 @@ impl UninstallPlan {
152188 mod tests {
153189     use super::*;
154190 
191+    /// The path the CLI actually holds: the executable it is running beside,
192+    /// inside the bundle. Every root below is derived from it the way the
193+    /// product derives it, so a plan that names the executable fails here
194+    /// rather than in `/Applications`.
195+    fn installed_executable(home: &Path) -> PathBuf {
196+        home.join("Applications/Omega.app/Contents/MacOS/omega")
197+    }
198+
155199     fn roots(home: &Path) -> UninstallRoots {
156200         UninstallRoots {
157-            app: Some(home.join("Applications/Omega.app")),
201+            app_root: Some(installation_root(&installed_executable(home))),
158202             data_dir: home.join("Library/Application Support/Omega RC"),
159203             config_dir: home.join(".config/omega-rc"),
160204             logs_dir: home.join("Library/Logs/omega-rc"),
@@ -198,6 +242,58 @@ mod tests {
198242         );
199243     }
200244 
245+    /// OMEGA-DELTA-0043. The plan names the installation, not one file in it.
246+    ///
247+    /// `0.2.0-rc16` planned `Omega.app/Contents/MacOS/omega`, so an uninstall
248+    /// that reported success left the bundle, the `cli` that carries
249+    /// `--uninstall`, `omega-identity-proof` and a bundled Node runtime in
250+    /// `/Applications` (omega#92). Both directions are asserted: the bundle is
251+    /// in the plan, and the executable is not standing in for it.
252+    #[test]
253+    fn the_plan_names_the_bundle_root_and_never_an_executable_inside_it() {
254+        let home = Path::new("/tmp/omega-uninstall-fixture");
255+        let bundle = home.join("Applications/Omega.app");
256+        let plan = roots(home).plan("Omega RC");
257+
258+        assert!(
259+            plan.removals.contains(&bundle),
260+            "the plan does not remove {}, so the installation survives an \
261+             uninstall that reports success",
262+            bundle.display()
263+        );
264+        for inside in [
265+            installed_executable(home),
266+            bundle.join("Contents/MacOS/cli"),
267+            bundle.join("Contents/Resources/omega-effectd/runtime/bin/node"),
268+        ] {
269+            assert!(
270+                !plan.removals.contains(&inside),
271+                "the plan names {}, a file inside the bundle. Removing one file \
272+                 out of an installation is not an uninstall.",
273+                inside.display()
274+            );
275+        }
276+
277+        // The derivation itself, on the shapes a caller can hand it.
278+        assert_eq!(
279+            installation_root(&installed_executable(home)),
280+            bundle,
281+            "a path inside a bundle resolves to the bundle"
282+        );
283+        assert_eq!(
284+            installation_root(&bundle),
285+            bundle,
286+            "the bundle resolves to itself"
287+        );
288+        let development = Path::new("/tmp/omega/target/debug/omega");
289+        assert_eq!(
290+            installation_root(development),
291+            development,
292+            "a loose development build has no bundle to climb to, and inventing \
293+             one would plan its parent directory for removal"
294+        );
295+    }
296+
201297     /// OMEGA-DELTA-0036. The shipped script, run for real, against a machine
202298     /// that has both editors installed.
203299     ///
@@ -209,10 +305,33 @@ mod tests {
209305         let home = temp.path();
210306         let roots = roots(home);
211307 
212-        // An Omega installation.
308+        // An Omega installation. The bundle is planted the way one ships:
309+        // several executables and a bundled Node runtime under the same root,
310+        // because "the app is gone" and "one file in the app is gone" look
311+        // identical from a root that holds a single marker (omega#92).
312+        // Named here, not read out of `roots`: reading it back from the plan
313+        // would make this circular, and a plan that named the executable would
314+        // plant its files under the executable and pass.
315+        let bundle = home.join("Applications/Omega.app");
316+        let bundle_contents: Vec<PathBuf> = [
317+            "Contents/Info.plist",
318+            "Contents/MacOS/omega",
319+            "Contents/MacOS/cli",
320+            "Contents/MacOS/omega-identity-proof",
321+            "Contents/Resources/omega-effectd/dist/omega-effectd.mjs",
322+            "Contents/Resources/omega-effectd/runtime/bin/node",
323+        ]
324+        .iter()
325+        .map(|relative| bundle.join(relative))
326+        .collect();
327+        for path in &bundle_contents {
328+            std::fs::create_dir_all(path.parent().unwrap()).expect("create bundle directory");
329+            std::fs::write(path, b"omega").expect("write bundle file");
330+        }
331+
213332         let mut planted = Vec::new();
214333         for directory in [
215-            &roots.app.clone().unwrap(),
334+            &bundle,
216335             &roots.data_dir,
217336             &roots.config_dir,
218337             &roots.logs_dir,
@@ -273,7 +392,7 @@ mod tests {
273392         // root would pass it. Destructuring is exhaustive, so a root added to
274393         // the struct and left out of the plan fails to compile here.
275394         let UninstallRoots {
276-            app,
395+            app_root,
277396             data_dir,
278397             config_dir,
279398             logs_dir,
@@ -282,7 +401,7 @@ mod tests {
282401             cli_symlink,
283402             platform_paths,
284403         } = &roots;
285-        let occupied: Vec<&PathBuf> = app
404+        let occupied: Vec<&PathBuf> = app_root
286405             .iter()
287406             .chain([data_dir, config_dir, logs_dir, temp_dir, state_dir, cli_symlink])
288407             .chain(platform_paths.iter())
@@ -294,6 +413,20 @@ mod tests {
294413                 path.display()
295414             );
296415         }
416+
417+        // And nothing inside the bundle either. A plan that names the
418+        // executable removes one file and reports success, which is what
419+        // `0.2.0-rc16` shipped: 130.9 MB and five executables survived
420+        // (omega#92). The root check above cannot see that, because the root it
421+        // was handed would have been the executable.
422+        for path in &bundle_contents {
423+            assert!(
424+                !path.exists(),
425+                "{} survived the uninstall. The plan removed something inside \
426+                 the installation instead of the installation.",
427+                path.display()
428+            );
429+        }
297430         for path in &foreign {
298431             assert_eq!(
299432                 std::fs::read(path).ok().as_deref(),
diff --git a/crates/omega_deltas/src/omega_deltas.rs b/crates/omega_deltas/src/omega_deltas.rs
index 26f6a4e286..4aab74ace7 100644
--- a/crates/omega_deltas/src/omega_deltas.rs
+++ b/crates/omega_deltas/src/omega_deltas.rs
@@ -70,6 +70,8 @@ pub const ENFORCED_DELTAS: &[&str] = &[
7070     "OMEGA-DELTA-0040",
7171     "OMEGA-DELTA-0041",
7272     "OMEGA-DELTA-0042",
73+    "OMEGA-DELTA-0043",
74+    "OMEGA-DELTA-0044",
7375 ];
7476 
7577 /// OMEGA-DELTA-0036. The uninstall script embedded in the shipped `cli`.
@@ -78,6 +80,10 @@ pub const UNINSTALL_SCRIPT_PATH: &str = "script/uninstall.sh";
7880 /// OMEGA-DELTA-0036. Where the removal plan is derived from `paths::`.
7981 pub const UNINSTALL_PLAN_PATH: &str = "crates/cli/src/uninstall.rs";
8082 
83+/// OMEGA-DELTA-0043, OMEGA-DELTA-0044. The CLI binary: it derives the
84+/// uninstall plan and renders the open-behavior prompt.
85+pub const CLI_MAIN_PATH: &str = "crates/cli/src/main.rs";
86+
8187 /// OMEGA-DELTA-0037. Outbound attribution on OpenRouter requests.
8288 pub const OPEN_ROUTER_PATH: &str = "crates/open_router/src/open_router.rs";
8389 
@@ -1466,6 +1472,65 @@ pub fn is_prose(text: &str) -> bool {
14661472     plain >= 2
14671473 }
14681474 
1475+/// Whether `text` is a command line whose program name is a competitor's
1476+/// binary.
1477+///
1478+/// OMEGA-DELTA-0044. `is_prose` needs three tokens, so `zed --existing`,
1479+/// `zed --classic` and `zed <path>` were invisible to it — and all three
1480+/// shipped in the signed `cli` of `0.2.0-rc16`, in an interactive panel whose
1481+/// surrounding copy says "Omega window" and "Omega settings" around them
1482+/// (omega#93). Substitute our own name and each stays true, so by the rule
1483+/// this gate is written around they are product claims, not references.
1484+///
1485+/// This admits the narrow shape only: a brand word standing in `argv[0]`,
1486+/// followed by flags, placeholders or paths. Admitting every two-token literal
1487+/// that starts with a brand word instead adds 8 more across the tree — `Zed
1488+/// Pro`, `Zed (Default)`, `Zed Repository` — which are genuine references to
1489+/// somebody else's product; admitting a bare brand token adds 55. This shape
1490+/// adds exactly the three, which is why the gate can afford to carry it
1491+/// instead of writing the three literals into a denylist.
1492+#[must_use]
1493+pub fn is_command_form(text: &str, policy: &serde_json::Value) -> bool {
1494+    let words: Vec<&str> = policy["brand"]["words"]
1495+        .as_array()
1496+        .map(|values| values.iter().filter_map(serde_json::Value::as_str).collect())
1497+        .unwrap_or_default();
1498+    let tokens: Vec<&str> = text.split_whitespace().collect();
1499+    if tokens.len() < 2 || !words.contains(&tokens[0]) {
1500+        return false;
1501+    }
1502+    tokens[1..].iter().all(|token| is_command_argument(token))
1503+}
1504+
1505+/// A flag (`-n`, `--existing`), a placeholder (`<path>`, `[FILE]`), or a path
1506+/// (`.`, `..`, `~/x`, `./x`, `/x`).
1507+fn is_command_argument(token: &str) -> bool {
1508+    let flag = token.strip_prefix("--").or_else(|| token.strip_prefix('-'));
1509+    if let Some(rest) = flag {
1510+        return rest
1511+            .chars()
1512+            .next()
1513+            .is_some_and(|character| character.is_ascii_alphanumeric())
1514+            && rest
1515+                .chars()
1516+                .all(|character| character.is_ascii_alphanumeric() || "-_.".contains(character));
1517+    }
1518+    let placeholder = matches!(token.chars().next(), Some('<' | '[' | '{'))
1519+        && matches!(token.chars().last(), Some('>' | ']' | '}'))
1520+        && token.len() >= 2
1521+        && !token[1..token.len() - 1].contains(['>', ']', '}']);
1522+    if placeholder {
1523+        return true;
1524+    }
1525+    token == "." || token == ".." || token.starts_with(['~', '/']) || token.starts_with("./")
1526+}
1527+
1528+/// Prose, or a command form. What the prose inventory admits.
1529+#[must_use]
1530+pub fn is_user_facing_text(text: &str, policy: &serde_json::Value) -> bool {
1531+    is_prose(text) || is_command_form(&normalize_prose(text), policy)
1532+}
1533+
14691534 /// Every Rust string literal in `source`, as `(start line, contents)`.
14701535 ///
14711536 /// Raw and multi-line literals included, comments skipped. A regex over single
@@ -1775,7 +1840,7 @@ pub fn prose_inventory(policy: &serde_json::Value) -> (Vec<ProseLiteral>, ProseR
17751840             if is_test_file || skipped.contains(&number) {
17761841                 continue;
17771842             }
1778-            if !brand_hits(&body, policy).is_empty() && is_prose(&body) {
1843+            if !brand_hits(&body, policy).is_empty() && is_user_facing_text(&body, policy) {
17791844                 items.push(ProseLiteral {
17801845                     kind: "rust_string",
17811846                     file: relative.clone(),
@@ -1792,7 +1857,7 @@ pub fn prose_inventory(policy: &serde_json::Value) -> (Vec<ProseLiteral>, ProseR
17921857             let Some(body) = doc_comment_body(line) else {
17931858                 continue;
17941859             };
1795-            if brand_hits(body, policy).is_empty() || !is_prose(body) {
1860+            if brand_hits(body, policy).is_empty() || !is_user_facing_text(body, policy) {
17961861                 continue;
17971862             }
17981863             let kind = if actions.contains(&number) {
@@ -1822,7 +1887,7 @@ pub fn prose_inventory(policy: &serde_json::Value) -> (Vec<ProseLiteral>, ProseR
18221887             continue;
18231888         }
18241889         for (index, line) in source.lines().enumerate() {
1825-            if !brand_hits(line, policy).is_empty() && is_prose(line) {
1890+            if !brand_hits(line, policy).is_empty() && is_user_facing_text(line, policy) {
18261891                 items.push(ProseLiteral {
18271892                     kind: "asset",
18281893                     file: relative.clone(),
@@ -4051,6 +4116,138 @@ mod tests {
40514116         );
40524117     }
40534118 
4119+    /// OMEGA-DELTA-0044. A brand-bearing command form is user-facing text.
4120+    ///
4121+    /// `is_prose` needs three tokens, and `0.2.0-rc16` shipped three two-token
4122+    /// command literals in the signed `cli` because of it: `zed --existing`,
4123+    /// `zed --classic` and `zed <path>`, rendered by an interactive panel that
4124+    /// says "Omega window" and "Omega settings" around them (omega#93). They
4125+    /// were in neither `prose.classified` nor the compatibility allow-list, and
4126+    /// `verify-omega-brand --app` exited 0.
4127+    ///
4128+    /// Both directions matter. Widening this to "any two tokens beginning with
4129+    /// a brand word" pulls in eight more literals that really are references to
4130+    /// somebody else's product, and widening it to a bare brand token pulls in
4131+    /// 55; a rule that cries wolf gets deleted, which is how the blind spot
4132+    /// stayed open once it was known about.
4133+    #[test]
4134+    fn a_brand_bearing_command_form_is_user_facing_text() {
4135+        let policy = brand_policy().expect("brand policy parses");
4136+
4137+        for shipped in ["zed --existing", "zed --classic", "zed <path>"] {
4138+            assert!(
4139+                !is_prose(shipped),
4140+                "OMEGA-DELTA-0044: {shipped:?} is being kept out of the \
4141+                 inventory by the three-token rule, which is the blind spot \
4142+                 this exists to close"
4143+            );
4144+            assert!(
4145+                is_command_form(shipped, &policy),
4146+                "OMEGA-DELTA-0044: {shipped:?} shipped in a signed `cli` and \
4147+                 the inventory still cannot see it"
4148+            );
4149+            assert!(is_user_facing_text(shipped, &policy));
4150+        }
4151+        for form in ["zed .", "zed ~/project", "zed -n", "Zed --help"] {
4152+            assert!(
4153+                is_command_form(form, &policy),
4154+                "OMEGA-DELTA-0044: {form:?} is a command form"
4155+            );
4156+        }
4157+
4158+        // The other direction: the shapes that would make this noisy enough to
4159+        // be deleted, and are therefore excluded on purpose.
4160+        for benign in [
4161+            "Zed Pro",
4162+            "Zed (Default)",
4163+            "Zed Repository",
4164+            "zed",
4165+            "crates/zed/src",
4166+            "X-Zed-Predict-Edits-Mode",
4167+            "zed_llm_client",
4168+        ] {
4169+            assert!(
4170+                !is_command_form(benign, &policy),
4171+                "OMEGA-DELTA-0044: {benign:?} is not a command form. This rule \
4172+                 covers a brand word standing in argv[0]; a two-word label is \
4173+                 either prose already or a reference to be classified."
4174+            );
4175+        }
4176+
4177+        // The shell gate carries the same rule, on the same three streams, or
4178+        // the source and packaged sides disagree about what is inventoried.
4179+        let verifier =
4180+            std::fs::read_to_string(repository_path(BRAND_VERIFIER_PATH)).expect("brand verifier");
4181+        assert!(
4182+            verifier.contains("def is_command_form("),
4183+            "OMEGA-DELTA-0044: {BRAND_VERIFIER_PATH} does not carry the \
4184+             command-form rule, so `verify-omega-brand` and these tests \
4185+             inventory different things."
4186+        );
4187+        let wired = verifier.matches("is_user_facing_text(").count();
4188+        assert!(
4189+            wired >= 4,
4190+            "OMEGA-DELTA-0044: {BRAND_VERIFIER_PATH} calls is_user_facing_text \
4191+             {wired} time(s) — one definition and one call per stream (Rust \
4192+             literals, doc lines, embedded assets) is the minimum. A predicate \
4193+             that is defined and never called is the state \
4194+             first_party_agent.phrases was in for four release candidates."
4195+        );
4196+        assert!(
4197+            !verifier.contains("and is_prose(body)") && !verifier.contains("not is_prose(doc)"),
4198+            "OMEGA-DELTA-0044: a stream in {BRAND_VERIFIER_PATH} still filters \
4199+             on is_prose alone, so command forms are invisible to it."
4200+        );
4201+    }
4202+
4203+    /// OMEGA-DELTA-0044. The prompt spells its command forms with the binary's
4204+    /// own name.
4205+    ///
4206+    /// Fixing the three literals is not the same as making them unable to come
4207+    /// back. Any command form written out by hand can drift from the binary it
4208+    /// describes; one built from `paths::BINARY_NAME` cannot.
4209+    #[test]
4210+    fn the_cli_prompt_names_our_own_binary() {
4211+        let path = repository_path(CLI_MAIN_PATH);
4212+        let source = std::fs::read_to_string(&path)
4213+            .unwrap_or_else(|error| panic!("cannot read {}: {error}", path.display()));
4214+        let prompt = source
4215+            .split("fn prompt_open_behavior()")
4216+            .nth(1)
4217+            .expect("OMEGA-DELTA-0044: prompt_open_behavior is gone");
4218+        let prompt = &prompt[..prompt.find("\n}\n").unwrap_or(prompt.len())];
4219+        // Ordinary comments are dropped, doc comments are not: a `//` line does
4220+        // not reach a user, and the comment recording what shipped here has to
4221+        // be able to quote it. `///` and `//!` stay, because clap and schemars
4222+        // print those.
4223+        let rendered: String = prompt
4224+            .lines()
4225+            .filter(|line| {
4226+                let trimmed = line.trim_start();
4227+                !trimmed.starts_with("//") || trimmed.starts_with("///") || trimmed.starts_with("//!")
4228+            })
4229+            .collect::<Vec<_>>()
4230+            .join("\n");
4231+
4232+        let policy = brand_policy().expect("brand policy parses");
4233+        let hits = brand_hits(&rendered, &policy);
4234+        assert!(
4235+            hits.is_empty(),
4236+            "OMEGA-DELTA-0044: the open-behavior prompt in {} names {hits:?}. \
4237+             This panel already says \"Omega window\" and \"Omega settings\"; \
4238+             the commands beside them have to be ours too (omega#93).",
4239+            path.display()
4240+        );
4241+        let derived = rendered.matches("paths::BINARY_NAME").count();
4242+        assert!(
4243+            derived >= 3,
4244+            "OMEGA-DELTA-0044: the prompt builds only {derived} of its command \
4245+             forms from paths::BINARY_NAME. All three — the two choices and the \
4246+             heading — come from the binary's own name, so a rename cannot \
4247+             leave one behind."
4248+        );
4249+    }
4250+
40544251     #[test]
40554252     fn delta_ids_are_unique() {
40564253         let mut seen = std::collections::BTreeSet::new();
@@ -5810,6 +6007,67 @@ mod tests {
58106007         );
58116008     }
58126009 
6010+    /// OMEGA-DELTA-0043. `--uninstall` plans the installation, not one file.
6011+    ///
6012+    /// `0.2.0-rc16` fixed the destructive half of omega#88 and still did not
6013+    /// remove Omega: `main.rs` handed `app.path()` — `Omega.app/Contents/MacOS/
6014+    /// omega` — to a field documented as "the application bundle or executable",
6015+    /// so a completed uninstall left `/Applications/Omega.app` in place with
6016+    /// 130.9 MB, five executables including a `cli` that still carries
6017+    /// `--uninstall`, and a bundled Node runtime (omega#92).
6018+    ///
6019+    /// The behavioural proof is in `crates/cli/src/uninstall.rs`, where the
6020+    /// shipped script runs against a fabricated home whose bundle holds several
6021+    /// executables and every one of them is read back. This asserts the two
6022+    /// structural properties: the call site asks for the installation rather
6023+    /// than the executable, and the constructor normalizes what it is given so
6024+    /// a caller cannot reintroduce the defect from outside.
6025+    #[test]
6026+    fn the_uninstall_plan_names_the_installation_root() {
6027+        let plan_path = repository_path(UNINSTALL_PLAN_PATH);
6028+        let plan = std::fs::read_to_string(&plan_path)
6029+            .unwrap_or_else(|error| panic!("cannot read {}: {error}", plan_path.display()));
6030+        assert!(
6031+            plan.contains("pub fn installation_root("),
6032+            "OMEGA-DELTA-0043: {} no longer derives an installation root, so \
6033+             whatever path a caller happens to be holding is what gets removed.",
6034+            plan_path.display()
6035+        );
6036+        let constructor = plan
6037+            .split("pub fn from_installed_paths")
6038+            .nth(1)
6039+            .and_then(|tail| tail.split("\n    }").next())
6040+            .expect("OMEGA-DELTA-0043: from_installed_paths is gone");
6041+        assert!(
6042+            constructor.contains("installation_root"),
6043+            "OMEGA-DELTA-0043: from_installed_paths takes the caller's path as \
6044+             the installation root. Every macOS caller holds an executable \
6045+             inside the bundle, and removing it is not an uninstall."
6046+        );
6047+
6048+        let main_path = repository_path(CLI_MAIN_PATH);
6049+        let main = std::fs::read_to_string(&main_path)
6050+            .unwrap_or_else(|error| panic!("cannot read {}: {error}", main_path.display()));
6051+        let call = main
6052+            .split("from_installed_paths(")
6053+            .nth(1)
6054+            .expect("OMEGA-DELTA-0043: the uninstall call site is gone");
6055+        let call = &call[..call.find(')').unwrap_or(call.len())];
6056+        assert!(
6057+            call.contains("installation_root"),
6058+            "OMEGA-DELTA-0043: {} passes {call:?} to the uninstaller. \
6059+             `path()` is one executable inside `Omega.app`; \
6060+             `installation_root()` is the installation.",
6061+            main_path.display()
6062+        );
6063+        assert!(
6064+            main.contains("fn installation_root(&self) -> PathBuf;"),
6065+            "OMEGA-DELTA-0043: InstalledApp no longer distinguishes the \
6066+             executable from the installation. One method answering both \
6067+             questions is what shipped omega#92."
6068+        );
6069+    }
6070+
58136071     /// OMEGA-DELTA-0037. Omega identifies itself to third parties as Omega.
58146072     ///
58156073     /// `X-Title` is displayed to the account holder in their own OpenRouter
diff --git a/script/verify-omega-brand b/script/verify-omega-brand
index 6e95a55a34..06f4675851 100755
--- a/script/verify-omega-brand
+++ b/script/verify-omega-brand
@@ -513,6 +513,40 @@ def is_prose(text: str) -> bool:
513513     return plain >= 2
514514 
515515 
516+COMMAND_ARGUMENT = re.compile(
517+    r"^(?:--?[A-Za-z0-9][-A-Za-z0-9_.]*"  # a flag: -n, --existing
518+    r"|[<\[{][^\s>\]}]*[>\]}]"  # a placeholder: <path>, [FILE]
519+    r"|\.{1,2}|[~./][^\s]*)$"  # a path: ., .., ~/x, ./x, /x
520+)
521+
522+
523+def is_command_form(text: str) -> bool:
524+    """A command line whose program name is a competitor's binary.
525+
526+    `is_prose` needs three tokens, so `zed --existing`, `zed --classic` and
527+    `zed <path>` were invisible to it -- and all three shipped in the signed
528+    `cli` of `0.2.0-rc16`, inside a panel that says "Omega window" and "Omega
529+    settings" around them (omega#93). Substituting our own name leaves each one
530+    true, so by the rule this gate is written around they are product claims.
531+
532+    This is the narrow shape only: a brand word standing in argv[0], followed
533+    by flags, placeholders or paths. Counting every two-token literal beginning
534+    with a brand word instead adds 8 more across the tree -- `Zed Pro`, `Zed
535+    (Default)`, `Zed Repository` -- which are genuine references to somebody
536+    else's product and would need classifying; counting a bare brand token adds
537+    55. This shape adds exactly the three, which is why the gate can carry it.
538+    """
539+    tokens = text.split()
540+    if len(tokens) < 2 or tokens[0] not in WORDS:
541+        return False
542+    return all(COMMAND_ARGUMENT.fullmatch(token) for token in tokens[1:])
543+
544+
545+def is_user_facing_text(text: str) -> bool:
546+    """Prose, or a command form. What the inventory admits."""
547+    return is_prose(text) or is_command_form(normalize_prose(text))
548+
549+
516550 def rust_string_literals(text: str) -> list[tuple[int, str]]:
517551     """(start line, contents) for every Rust string literal in `text`.
518552 
@@ -686,13 +720,13 @@ def prose_inventory() -> tuple[tuple[tuple[str, str, int, str], ...], dict[str,
686720         for number, body in literals:
687721             if is_test_file or number in skipped:
688722                 continue
689-            if brand_hits(body) and is_prose(body):
723+            if brand_hits(body) and is_user_facing_text(body):
690724                 items.append(("rust_string", relative, number, normalize_prose(body)))
691725         for number, line in enumerate(lines, 1):
692726             if is_test_file or number in skipped:
693727                 continue
694728             doc = doc_line_body(line)
695-            if doc is None or not brand_hits(doc) or not is_prose(doc):
729+            if doc is None or not brand_hits(doc) or not is_user_facing_text(doc):
696730                 continue
697731             if number in actions:
698732                 kind = "action_doc"
@@ -713,7 +747,7 @@ def prose_inventory() -> tuple[tuple[tuple[str, str, int, str], ...], dict[str,
713747         if not brand_hits(text):
714748             continue
715749         for number, line in enumerate(text.splitlines(), 1):
716-            if brand_hits(line) and is_prose(line):
750+            if brand_hits(line) and is_user_facing_text(line):
717751                 items.append(("asset", relative, number, normalize_prose(line)))
718752     return tuple(items), read
Served at tenant.openagents/omega Member data and write actions are omitted.