Skip to content

Umi conventions

A living reference for how we name and shape things, so the codebase reads as one system. New conventions land here as they’re decided.

  • Rust crates (backend): umi-<area>umi-primitives, umi-crypto, umi-silo.
  • JS packages (frontend/shared): @umi/<name>@umi/email, @umi/canvas.
  • Silos: a lowercase, snake_case id (email, notes) is the silo’s stable identity. Its frontend package is @umi/<id> under packages/silos/<id>/; its manifest is silo.toml at the silo root.
  • Apps (deployable) live in top-level apps/; shared/embedded code in packages/.
  • Record ids are hyphenless (SurrealDB rule). Use snake_case, never kebab.
  • snake_case everywhere it’s a wire/string value: capability names, purposes, event tags, gate kinds, silo ids. The Rust enums serialize this way (#[serde(rename_all = "snake_case")]).

Every silo declares itself in one TOML file — the user-facing consent surface. Shape:

[silo]
id = "email" # lowercase snake_case, no hyphens; the silo's stable identity
name = "Email"
version = "0.1.0"
description = ""
[entry]
frontend = "@umi/email" # the UI package (optional if backend-only)
backend = "" # the backend entry (optional if frontend-only)
[[capabilities.required]] # must be granted to install
capability = "network" # a umi-security Capability name (snake_case)
scope = "imap:993" # host-scopes the grant; "self" = the silo's own store partition
reason = "Fetch mail over IMAP." # human rationale shown at the consent prompt
[[capabilities.optional]] # granted just-in-time, when first used
capability = "network"
scope = "smtp:587"
reason = "Send mail (only when you send)."

Rules:

  • Least privilege. Request the smallest set; scope every grant. Secondary/sensitive abilities go in optional (granted just-in-time via the ConsentGate), not required.
  • capability is a [umi-security] capability name — the manifest never invents permissions, it only requests ones the system already enforces.
  • scope narrows a grant: a network host/port, or self for the silo’s own store partition. (Capabilities are coarse today; scope is where per-silo narrowing lives until the gate enforces it directly.)
  • No remote code. Everything a silo runs is bundled; nothing is fetched-and-executed.

A silo’s backend tools are MCP tools, namespaced by silo id: email.search, email.send, notes.create. Core is the MCP host; the agent is the client.

  • umi-error is one enum that grows per emitter — a variant is added when a crate genuinely returns it, not in anticipation.
  • ProcessEvent (the reactivity bus) is snake_case-tagged and also grows per emitter.

A test must be able to fail for a real bug — if you can’t name the bug it catches, don’t write it. No tests of the language/libraries (serde roundtrips, getters, enum counts); no near-duplicates.

  • Invariants / property tests (proptest) for laws over an input space — crypto, parsers, planners. One property test beats ten hand-picked examples.
  • Integration tests at boundaries with real deps (embedded SurrealDB in-mem, tempfile) — a system’s value is in its seams.
  • Adversarial security tests are the highest value: capability bypass, silo-isolation breach, tamper, taint→sink, path traversal.
  • The suite grows with the system, one crate’s real invariants at a time. Gate slow/live tests (real-LLM, real-IMAP) behind a feature or #[ignore].