From 897f3af2ab4adf4659e7ea76254c4979817b9f78 Mon Sep 17 00:00:00 2001 From: James Yong Date: Thu, 15 Oct 2026 14:36:20 -0700 Subject: [PATCH 1/2] atlas: rosetta states its own design intent, graded by Project Atlas What the code cannot say about itself (layer and domain placement, the dependency rules, contracts, the live and search hot paths, decisions) now lives in rosetta, and Project Atlas grades every statement against the code. It is not a third living document: every rule and decision cites CLAUDE.md, docs/ or a package README with a `see "file#slug"` the check resolves. - atlas/*.atlas: system and thesis, the layer stack (exhaustive), domains below crate level, components and runtime links, the wire contract, the two flows, cross-cutting rules (iron rules carry `code IRn`), decisions with their real status (IR2 hypothesis, two proposals still proposed). - @atlas comment lines only, no code change: every crate root ends its `//!` docs with its layer and domain; the rules about one crate sit in that crate (core-is-a-leaf, pipeline-deps, thin-protocol, server-deps, search-seam, rosettad-is-root); AgentAdapter, IngestOp and SearchBackend carry their contracts; 16 flow steps sit above the function that does the step; room-dials-nothing sits in the room's index.ts header. - CLAUDE.md "Atlas" and the `atlas` skill: place a new crate or package in the same change, update selectors on a rename, never weaken a rule (an except or a wider only-list is the user's call), and the check command. Left visible on purpose, for a decision: thin-protocol (protocol re-exports search and store types), server-deps (server uses media, pricing, search), module-size (14 files over 1,000 lines), IngestOp::GitCommit has no producer, and dependency-rule fails because of the first two. Co-Authored-By: Claude Opus 5.5 --- .claude/skills/atlas/SKILL.md | 190 ++++++++++++++++++ CLAUDE.md | 27 +++ atlas/components.atlas | 80 ++++++++ atlas/contracts.atlas | 13 ++ atlas/decisions.atlas | 90 +++++++++ atlas/domains.atlas | 124 ++++++++++++ atlas/flows.atlas | 35 ++++ atlas/layers.atlas | 39 ++++ atlas/rules.atlas | 90 +++++++++ atlas/system.atlas | 21 ++ .../app/renderer/src/host/rosetta-ports.ts | 1 + .../src/components/search/SearchSheet.tsx | 1 + clients/room/src/index.ts | 5 + clients/room/src/lib/hooks.ts | 2 + clients/ts/client.ts | 1 + crates/adapter-support/src/adapter.rs | 5 + crates/adapter-support/src/lib.rs | 2 + crates/adapters/antigravity/src/lib.rs | 2 + crates/adapters/claude-code/src/lib.rs | 2 + crates/adapters/codex/src/lib.rs | 2 + crates/adapters/copilot/src/lib.rs | 2 + crates/adapters/grok/src/lib.rs | 2 + crates/adapters/hermes/src/lib.rs | 2 + crates/adapters/kimi-code/src/lib.rs | 2 + crates/adapters/opencode/src/lib.rs | 2 + crates/adapters/pi/src/lib.rs | 2 + crates/bench-corpus/src/lib.rs | 2 + crates/capture/src/ledger.rs | 1 + crates/capture/src/lib.rs | 2 + crates/capture/src/tailer.rs | 1 + crates/core/src/lib.rs | 6 + crates/core/src/ops.rs | 4 + crates/media/src/lib.rs | 2 + crates/pipeline/src/lib.rs | 6 + crates/pipeline/src/live.rs | 1 + crates/pricing/src/lib.rs | 2 + crates/protocol/src/lib.rs | 6 + crates/rosettad/src/engine.rs | 1 + crates/rosettad/src/main.rs | 6 + crates/rosettad/src/serve.rs | 1 + crates/search-bench/src/lib.rs | 2 + crates/search/src/lib.rs | 6 + crates/search/src/sidecar.rs | 1 + crates/server/src/http.rs | 1 + crates/server/src/hub.rs | 1 + crates/server/src/lib.rs | 11 + crates/server/src/ws.rs | 1 + crates/store/src/lib.rs | 3 + 48 files changed, 811 insertions(+) create mode 100644 .claude/skills/atlas/SKILL.md create mode 100644 atlas/components.atlas create mode 100644 atlas/contracts.atlas create mode 100644 atlas/decisions.atlas create mode 100644 atlas/domains.atlas create mode 100644 atlas/flows.atlas create mode 100644 atlas/layers.atlas create mode 100644 atlas/rules.atlas create mode 100644 atlas/system.atlas diff --git a/.claude/skills/atlas/SKILL.md b/.claude/skills/atlas/SKILL.md new file mode 100644 index 00000000..5c3680fa --- /dev/null +++ b/.claude/skills/atlas/SKILL.md @@ -0,0 +1,190 @@ +--- +name: atlas +description: Maintain rosetta's architecture atlas (atlas/*.atlas plus @atlas comments, graded by Project Atlas) — place a new crate, package or directory in a layer and domain, keep selectors right through renames, write rules/contracts/flow steps/decisions that cite rosetta's docs, run `atlas check`, and fix dangling or violated claims. +--- + +# Atlas workflow + +The atlas states what rosetta's code cannot say about itself, and Project Atlas grades every +statement against the code: `verified`, `violated`, `asserted` (declared, nothing checkable), +`dangling` (a selector or reference matches nothing), `stale` (≥ 10 commits touched the code since +the claim's file last changed) or `ambiguous`. Rosetta's own docs stay the authority: the atlas +cites them, never replaces them. Read CLAUDE.md "Atlas" first. The DSL spec is +`$ATLAS/docs/DSL.md` (§3 selectors, §4 statements, §5 in-code, §7 grading, §8 agent guide). + +## Where it lives + +- `atlas/system.atlas` — system, thesis, `let $agents` (the 9 agent crates). +- `atlas/layers.atlas` — the layer stack (position is rank), the TS packages, the directories. +- `atlas/domains.atlas` — domain claims below crate level (modules, files, TS paths). +- `atlas/components.atlas` — runtime units and how they talk (`via http|ws|spawn|fs|ipc|port|embed|codegen`). +- `atlas/contracts.atlas` — `wire` (four anchors in two crates). +- `atlas/flows.atlas` — `live` and `search-query`: step ids, titles, `via`/`direct`, type-anchored steps. +- `atlas/rules.atlas`, `atlas/decisions.atlas` — cross-cutting rules; decisions with their status. +- In code (`grep -rn '@atlas' crates clients`): + - every crate root: its layer and domain, and the rules whose scope is that one crate + (core-is-a-leaf, pipeline-deps, thin-protocol, server-deps, search-seam, rosettad-is-root); + - contracts at their single anchor: `AgentAdapter`, `IngestOp`, `SearchBackend`; + - flow steps at the function that does the step (`// @atlas step live.read`); + - `room-dials-nothing` in the header of `clients/room/src/index.ts`. +- `atlas/atlas.baseline.json` — written only by `atlas baseline`. + +## Running it + +Atlas lives at `$ATLAS`, default `$HOME/Projects/project100/protos/project-atlas` +(github.com/vibefield/project-atlas, private; clone it there and `npm install`). From the rosetta root: + +```sh +ATLAS="${ATLAS:-$HOME/Projects/project100/protos/project-atlas}" +npm --prefix "$ATLAS" run -s atlas -- check "$PWD" --intent "$PWD/atlas" # what is wrong +npm --prefix "$ATLAS" run -s atlas -- check "$PWD" --intent "$PWD/atlas" --all # also asserted + verified +npm --prefix "$ATLAS" run -s atlas -- explain "$PWD" --intent "$PWD/atlas" crates/pipeline/src/skim.rs +npm --prefix "$ATLAS" run -s atlas -- explain "$PWD" --intent "$PWD/atlas" 'mod:rosetta_capture::{ledger, segment}' +npx --prefix "$ATLAS" tsx "$ATLAS/packages/engine-intent/src/cli.ts" atlas crates clients # syntax only, 2 s +``` + +- Pass `--intent "$PWD/atlas"`: it pins the DSL root and the baseline + (`atlas/atlas.baseline.json`, beside `system.atlas`) whatever Atlas version you run. + Relative paths resolve against the directory you run from. +- Rust facts come from `cargo doc`, which compiles and runs rosetta's build scripts with its + target dir in Atlas's own cache, not `target/`: about 20 s cold, 2–5 s when nothing changed. + Uncommitted files count. +- The last command parses `atlas/` and every `@atlas` comment without building anything: syntax + and duplicate ids only, no selector or reference checks. +- `baseline` (`npm --prefix "$ATLAS" run -s atlas -- baseline "$PWD" --intent "$PWD/atlas"`) turns + today's violations into `known` ones. Run it only when the user decides to accept them, never to + hide a violation your change caused. + +## Reading `check` + +``` +atlas check · Rosetta @ 4348545 · 381 claims + 339 verified · 37 asserted · 5 violated (5 new, 0 known) · 0 dangling · 0 ambiguous · 0 stale + coverage: layers 100.0% · domains 100.0% of 120,191 prod lines · rules 17 pass, 3 fail, 2 unchecked + +VIOLATED · new (5) + rule:server-deps only this -> crate:rosetta-{core, protocol, store} + at crates/server/src/lib.rs:10 · rule · basis edge + pkg:cargo/rosetta-server → pkg:cargo/rosetta-media + crates/server/src/http/images.rs:11 use rosetta_media::{MAX_INPUT_BYTES, PreparedImage}; + → this depends on 3 units outside its allow-list: rosetta-media, rosetta-pricing, rosetta-search (first at crates/server/src/http.rs:32). Add them to the only-list if intended, route the call through a… +``` + +- Sections in order: VIOLATED · new, VIOLATED · known (baselined), DANGLING, AMBIGUOUS, STALE. + ASSERTED and VERIFIED are only counted unless you pass `--all`. +- Each claim: `subject DSL text [severity]`, then `at file:line · kind · basis`, then the + evidence (`file:line source`), then `→` the remedy. Every glob prints its match count + (`crate:rosetta-adapter-* → 10`): read it; a count of 0 is the bug. +- Exit 1 only for a NEW violation of severity `error`. DANGLING, STALE, AMBIGUOUS and `warn` + rules (module-size) do not fail the check, but you still leave 0 dangling and 0 ambiguous. +- Both coverages are 100%: a drop means new code sits in no layer (also an `exhaustive` + violation) or in no domain (unclaimed). Place it. +- Findings left visible on purpose for the user to decide: `thin-protocol` (protocol re-exports + search/store types), `server-deps` (server uses media, pricing, search), `module-size` (14 + files, 2 of them TS), `ingest-op` (`IngestOp::GitCommit` has no producer), and + `dependency-rule` (fails because of the first two). Do not edit a rule or decision to make + one of them pass. + +## Fixing + +- **Dangling selector.** `explain` the selector: each term prints `→ n` and + `DANGLING: matches nothing` for the bad one. Find where the code went (`git log --follow`, + grep) and fix the selector in the same change as the rename. A crate's name also sits in other + crates' `@atlas` lines, often in a brace form (`crate:rosetta-{core, protocol, store}`) that a + grep for the full name misses: grep the stem. If the code is gone for good, delete the claim + with it. +- **Dangling `see`.** The message suggests the nearest heading (`Did you mean #…?`). A heading + rename in the docs dangles every `see` pointing at it: fix them together. +- **"has no code subject".** An `@atlas` comment sits below the last declaration or in the wrong + place; move it directly above its item. +- **Dependency rule violated.** The evidence names the package edge and its sites. Fix the code + (route through an allowed crate). The remedy line also offers "Add them to the only-list if + intended" and an `except …` to paste: an agent never takes either on its own. Widening an + allow-list or adding an exception changes the design, so the rule and the document it cites + (ARCHITECTURE.md §8) change together, and that is the user's call; propose it instead. A + sanctioned exception reads `except "real reason"` (see `store-sealed`, + `async-at-edge`, `search-seam`); an `except` that excuses nothing goes stale: delete it. +- **Contract violated.** `implemented-by` / `used-by` / `produced-by … each-variant` list the + missing package or enum variant: implement or produce it, or delete the dead variant. +- **Step asserted: "The step has no `at` anchor".** You deleted or renamed a function that + carried `// @atlas step flow.step`. Put the line above the function that now does the work. + A misspelt `@atlas step` id produces no claim and no error at all, so after touching a flow + run `check --all` and confirm every step of `flow:live` and `flow:search-query` is verified. +- **Stale.** At least 10 commits touched the claim's code since its file (the `.atlas` file, or + the source file holding the `@atlas` line) last changed. Re-read the code; fix the statement + if it no longer holds. Staleness is per file in v1, so it clears when that file is next committed. +- **Planned element landed.** `expects … [warn]` resolving means the thing was built: drop + `status planned`/`proposed` and `expects`, then bind it. + +## Authoring + +1. **Crate facts go in the crate root**, at the end of the existing `//!` block, after one blank + `//!` line. Only an inner doc comment in `lib.rs`/`main.rs` binds the whole package, bin + targets included; there `this` is the package. + + ```rust + //! …the crate's existing docs, unchanged… + //! + //! @atlas layer orchestration · domain ingest + //! @atlas rule pipeline-deps "pipeline → {core, capture, store, adapters}" { + //! only this -> crate:rosetta-{core, capture, store}, crate:rosetta-adapter-* + //! see "docs/ARCHITECTURE.md#8-workspace-layout" + //! } + ``` + + These lines render into rustdoc, and CI runs `clippy -D warnings` and doctests. So: never + touch the first doc paragraph; indent continuation lines 2 spaces, never 4+ (an indented + code block becomes a doctest); no `[…]` (intra-doc link), no ``, `-`, `*` or `1.`; when the docs end in a code fence, + insert after the closing fence. +2. **Everything else in Rust uses `//`**, never `///`: put it directly above the item, above its + `///` docs and attributes, at the item's indentation. `///` would render into rustdoc, and + ts-rs copies item docs into `clients/ts/generated/*.ts`. + + ```rust + // @atlas contract ingest-op "IngestOp: where agent weirdness ends" { + // produced-by $agents, crate:rosetta-pipeline each-variant + // used-by crate:rosetta-store + // } + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + pub enum IngestOp { + ``` +3. **TypeScript uses `//`** (a `*/` in a glob would close a block comment). +4. **Header pitfall.** A `//` annotation before a file's first code token binds the file (Rust: + its module; TS: the file, `index.*` its directory), not the declaration below it. Symbol + annotations go after the imports. The parse command with `--json` shows each annotation's + `host.style` (`inner-doc` for crate roots) and `host.fileHeader`; `explain ` shows + what it bound to (`crates/core/src/lib.rs:6 · root pkg:cargo/rosetta-core`). +5. **Standalone files** hold what spans crates: the layer structure, domain claims below crate + level, components and relations, cross-cutting rules, decisions. Declaring an element in two + places is a parse error. +6. **Cite rosetta.** Every rule and decision carries `see "#"`; `code IRn` only when + CLAUDE.md iron rule n is the source. A slug is the heading lowercased, with every character + that is not a letter, digit, whitespace or `-` dropped, then each whitespace character turned + into `-` (no collapsing); repeated headings get `-1`, `-2`; headings inside code fences don't + count. So `## 4. Layer 2 — Normalizer` → `4-layer-2--normalizer`, and `### 5.1 Transport + decision: one daemon, localhost HTTP/WS` → `51-transport-decision-one-daemon-localhost-httpws`. + A non-Markdown file (`docs/ingest-engine.html`) takes no `#slug`. +7. **Style** (DSL §8): ids are slugs; the coarsest true selector (crate > mod > path > sym); one + statement per line; prose only for what code cannot show, at most two sentences; never restate + a doc comment; do not annotate every module. + +## Worked examples + +- **New agent adapter** `crates/adapters/foo` (package `rosetta-adapter-foo`): end its `lib.rs` + docs with `//! @atlas layer agents · domain normalize`. `$agents` picks it up, so + `adapters-isolated`, `agents-sealed` and the `adapter` contract now cover it (the contract + fails until it implements `AgentAdapter`). Its `usage*.rs`, `images.rs` and `spec.rs` are + claimed by the path globs in `atlas/domains.atlas`. Only `rosettad` may name it. +- **New L2 crate.** Rank matters: `data-flows-up` (`layered`) forbids any edge going up the + stack, so pick the runtime sub-tier that sits above everything the crate depends on, and add + its allowed dependencies to the doc's §8 dependency rule if it has one. +- **New npm package or top-level directory.** No comment can bind a whole npm package: + add `contains pkg:…` or `contains path:dir/` to a layer in `atlas/layers.atlas` and a domain + in `atlas/domains.atlas`. +- **Splitting a big file** (module-size): `// @atlas step` lines move with their functions; + `mod:`/`file:`/`sym:` selectors that named the old path, in `atlas/` or in other code's + `@atlas` lines, dangle. Fix them in the + same change and check `--all` that the flow's steps are still verified. +- **Monitor lands.** When `mod:rosetta_server::metrics` exists, `domain monitor` reports its + `expects` as landed: drop `status planned` and `expects`, keep its `touches`, add `contains`. diff --git a/CLAUDE.md b/CLAUDE.md index 03704f7b..2b9e73f7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -219,6 +219,7 @@ tools/groundtruth/ LAYER 1 dev tools (stdlib Python): fixture farm, mock model, census, schema merge, HTML render, boundary/artifact checks tools/ingest-bench/ local Electron cold-ingest profiler + read-only DB inspector +atlas/ graded design intent (*.atlas); see "Atlas" below crates/ all Rust crates adapter-support/ permissive parser + replay-test primitives adapters// raw parsers using canonical ground truth as spec @@ -266,6 +267,32 @@ clients/ TypeScript consumers [Phase 4] - `groundtruth/` is data-only. Rust belongs under `crates/`; repository tooling belongs under `tools/`. +## Atlas + +`atlas/*.atlas` plus `@atlas` comment lines (every crate root, a few trait/enum/fn seams) +declare what code cannot say about itself: layer and domain placement, contracts, the two +hot-path flows, dependency rules, decisions. Project Atlas grades each statement against the +code. It is not a third living document: every rule and decision cites this file, `docs/` or +a package README with a checked `see "file#slug"`. Authoring guide: the `atlas` skill. + +- A new crate, npm package or top-level directory is placed in a layer and a domain in the + same change: a crate ends its root `//!` doc with a blank `//!` line, then + `//! @atlas layer x · domain y`; anything else goes in `atlas/layers.atlas` and + `atlas/domains.atlas`. +- Renaming or moving code updates every selector that names it, in `atlas/` and in other + code's `@atlas` lines (brace forms like `crate:rosetta-{core, store}` hide from grep), and + `check` then shows 0 DANGLING. An `@atlas` line travels with the code it sits on. +- Never weaken a rule to make it pass: fix the code. Widening an `only` list, adding an + `except`, or changing what a rule or decision means is the user's call, even when `check` + suggests it. `atlas/atlas.baseline.json` is written only by `atlas baseline`, never by hand. +- Check after changing crates, packages, modules, dependencies or `atlas/`, with + `ATLAS=${ATLAS:-$HOME/Projects/project100/protos/project-atlas}`: + `npm --prefix "$ATLAS" run -s atlas -- check "$PWD" --intent "$PWD/atlas"`. Exit 1 = a new + error violation; DANGLING does not fail it but must be fixed. It runs `cargo doc`, a build; + when builds are off-limits, + `npx --prefix "$ATLAS" tsx "$ATLAS/packages/engine-intent/src/cli.ts" atlas crates clients` + checks syntax only. + ## Commands - `cargo check` / `cargo test --workspace` / `cargo fmt --all` / diff --git a/atlas/components.atlas b/atlas/components.atlas new file mode 100644 index 00000000..9e1a0b14 --- /dev/null +++ b/atlas/components.atlas @@ -0,0 +1,80 @@ +// Runtime units and how they talk. A link with `via` is a runtime claim: asserted, its `at` anchors must +// resolve and each `witness` literal must occur in them. A link without `via` is graded against code edges. + +component daemon "rosettad serve" kind daemon { + intent "One process: the sole synchronous canonical writer thread, a tokio/axum edge, the search worker." + contains crate:rosettad + see "docs/ARCHITECTURE.md#51-transport-decision-one-daemon-localhost-httpws" +} +component protocol-rs "rosetta-protocol (Rust wire types)" kind library { contains crate:rosetta-protocol } +component sdk "@vibefield/rosetta-protocol" kind library { contains pkg:@vibefield/rosetta-protocol } +component room-ui "@vibefield/rosetta-room" kind ui-library { + contains pkg:@vibefield/rosetta-room + see "clients/room/README.md#vibefieldrosetta-room" +} +component app "Agent History (Electron)" kind desktop-app { + contains pkg:@vibefield/rosetta-agent-history + component app-main "Electron main" kind process { + contains file:clients/app/electron/main.mjs, file:clients/app/electron/daemon-service.mjs + } + component preload "Preload bridge" kind bridge { contains file:clients/app/electron/preload.cjs } + component renderer "Renderer (React 19)" kind ui-process { contains path:clients/app/renderer/ } + component fx-host "Worktree host (mille + git)" kind utility-process { + contains file:clients/app/electron/fx-host.mjs, file:clients/app/electron/git-service.mjs + } +} +component gt-tools "Ground-truth pipeline" kind dev-cli { contains path:tools/groundtruth/ } +component ingest-bench "Cold-ingest profiler + DB inspector" kind dev-tool { contains path:tools/ingest-bench/ } +component agent-clis "Agent CLIs (~/.claude, ~/.codex, …)" external +component supervisor "Supervisors (VibeField fieldd, chopsticks)" external + +agent-clis -> daemon "JSONL appends, rewrites, SQLite DBs" via fs { + at mod:rosetta_capture::watch + witness "recommended_watcher" +} +app-main -> daemon "serve --json --announce; version-checked; SIGINT, then SIGKILL" via spawn { + at file:clients/app/electron/daemon-service.mjs + witness "--announce" + witness "PROTOCOL_VERSION" +} +sdk -> daemon "REST /api/*; the version header is checked first" via http { + at sym:clients/ts/client.ts#RosettaClient, mod:rosetta_server::http + witness "/api/search" + witness "x-rosetta-protocol-version" +} +sdk -> daemon "WS /ws/sessions, /ws/meta, /ws/collaboration" via ws { + at sym:clients/ts/client.ts#RosettaClient, mod:rosetta_server::http + witness "/ws/meta" +} +renderer -> app-main "contextBridge: daemon state, git facts, previews" via ipc { + at file:clients/app/electron/preload.cjs, file:clients/app/renderer/src/host/desktop-bridge.ts + witness "agentHistoryHost" +} +renderer -> fx-host "transferred MessagePorts: worktree tree + git streams" via port { + at file:clients/app/renderer/src/host/worktree-ports.ts + witness "ah-worktree-ports" +} +daemon -> supervisor "announce file: origin, pid, protocol version" via fs { + at mod:rosettad::announce + witness "rosetta-daemon/1" +} +supervisor -> daemon "--enroll file: authoritative consent" via fs { + at mod:rosettad::enroll + witness "enabled_kinds" +} +sdk -> protocol-rs "clients/ts/generated/ comes from ts-rs; never hand-written" via codegen { + at file:crates/protocol/src/lib.rs + witness "ts_rs::TS" +} +gt-tools -> daemon "groundtruth.json compiled into every adapter" via embed { + at path:crates/adapters/*/src/spec.rs + witness "groundtruth.json" +} +ingest-bench -> daemon "reads the projection SQLite directly (sanctioned escape hatch)" via fs { + at file:tools/ingest-bench/src/lib/database.js + witness "node:sqlite" +} + +renderer -> room-ui "hosts HistoryRoom and hands it every port" +renderer -> sdk "the reference host builds the room's ports over RosettaClient" +room-ui -> sdk "wire types only; the room dials nothing" diff --git a/atlas/contracts.atlas b/atlas/contracts.atlas new file mode 100644 index 00000000..eaa72b23 --- /dev/null +++ b/atlas/contracts.atlas @@ -0,0 +1,13 @@ +// The wire seam has four anchors in two crates, so it is declared here. Contracts with one anchor are declared +// in-code at their trait or enum: adapter (AgentAdapter), ingest-op (IngestOp), search-backend (SearchBackend). + +contract wire "Wire protocol (REST + WS frames)" { + intent "Protocol re-exports core/store/search types instead of DTO mirrors, so the wire shape follows L2." + at sym:rosetta_protocol::ServerFrame, sym:rosetta_protocol::ErrorCode + at sym:rosetta_store::hot::{Mutation, Stamp} + mirror "clients/ts/generated/{name}.ts#{name}" + used-by crate:rosetta-server + note file:clients/ts/protocol-version.mjs "Bumped together with PROTOCOL_VERSION; the wire golden test checks that both agree." + see "docs/ARCHITECTURE.md#52-the-three-services" + see "clients/ts/README.md#vibefieldrosetta-protocol" +} diff --git a/atlas/decisions.atlas b/atlas/decisions.atlas new file mode 100644 index 00000000..8b85b81c --- /dev/null +++ b/atlas/decisions.atlas @@ -0,0 +1,90 @@ +// Decisions with their real status. Verified only when accepted and every confirmed-by rule, contract or +// flow is verified; proposed and hypothesis decisions are asserted. + +decision dependency-rule "Crate dependency rule: enforced, not aspirational" status accepted { + because "spaghetti's layers leaked into each other; ARCHITECTURE.md §8 states the rule for adapters, pipeline, server, core, protocol and rosettad." + affects path:crates/ + confirmed-by data-flows-up, rosettad-is-root, core-is-a-leaf, + thin-protocol, server-deps, pipeline-deps, adapters-isolated + see "docs/ARCHITECTURE.md#8-workspace-layout" +} + +decision model-hypothesis "Canonical model is a hypothesis until Phase 2" code IR2 status hypothesis { + because "spaghetti overfit its model to one agent (Claude Code)." + affects mod:rosetta_core::{ops, timeline, rows} + see "docs/ARCHITECTURE.md#41-one-canonical-model-consumer-driven" +} + +decision machine-independent-truth "Machine-independent ground truth" code IR3 status accepted { + because "CI enforcement is fixture-only; local agent data may corroborate but is never load-bearing." + affects path:groundtruth/, path:tools/groundtruth/ + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" + see "docs/ARCHITECTURE.md#3-layer-1--ground-truth-evidence" +} + +decision two-representations "Two representations: raw ledger + one projection" code IR4 status accepted { + because "spaghetti kept three coexisting models in a 101-table schema." + affects crate:rosetta-capture, crate:rosetta-store + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" + see "docs/ARCHITECTURE.md#6-storage" +} + +decision persist-what-users-care-about "Persist only what a user cares about" code IR5 status accepted { + because "Normalization is deliberately lossy; reconciliation machinery may live in RAM, never in canonical SQLite." + affects crate:rosetta-store, crate:rosetta-core, crate:rosetta-pipeline + confirmed-by resolver-mints-project-id + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" +} + +decision parsers-once "Parsers exist once, in Rust; TS gets generated types" code IR6 status accepted { + because "spaghetti kept ~20k LOC of duplicate TS parsers." + affects crate:rosetta-protocol, path:clients/ts/generated/ + confirmed-by wire, room-dials-nothing + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" +} + +decision one-groundtruth-file "One ground-truth file per agent" code IR7 status accepted { + because "Intermediates contribute but are never authoritative, so schema truth is read from one place." + affects path:groundtruth/, path:tools/groundtruth/ + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" +} + +decision ledger-first "Ledger-first crash protocol" status accepted { + because "An orphan ledger tail is harmless; a projection row that points at unflushed ledger is forbidden." + affects mod:rosetta_pipeline::stream, mod:rosetta_capture::ledger + confirmed-by live + see "docs/ingest-engine.html" +} + +decision single-writer "Single synchronous writer; async only at the L3 edge" status accepted { + because "Debuggability: plain threads and bounded channels keep flamegraphs readable." + affects crate:rosettad, crate:rosetta-server + confirmed-by async-at-edge + see "docs/ARCHITECTURE.md#51-transport-decision-one-daemon-localhost-httpws" + see "docs/ingest-engine.html" +} + +decision fts5-sidecar "Detached FTS5 sidecar behind a backend-neutral seam" status accepted { + because "Search loss never blocks canonical reads; Tantivy has not cleared relevance or storage acceptance." + affects crate:rosetta-search + confirmed-by search-backend, search-seam, tantivy-benchmark-only + see "docs/ARCHITECTURE.md#6-storage" + see "docs/ARCHITECTURE.md#11-open-decisions-defaults-chosen-cheap-to-revisit" +} + +decision access-control "Open listener by default; bearer and per-session grants on demand" status accepted date 2026-09-30 { + affects mod:rosetta_server::{auth, grant}, mod:rosettad::auth_files + see "docs/ARCHITECTURE.md#11-open-decisions-defaults-chosen-cheap-to-revisit" + see "docs/AUTH.md#2-two-guards-never-one-for-the-other" +} + +decision ingestion-api "Ingestion lifecycle API" status proposed date 2026-10-05 { + expects mod:rosettad::ingestion? + see "docs/INGESTION_API_PROPOSAL.md#decision" +} + +decision daemon-lifecycle "Daemon ownership, control, and recovery" status proposed date 2026-10-06 { + expects mod:rosettad::lifecycle? + affects crate:rosettad, file:clients/app/electron/daemon-service.mjs + see "docs/DAEMON_LIFECYCLE_PROPOSAL.md#1-decisions" +} diff --git a/atlas/domains.atlas b/atlas/domains.atlas new file mode 100644 index 00000000..12411749 --- /dev/null +++ b/atlas/domains.atlas @@ -0,0 +1,124 @@ +// Domains cut across layers and languages. Each crate root names its crate's domain in-code +// (`//! @atlas layer … · domain …`); the claims below are the modules, files and TS paths that serve another +// domain. The most specific `contains` wins; `touches` adds a secondary membership and never competes. + +domain capture "Discovery, consent & capture" { + contains mod:rosetta_pipeline::{discover, skim, snapshot, document} + contains mod:rosetta_server::{consent, enrolled}, mod:rosettad::enroll + contains file:clients/app/renderer/src/components/RollCall.tsx + touches mod:rosetta_adapter_support::glob + see "docs/ARCHITECTURE.md#35-runtime-handoff-watcher-tailer-and-raw-ledger" + see "docs/ARCHITECTURE.md#supervisor-enrolled-runtimes-2026-09-12" +} + +domain ledger "Raw ledger (durable truth)" { + contains mod:rosetta_capture::{ledger, segment, segment_index, recompress} + contains mod:rosetta_pipeline::{replay, command} + contains mod:rosettad::{runtime_ledger, ledger_cmd, ledger_verify, ledger_compression, cold_stage} + see "docs/ARCHITECTURE.md#35-runtime-handoff-watcher-tailer-and-raw-ledger" +} + +domain normalize "Per-agent normalization" { + intent "Where agent weirdness ends: native records in, canonical IngestOps out." + see "docs/ARCHITECTURE.md#4-layer-2--normalizer" +} + +domain ingest "Cold & live ingestion" { + contains mod:rosetta_store::{apply, cold_stream, bulk, reclaim} + contains mod:rosetta_server::{health, board} + contains file:clients/room/src/lib/{ingest, ingest-activity, use-ingest-activity, catalog-status}.ts + contains file:clients/room/src/components/{IngestStatus, ColdStart}.tsx + see "CLAUDE.md#current-phase" + see "docs/ingest-engine.html" +} + +domain projection "Canonical projection" { + intent "One disposable SQLite projection plus content-addressed members; a cache of the ledger." + see "docs/ARCHITECTURE.md#6-storage" +} + +domain search "Full-text search (job 1)" { + contains mod:rosetta_store::search_sync, sym:rosetta_server::http::search + contains path:clients/room/src/components/search/, path:clients/room/src/lib/search-*.ts + touches sym:clients/ts/client.ts#RosettaClient.search + see "docs/ARCHITECTURE.md#52-the-three-services" +} + +domain observe "Live observation (job 2)" { + intent "The History room is this job's client: every catalog list, transcript and live patch." + contains pkg:@vibefield/rosetta-room + contains mod:rosetta_store::hot, mod:rosetta_server::{hub, ws}, mod:rosetta_server::http::activity + contains sym:clients/ts/client.ts#{SessionSubscription, MetaSubscription, CollaborationSubscription} + contains file:clients/ts/ordered-cache.ts + see "docs/ARCHITECTURE.md#52-the-three-services" +} + +domain usage "Usage & spend" { + contains path:tools/pricing/ + contains mod:rosetta_core::{fold, usage_totals}, mod:rosetta_store::apply::usage_owners + contains mod:rosetta_store::{pricing, usage_sql, usage_totals} + contains path:crates/adapters/*/src/usage*.rs, path:crates/adapters/copilot/src/usage/ + contains path:clients/room/src/components/charts/ + contains path:clients/room/src/lib/{spend, chart-data, day-activity, model-labels, use-day-usage}.ts + contains file:clients/room/src/components/{SummaryPanels, SummaryMetrics}.tsx + see "docs/ARCHITECTURE.md#413-usage-accounting-and-list-price-usd-2026-09-22" +} + +domain media "Images & assets" { + contains mod:rosetta_adapter_support::media, mod:rosetta_core::assets + contains mod:rosetta_store::assets, mod:rosetta_server::http::images + contains path:crates/adapters/*/src/images.rs, file:crates/adapters/claude-code/src/media.rs + contains path:clients/room/src/components/file-viewer/, file:clients/room/src/lib/image-media.ts + see "docs/ARCHITECTURE.md#412-authored-images-2026-09-09" +} + +domain auth "Access control" { + contains mod:rosetta_server::{auth, grant}, mod:rosettad::auth_files + contains file:clients/ts/grants.ts, file:clients/ts/base64url.ts, path:tools/auth/ + see "docs/AUTH.md#daemon-access-control" +} + +domain api "Daemon API (REST + WS + SDK)" { + intent "The wire surface every consumer dials; the job domains claim their own routes and frames." + contains pkg:@vibefield/rosetta-protocol + touches path:docs/site/ + see "docs/ARCHITECTURE.md#5-layer-3--service-provider" + see "clients/ts/README.md#package-shape" +} + +domain host "Room hosting & daemon supervision" { + intent "How a host gives the room its ports and owns one daemon." + contains pkg:@vibefield/rosetta-agent-history + contains file:clients/room/src/{ports, host-inputs}.ts, file:clients/room/src/room-context.tsx + contains file:clients/room/src/components/{HostMenu, HostAct}.tsx, file:clients/room/src/lib/session-leases.ts + contains mod:rosettad::announce + see "clients/room/README.md#the-ports-srcportsts" + see "docs/ARCHITECTURE.md#51-transport-decision-one-daemon-localhost-httpws" +} + +domain worktree "Worktree & git column" { + contains path:clients/room/src/components/{worktree, git}/ + contains file:clients/app/electron/{git-service, fx-host, worktree-file-actions, worktree-explorer-config}.mjs + contains file:clients/app/renderer/src/host/worktree-ports.ts + see "clients/room/README.md#the-ports-srcportsts" +} + +domain perf "Performance evidence" { + intent "Frozen before/after pairs authorize optimizations." + contains mod:rosettad::{bench, brutal}, mod:rosetta_pipeline::instrument, path:tools/ingest-bench/ + see "docs/ARCHITECTURE.md#7-performance-playbook" + see "CLAUDE.md#conventions" +} + +domain groundtruth "Ground-truth evidence (L1)" { + contains path:groundtruth/, path:tools/groundtruth/ + contains path:crates/adapters/*/src/spec.rs + see "docs/ARCHITECTURE.md#3-layer-1--ground-truth-evidence" +} + +domain monitor "Metric feeds (job 3)" status planned { + intent "Distilled live metrics for runtime monitors such as chopsticks; WS /v1/metrics is documented but not routed." + expects mod:rosetta_server::metrics? + touches mod:rosetta_server::{health, board}, mod:rosetta_server::http::activity + see "docs/ARCHITECTURE.md#52-the-three-services" +} diff --git a/atlas/flows.atlas b/atlas/flows.atlas new file mode 100644 index 00000000..5a6bef4e --- /dev/null +++ b/atlas/flows.atlas @@ -0,0 +1,35 @@ +// Two hot paths. A step done by a function is bound in-code (`// @atlas step .` above it; list them +// with `grep -rn '@atlas step' crates clients`); a step anchored on a type keeps its `at` here. A step with no +// anchor at all grades `asserted`, not dangling: after deleting or renaming a step's function, re-bind it. +// A hop is verified by an edge between its steps, one shared unit, or a `by` orchestrator reaching both; +// `via` marks a runtime hop (asserted). + +flow live "Live file change → client update" { + intent "The observe hot path; the budget is watch event → subscriber frame p99 < 20 ms." + by mod:rosettad::serve, mod:rosetta_pipeline::stream + step wake "notify event wakes the shared SourceWake" at sym:rosetta_capture::watch::NotifyEvents + step tick "writer thread ticks each live runner" + step map "runner maps the hint to changed sources" direct + step read "tailer reads complete records only" + step parse "adapter turns each record into IngestOps" + step ledger "ledger first: append, then durable flush" + step apply "one SQLite transaction: checkpoint CAS + rows" + step absorb "drained effects reach the hub" + step hub "hub fans mutations into per-session tail rings" direct + step frame "mutation frames on /ws/sessions/{id}" direct + step patch "SDK patches its cache, 16 ms coalescing" at sym:clients/ts/client.ts#SessionSubscription via ws + step render "room hooks re-render the transcript" via port + see "docs/ARCHITECTURE.md#71-cross-adapter-applicability-checkpoint-2026-08-27" +} + +flow search-query "Search query" { + intent "Job 1. A sidecar generation behind the canonical watermark answers `catching-up` from a consistent snapshot." + step sheet "search sheet collects query and facets" + step feed "useSearch asks the injected CatalogPort" + step port "the host's catalog port wraps RosettaClient" via port + step client "GET /api/search?q=…" + step handler "parse grammar, expand session scope" via http + step backend "spawn_blocking → SearchBackend::search" at sym:rosetta_search::SearchBackend + step sidecar "FTS5 MATCH on the generation CURRENT names" + see "docs/ARCHITECTURE.md#52-the-three-services" +} diff --git a/atlas/layers.atlas b/atlas/layers.atlas new file mode 100644 index 00000000..6a954e81 --- /dev/null +++ b/atlas/layers.atlas @@ -0,0 +1,39 @@ +// Position is rank, top to bottom; nested layers are ordered sub-tiers. Every Rust crate places itself in its +// crate root (`//! @atlas layer …`), so the crate leaves below are empty here. This file holds the structure and +// what is not a crate: the TS packages and the directories. `exhaustive`: an unplaced production file is a violation. + +layers stack "Data flows up; dependencies point down" exhaustive { + see "docs/ARCHITECTURE.md#2-the-three-layers" + layer consumers "Consumers" { + intent "Consumers never reach past L3 into the store." + layer host-app "Desktop host" { contains pkg:@vibefield/rosetta-agent-history } + layer room-lib "History room package" { contains pkg:@vibefield/rosetta-room } + layer client-sdk "TS client SDK" { contains pkg:@vibefield/rosetta-protocol } + } + layer services "Services" code L3 { + layer edge "Runtime edge (axum REST + WS)" + layer wire-types "Wire contract" + layer query "Search seam" + note crate:rosetta-search "ARCHITECTURE.md §8 calls it the L3 search boundary; the §2 diagram omits it." + } + layer runtime "Runtime" code L2 { + layer orchestration "Ingest orchestration" + layer agents "Agent adapters" independent + layer adapter-kit "Adapter kit" + layer storage "Projection store" + layer capture-io "Capture & raw ledger" + layer kernels "Pure kernels" + layer model "Canonical model" + note crate:rosetta-media, crate:rosetta-pricing "In no layer diagram of the docs; pure libraries used by both L2 and L3." + } + layer truth "Ground truth" code L1 { + contains path:groundtruth/, path:tools/groundtruth/ + intent "Dev-time evidence; runs no production code. Adapters embed groundtruth.json, so no code edge reaches it." + } + layer root "Composition root" outside { + intent "CLI and daemon in one binary; wires every crate and nothing depends on it." + } + layer tooling "Evidence & tooling" outside { + contains path:tools/, path:docs/site/ + } +} diff --git a/atlas/rules.atlas b/atlas/rules.atlas new file mode 100644 index 00000000..19267bbb --- /dev/null +++ b/atlas/rules.atlas @@ -0,0 +1,90 @@ +// Cross-cutting rules. A rule about one crate or package lives in its root (`grep -rn '@atlas rule' crates clients`). +// Every rule cites where rosetta states it; `code IRn` is CLAUDE.md iron rule n. Checks see production code only +// (no tests, benches, cfg(test) code or dev-dependencies). Never weaken a rule to pass: fix the code. An +// `except "real reason"` or a wider `only` list changes the design: that is the user's call. + +rule data-flows-up "Data flows up only; dependencies point down" code IR1 { + layered + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" + see "docs/ARCHITECTURE.md#2-the-three-layers" +} + +rule adapters-isolated "All knowledge of one agent lives in its adapter pair" code IR1 { + independent $agents + only $agents -> crate:rosetta-core, crate:rosetta-adapter-support + see "docs/ARCHITECTURE.md#31-one-data-pack-and-one-parser-crate-per-agent" + see "docs/ARCHITECTURE.md#8-workspace-layout" +} + +rule agents-sealed "Only the composition root names a concrete adapter" code IR1 { + deny *, !crate:rosettad -> $agents + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" +} + +rule resolver-mints-project-id "The shared meta resolver, never an adapter, mints the canonical project id" code IR5 { + deny $agents, crate:rosetta-adapter-support -> sym:rosetta_core::ids::project_id + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" +} + +rule l3-parses-nothing "L3 never parses agent files" { + deny layer:services -> layer:agents, layer:adapter-kit, layer:capture-io + see "docs/ARCHITECTURE.md#2-the-three-layers" +} + +rule store-sealed "Consumers never reach past L3 into the store" code IR1 { + forbid text "node:sqlite", "better-sqlite3" in path:clients/, path:tools/ + except file:tools/ingest-bench/src/lib/database.js "ARCHITECTURE.md §5.1: the read-only escape hatch onto an unstable internal format" + see "docs/ARCHITECTURE.md#51-transport-decision-one-daemon-localhost-httpws" +} + +rule tooling-stays-out "Production crates never import development tooling" { + deny *, !layer:tooling -> layer:tooling + see "docs/ARCHITECTURE.md#8-workspace-layout" +} + +rule pure-normalizers "normalize() is pure: no IO, no clock, no randomness" { + forbid text "std::fs", "File::open", "std::env", "std::net", "std::process", + "SystemTime", "Instant::now", "thread::spawn", "rand::" in $agents, + crate:rosetta-{core, adapter-support, media, pricing} + see "CLAUDE.md#conventions" + see "docs/ARCHITECTURE.md#42-normalization-is-a-pure-versioned-function" +} + +rule async-at-edge "Async lives only at the L3 edge" { + forbid text "async fn", "tokio::" in path:crates/, !crate:rosetta-server + deny path:crates/, !crate:rosetta-server -> ext:tokio, ext:axum, ext:futures-util, ext:tower, ext:tower-http + except file:crates/rosettad/src/serve.rs "composition root builds the tokio runtime that hosts the L3 server" + see "docs/ingest-engine.html" +} + +rule tantivy-benchmark-only "Tantivy stays a benchmark-only prototype" { + deny *, !crate:rosetta-search-bench -> ext:tantivy + see "docs/ARCHITECTURE.md#11-open-decisions-defaults-chosen-cheap-to-revisit" +} + +rule grants-node-only "Grants sign with node:crypto; no browser bundle imports them" { + deny pkg:@vibefield/rosetta-room, path:clients/app/renderer/, + file:clients/ts/index.ts, file:clients/ts/client.ts -> file:clients/ts/grants.ts + see "clients/ts/README.md#package-shape" +} + +rule generated-bindings "clients/ts/generated/ holds only ts-rs output" code IR6 { + limit code-lines <= 0 in path:clients/ts/generated/ + see "clients/ts/README.md#vibefieldrosetta-protocol" +} + +rule groundtruth-data-only "groundtruth/ is data-only" { + limit code-lines <= 0 in path:groundtruth/ + see "CLAUDE.md#conventions" +} + +rule permissive-raw "Raw serde types are permissive" { + forbid text "deny_unknown_fields" in path:crates/adapters/*/src/raw/ + see "CLAUDE.md#conventions" +} + +rule module-size "Keep modules under ~1,000 lines" warn { + limit lines <= 1000 in path:crates/, path:clients/ + see "CLAUDE.md#conventions" + see "docs/ARCHITECTURE.md#1-lessons-from-spaghetti-what-we-keep-what-we-kill" +} diff --git a/atlas/system.atlas b/atlas/system.atlas new file mode 100644 index 00000000..db263831 --- /dev/null +++ b/atlas/system.atlas @@ -0,0 +1,21 @@ +atlas 1 + +// Rosetta's design intent: what the code cannot say about itself, graded against the code by Project Atlas. +// Crate roots and a few seams carry their own `@atlas` lines (`grep -rn '@atlas' crates clients`). +// Duties and the one command: CLAUDE.md "Atlas"; the authoring guide is the `atlas` skill. + +system rosetta "Rosetta" { + intent "Multi-agent ingestion engine: ledger every raw record coding agents write, serve one canonical projection over localhost HTTP/WS." + thesis """ + Three jobs, in consumer priority: search, observe, monitor. + L1 ground truth → L2 runtime → L3 services: data flows up, dependencies point down. + All knowledge of one agent lives in its pair: groundtruth/ and crates/adapters/. + Two data representations only: an append-only raw ledger and one disposable projection, rebuilt, never migrated. + Parsers exist once, in Rust; consumers get generated types and dial the daemon, never the store. + """ + see "CLAUDE.md#iron-rules-each-one-is-a-spaghetti-failure-mode--do-not-relitigate" + see "docs/ARCHITECTURE.md#rosetta-architecture" +} + +// The adapter glob also matches rosetta-adapter-support; the agents are the other 9. +let $agents = crate:rosetta-adapter-*, !crate:rosetta-adapter-support diff --git a/clients/app/renderer/src/host/rosetta-ports.ts b/clients/app/renderer/src/host/rosetta-ports.ts index 32d57d76..94862970 100644 --- a/clients/app/renderer/src/host/rosetta-ports.ts +++ b/clients/app/renderer/src/host/rosetta-ports.ts @@ -15,6 +15,7 @@ export interface DaemonWords { const NO_WORDS: DaemonWords = { unavailable: () => null, onChange: () => () => {} }; +// @atlas step search-query.port /** The catalog over the daemon's own lists and figures: REST and /ws/meta. */ export function catalogOver(client: RosettaClient, words: DaemonWords = NO_WORDS): CatalogPort { return { diff --git a/clients/room/src/components/search/SearchSheet.tsx b/clients/room/src/components/search/SearchSheet.tsx index d1c09b1b..28033ce8 100644 --- a/clients/room/src/components/search/SearchSheet.tsx +++ b/clients/room/src/components/search/SearchSheet.tsx @@ -211,6 +211,7 @@ const GRAMMAR_HINT = ( ); +// @atlas step search-query.sheet export function SearchSheet({ open, onClose, diff --git a/clients/room/src/index.ts b/clients/room/src/index.ts index dacf0509..37f749c4 100644 --- a/clients/room/src/index.ts +++ b/clients/room/src/index.ts @@ -1,6 +1,11 @@ // @vibefield/rosetta-room — rosetta's History room behind injected ports. A host hands // it a catalog, a way to open one session's body, an asset source and, on a // desktop, a host port; the room dials nothing itself. See README.md. +// +// @atlas rule room-dials-nothing "The room dials nothing: every byte arrives through a port" { +// forbid text "fetch(", "new WebSocket(", "new EventSource(", "XMLHttpRequest" in pkg:@vibefield/rosetta-room +// see "clients/room/README.md#what-the-package-never-does" +// } export { HistoryRoom } from "./HistoryRoom"; export type { diff --git a/clients/room/src/lib/hooks.ts b/clients/room/src/lib/hooks.ts index e8c2949a..b85242f3 100644 --- a/clients/room/src/lib/hooks.ts +++ b/clients/room/src/lib/hooks.ts @@ -326,6 +326,7 @@ export interface TimelineFeed { loadAnchor: (timelineId: string) => Promise; } +// @atlas step live.render export function useTimeline(sessions: SessionLeases, sessionId: string | null): TimelineFeed { const [owner, setOwner] = useState(sessionId); const [attempt, setAttempt] = useState(0); @@ -693,6 +694,7 @@ export interface SearchFacets { export const SEARCH_LIMIT = 200; +// @atlas step search-query.feed /** * One ranked search per settled query. The previous response stays on screen * while its successor is in flight — a list that blanks on every keystroke diff --git a/clients/ts/client.ts b/clients/ts/client.ts index b2fddbde..45916de4 100644 --- a/clients/ts/client.ts +++ b/clients/ts/client.ts @@ -527,6 +527,7 @@ export class RosettaClient { return new Uint8Array(await response.arrayBuffer()); } + // @atlas step search-query.client search(request: SearchRequest, signal?: AbortSignal): Promise { return this.request( `/api/search${queryString({ diff --git a/crates/adapter-support/src/adapter.rs b/crates/adapter-support/src/adapter.rs index fd2adb47..003f4051 100644 --- a/crates/adapter-support/src/adapter.rs +++ b/crates/adapter-support/src/adapter.rs @@ -227,6 +227,10 @@ impl NormalizeCtx { } } +// @atlas contract adapter "Agent adapter interface" { +// implemented-by $agents +// used-by crate:rosetta-pipeline, crate:rosettad +// } /// One implementation per agent. Object-safe: the pipeline holds a registry /// of `&'static dyn AgentAdapter`. pub trait AgentAdapter: Send + Sync { @@ -390,6 +394,7 @@ pub trait AgentAdapter: Send + Sync { true } + // @atlas step live.parse /// Stream phase: one raw record in, canonical ops out. Combines the pure /// parse and normalize stages; each adapter keeps its `parse_line` public /// so the stages stay independently testable and benchable. diff --git a/crates/adapter-support/src/lib.rs b/crates/adapter-support/src/lib.rs index 246af574..710764cf 100644 --- a/crates/adapter-support/src/lib.rs +++ b/crates/adapter-support/src/lib.rs @@ -3,6 +3,8 @@ //! Ground-truth document construction is intentionally implemented by the //! Python tooling under `tools/groundtruth`; this crate contains only the //! pieces that production Rust parsers need. +//! +//! @atlas layer adapter-kit · domain normalize pub mod adapter; mod glob; diff --git a/crates/adapters/antigravity/src/lib.rs b/crates/adapters/antigravity/src/lib.rs index df1e589c..2294b393 100644 --- a/crates/adapters/antigravity/src/lib.rs +++ b/crates/adapters/antigravity/src/lib.rs @@ -9,6 +9,8 @@ //! siblings remain layout inventory. `conversations/.db` owns usage: the //! transcript carries no token counts, while the store's protobuf step //! metadata records every model request (see [`usage`]). +//! +//! @atlas layer agents · domain normalize pub mod adapter; pub mod normalize; diff --git a/crates/adapters/claude-code/src/lib.rs b/crates/adapters/claude-code/src/lib.rs index 15bd68bc..5d934b3c 100644 --- a/crates/adapters/claude-code/src/lib.rs +++ b/crates/adapters/claude-code/src/lib.rs @@ -12,6 +12,8 @@ //! * The workspace carries no date crate and `rosetta-core` exposes no //! timestamp parser, so RFC 3339 → `Ms` lives in [`time`]. Every agent will //! need it; it belongs in `core`. +//! +//! @atlas layer agents · domain normalize pub mod adapter; mod cost_state; diff --git a/crates/adapters/codex/src/lib.rs b/crates/adapters/codex/src/lib.rs index 584d23e5..15094252 100644 --- a/crates/adapters/codex/src/lib.rs +++ b/crates/adapters/codex/src/lib.rs @@ -12,6 +12,8 @@ //! * The workspace carries no date crate and `rosetta-core` exposes no //! timestamp parser, so RFC 3339 → `Ms` lives in [`time`]. Every agent will //! need it; it belongs in `core`. +//! +//! @atlas layer agents · domain normalize pub mod adapter; mod exec_program; diff --git a/crates/adapters/copilot/src/lib.rs b/crates/adapters/copilot/src/lib.rs index 4df43aa1..88cdd286 100644 --- a/crates/adapters/copilot/src/lib.rs +++ b/crates/adapters/copilot/src/lib.rs @@ -1,6 +1,8 @@ //! rosetta-adapter-copilot — the Layer-2 PARSER for GitHub Copilot CLI, //! designed against the common interface with the Layer-1 canonical ground //! truth (`groundtruth/copilot/groundtruth.json`) as its specification. +//! +//! @atlas layer agents · domain normalize pub mod adapter; pub mod normalize; diff --git a/crates/adapters/grok/src/lib.rs b/crates/adapters/grok/src/lib.rs index 8832ec76..2ec652f6 100644 --- a/crates/adapters/grok/src/lib.rs +++ b/crates/adapters/grok/src/lib.rs @@ -1,6 +1,8 @@ //! rosetta-adapter-grok — the Layer-2 PARSER for Grok Build, designed against //! the common interface with the Layer-1 canonical ground truth //! (`groundtruth/grok/groundtruth.json`) as its specification. +//! +//! @atlas layer agents · domain normalize pub mod adapter; mod images; diff --git a/crates/adapters/hermes/src/lib.rs b/crates/adapters/hermes/src/lib.rs index c5bf2eea..89d92806 100644 --- a/crates/adapters/hermes/src/lib.rs +++ b/crates/adapters/hermes/src/lib.rs @@ -2,6 +2,8 @@ //! against the common interface with the Layer-1 canonical ground truth //! (`groundtruth/hermes/groundtruth.json`) as its specification. //! Replay tests prove it against the L1 fixture corpus. +//! +//! @atlas layer agents · domain normalize pub mod adapter; pub mod normalize; diff --git a/crates/adapters/kimi-code/src/lib.rs b/crates/adapters/kimi-code/src/lib.rs index 28824c57..5979a961 100644 --- a/crates/adapters/kimi-code/src/lib.rs +++ b/crates/adapters/kimi-code/src/lib.rs @@ -1,6 +1,8 @@ //! rosetta-adapter-kimi-code — the Layer-2 PARSER for Kimi Code CLI, designed //! against the common interface with the Layer-1 canonical ground truth //! (`groundtruth/kimi-code/groundtruth.json`) as its specification. +//! +//! @atlas layer agents · domain normalize pub mod adapter; mod content; diff --git a/crates/adapters/opencode/src/lib.rs b/crates/adapters/opencode/src/lib.rs index 4981b99a..37f2eac3 100644 --- a/crates/adapters/opencode/src/lib.rs +++ b/crates/adapters/opencode/src/lib.rs @@ -2,6 +2,8 @@ //! against the common interface with the Layer-1 canonical ground truth //! (`groundtruth/opencode/groundtruth.json`) as its specification. //! Replay tests prove it against the L1 fixture corpus. +//! +//! @atlas layer agents · domain normalize pub mod adapter; pub mod normalize; diff --git a/crates/adapters/pi/src/lib.rs b/crates/adapters/pi/src/lib.rs index 1fa34732..c68011a1 100644 --- a/crates/adapters/pi/src/lib.rs +++ b/crates/adapters/pi/src/lib.rs @@ -1,6 +1,8 @@ //! rosetta-adapter-pi — the Layer-2 PARSER for Pi, designed against the //! common interface with the Layer-1 canonical ground truth //! (`groundtruth/pi/groundtruth.json`) as its specification. +//! +//! @atlas layer agents · domain normalize pub mod adapter; pub mod normalize; diff --git a/crates/bench-corpus/src/lib.rs b/crates/bench-corpus/src/lib.rs index 541740b8..405183be 100644 --- a/crates/bench-corpus/src/lib.rs +++ b/crates/bench-corpus/src/lib.rs @@ -20,6 +20,8 @@ //! let report = generate(&spec, Path::new("/tmp/rosetta-bench/cc")).unwrap(); //! println!("{}", report.table()); //! ``` +//! +//! @atlas layer tooling · domain perf pub mod claude_code; pub mod clock; diff --git a/crates/capture/src/ledger.rs b/crates/capture/src/ledger.rs index ed713f74..1a1e97d2 100644 --- a/crates/capture/src/ledger.rs +++ b/crates/capture/src/ledger.rs @@ -292,6 +292,7 @@ impl Ledger { Ok(()) } + // @atlas step live.ledger /// Owned variant for cold pipelines that can move framed payloads out of /// their tail batch. Small records share a bounded frame so checksumming /// and compression writes are amortized. Large records retain the direct diff --git a/crates/capture/src/lib.rs b/crates/capture/src/lib.rs index 881e6823..2fcb9f99 100644 --- a/crates/capture/src/lib.rs +++ b/crates/capture/src/lib.rs @@ -3,6 +3,8 @@ //! Agent-agnostic source IO lives here: append-delimited [`tailer`] reads, //! data-root [`discovery`], bounded meta reads, consistent SQLite snapshots, //! filesystem notification hints, and the append-only raw ledger. +//! +//! @atlas layer capture-io · domain capture pub mod discovery; pub mod document; diff --git a/crates/capture/src/tailer.rs b/crates/capture/src/tailer.rs index 117343bd..f4e7eb00 100644 --- a/crates/capture/src/tailer.rs +++ b/crates/capture/src/tailer.rs @@ -378,6 +378,7 @@ impl Tailer { Ok(Self { config }) } + // @atlas step live.read pub fn read( &self, path: &Path, diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 586853de..84963823 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -2,6 +2,12 @@ //! the [`IngestOp`] stream every adapter normalizes into, deterministic ids, //! and the pure fold helpers. Zero IO dependencies by design; every function //! here benches and tests in isolation (docs/ingest-engine.html). +//! +//! @atlas layer model · domain normalize +//! @atlas rule core-is-a-leaf "core depends on nothing internal" { +//! only this -> nothing +//! see "docs/ARCHITECTURE.md#8-workspace-layout" +//! } pub mod assets; pub mod fold; diff --git a/crates/core/src/ops.rs b/crates/core/src/ops.rs index a577f7ca..f9757b16 100644 --- a/crates/core/src/ops.rs +++ b/crates/core/src/ops.rs @@ -15,6 +15,10 @@ use crate::timeline::{ ToolUpdatePatch, TruncationScope, WorkflowJournalObservation, WorkflowRun, }; +// @atlas contract ingest-op "IngestOp: where agent weirdness ends" { +// produced-by $agents, crate:rosetta-pipeline each-variant +// used-by crate:rosetta-store +// } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(tag = "op", rename_all = "snake_case")] pub enum IngestOp { diff --git a/crates/media/src/lib.rs b/crates/media/src/lib.rs index 382b004d..a8eaf90d 100644 --- a/crates/media/src/lib.rs +++ b/crates/media/src/lib.rs @@ -1,5 +1,7 @@ //! One bounded, vendor-neutral WebP pipeline. Original bytes remain ledger-only. //! No filesystem, network, clock or vendor knowledge enters image preparation. +//! +//! @atlas layer kernels · domain media mod animation; mod pixels; diff --git a/crates/pipeline/src/lib.rs b/crates/pipeline/src/lib.rs index 0bbcdd68..1c70b385 100644 --- a/crates/pipeline/src/lib.rs +++ b/crates/pipeline/src/lib.rs @@ -10,6 +10,12 @@ //! The pipeline owns id derivation: adapters report NATIVE facts, and every //! rosetta id (runtime, project, session, timeline prefix inputs) is minted //! here from `rosetta_core::ids`. +//! +//! @atlas layer orchestration · domain ingest +//! @atlas rule pipeline-deps "pipeline → {core, capture, store, adapters}" { +//! only this -> crate:rosetta-{core, capture, store}, crate:rosetta-adapter-* +//! see "docs/ARCHITECTURE.md#8-workspace-layout" +//! } pub mod capture; pub mod checkpoint; diff --git a/crates/pipeline/src/live.rs b/crates/pipeline/src/live.rs index 7e074cc9..a23b01dd 100644 --- a/crates/pipeline/src/live.rs +++ b/crates/pipeline/src/live.rs @@ -334,6 +334,7 @@ impl<'a> LiveRunner<'a> { .map(|_| ()) } + // @atlas step live.map /// Drain ready hints and run due recovery work without waiting on this /// runtime. A shared writer waits once on `rosetta_capture::SourceWake` /// between passes, and publishes committed effects after each runner. diff --git a/crates/pricing/src/lib.rs b/crates/pricing/src/lib.rs index 47d5e86b..ba6e9845 100644 --- a/crates/pricing/src/lib.rs +++ b/crates/pricing/src/lib.rs @@ -6,6 +6,8 @@ //! reads no clock, and allocates only to remember a new model id per thread. //! Adapters never call it: the store prices usage records inside the hourly //! rollup, and the server prices one response at read time. +//! +//! @atlas layer kernels · domain usage mod catalog; // Generator output is committed byte-exact (`tools/pricing generate --check`). diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 08214f31..232171f6 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -13,6 +13,12 @@ //! Pagination follows the exact-watermark rule: a cursor binds the session //! watermark at first page; on mismatch the server answers //! [`ErrorCode::CursorExpired`] — match or expired, never a silent rebase. +//! +//! @atlas layer wire-types · domain api +//! @atlas rule thin-protocol "protocol depends on almost nothing" { +//! only this -> crate:rosetta-core +//! see "docs/ARCHITECTURE.md#8-workspace-layout" +//! } use serde::{Deserialize, Serialize}; diff --git a/crates/rosettad/src/engine.rs b/crates/rosettad/src/engine.rs index 8401311b..c9aad93a 100644 --- a/crates/rosettad/src/engine.rs +++ b/crates/rosettad/src/engine.rs @@ -127,6 +127,7 @@ pub(crate) fn now_ms() -> i64 { .unwrap_or(0) } +// @atlas step live.absorb /// Drain both effects seams into the hub, strictly after commit. Row /// effects first — session mutations into the rings, their touched rows /// into the mirror — then the metadata identity keys, the writer thread's diff --git a/crates/rosettad/src/main.rs b/crates/rosettad/src/main.rs index f5f95f9a..218c0251 100644 --- a/crates/rosettad/src/main.rs +++ b/crates/rosettad/src/main.rs @@ -17,6 +17,12 @@ //! `--enroll FILE` (2026-09-12) is a supervisor's enrollment file: consent for //! discovered kinds given at start, plus extra managed data roots of a kind //! that are consented by construction — see `enroll.rs`. +//! +//! @atlas layer root · domain ingest +//! @atlas rule rosettad-is-root "Nothing depends on rosettad" { +//! deny * -> this on code, manifest +//! see "docs/ARCHITECTURE.md#8-workspace-layout" +//! } use std::collections::HashMap; use std::path::PathBuf; diff --git a/crates/rosettad/src/serve.rs b/crates/rosettad/src/serve.rs index 8225746b..12faa3e0 100644 --- a/crates/rosettad/src/serve.rs +++ b/crates/rosettad/src/serve.rs @@ -1664,6 +1664,7 @@ fn run_warm_live_serve( Ok((store, absorbed)) } +// @atlas step live.tick #[allow(clippy::too_many_arguments)] fn drive_live_runners( flags: &Flags, diff --git a/crates/search-bench/src/lib.rs b/crates/search-bench/src/lib.rs index 5b11a48c..5fe52658 100644 --- a/crates/search-bench/src/lib.rs +++ b/crates/search-bench/src/lib.rs @@ -6,6 +6,8 @@ //! every run verifies them before and after, opens SQLite read-only, and emits //! only timings, counts, labels, and digests. Query text, snippets, and row ids //! never enter the report. +//! +//! @atlas layer tooling · domain perf · touches search mod compare; mod fingerprint; diff --git a/crates/search/src/lib.rs b/crates/search/src/lib.rs index 05561d9b..aee8f039 100644 --- a/crates/search/src/lib.rs +++ b/crates/search/src/lib.rs @@ -4,6 +4,8 @@ //! query/readiness contract and the synchronous FTS5 reference backend so the //! server does not depend on an engine-specific query path. A disposable //! sidecar can implement [`SearchBackend`] without changing the L3 protocol. +//! +//! @atlas layer query · domain search use std::fmt; use std::path::{Path, PathBuf}; @@ -382,6 +384,10 @@ impl fmt::Display for SearchError { impl std::error::Error for SearchError {} +// @atlas contract search-backend "Backend-neutral search seam" { +// implemented-by crate:rosetta-search +// used-by crate:rosetta-server, crate:rosettad +// } /// Synchronous interface because search engines and SQLite are blocking. The /// async server runs calls on its blocking pool. pub trait SearchBackend: Send + Sync { diff --git a/crates/search/src/sidecar.rs b/crates/search/src/sidecar.rs index 631ab682..a5cab0d0 100644 --- a/crates/search/src/sidecar.rs +++ b/crates/search/src/sidecar.rs @@ -481,6 +481,7 @@ impl SearchBackend for Fts5SidecarSearch { state } + // @atlas step search-query.sidecar fn search(&self, query: SearchQuery) -> Result { query.validate()?; let before = canonical_state(&self.canonical)?; diff --git a/crates/server/src/http.rs b/crates/server/src/http.rs index da6a8be3..3fcf63a5 100644 --- a/crates/server/src/http.rs +++ b/crates/server/src/http.rs @@ -1122,6 +1122,7 @@ async fn command( Ok(Json(CommandResponse { command })) } +// @atlas step search-query.handler async fn search( State(state): State, Query(request): Query, diff --git a/crates/server/src/hub.rs b/crates/server/src/hub.rs index a401527b..33ae9ce6 100644 --- a/crates/server/src/hub.rs +++ b/crates/server/src/hub.rs @@ -420,6 +420,7 @@ impl Hub { state.mirror.runtimes().cloned().collect() } + // @atlas step live.hub /// Absorb one drained [`Effects`] batch, strictly after its transactions /// committed: refresh the mirror (row diffs → precise meta events), push /// session-scoped mutations into rings, fan out, evict idle rings. diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index b68fe8cd..adfa406b 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -5,6 +5,17 @@ //! [`grant`] (the bearer, per-session grants and the Origin rule — //! docs/AUTH.md). Async lives only in this layer; the ingest engine below //! stays synchronous. +//! +//! @atlas layer edge · domain api +//! @atlas rule server-deps "server → {core, protocol, store}" { +//! only this -> crate:rosetta-{core, protocol, store} +//! see "docs/ARCHITECTURE.md#8-workspace-layout" +//! } +//! @atlas rule search-seam "server searches only through the backend-neutral seam" { +//! deny this -> sym:rosetta_search::{Fts5SidecarSearch, Fts5Search} +//! except mod:rosetta_server::http -> sym:rosetta_search::Fts5SidecarSearch "AppState::new builds the FTS5 default for tests; the daemon injects its backend via with_search" +//! see "docs/ARCHITECTURE.md#6-storage" +//! } pub mod auth; pub mod board; diff --git a/crates/server/src/ws.rs b/crates/server/src/ws.rs index 51d4f506..19fc0a30 100644 --- a/crates/server/src/ws.rs +++ b/crates/server/src/ws.rs @@ -320,6 +320,7 @@ async fn timeline_meta_snapshot( .await } +// @atlas step live.frame /// One session subscription: hello, snapshot-or-replay, then live items /// until the client disconnects. pub(crate) async fn run_session_stream( diff --git a/crates/store/src/lib.rs b/crates/store/src/lib.rs index 325930c9..f096e15e 100644 --- a/crates/store/src/lib.rs +++ b/crates/store/src/lib.rs @@ -2,6 +2,8 @@ //! transactional write path, and the typed read surface. Foreign schema versions //! fail closed on ordinary open; `rosettad rebuild` explicitly reconstructs and //! atomically replaces the projection from the durable raw ledger. +//! +//! @atlas layer storage · domain projection mod activity; pub mod apply; @@ -449,6 +451,7 @@ impl Store { }) } + // @atlas step live.apply /// Transfer an owned batch into a cold planner without copying its /// normalized payload. Other store modes use the ordinary apply path. pub fn apply_owned(&mut self, batch: Batch) -> Result { From f539aace2b6f10f2d3d9360901ed5d625c399d6a Mon Sep 17 00:00:00 2001 From: James Yong Date: Thu, 15 Oct 2026 16:07:18 -0700 Subject: [PATCH 2/2] atlas: baseline today's five findings as known `atlas baseline` records the violations the atlas found on the day it landed (thin-protocol, server-deps, module-size's 14 files, IngestOp::GitCommit and the dependency-rule decision), so `check` exits 0 and a violation an agent introduces shows as new. They stay violated, tagged known. Co-Authored-By: Claude Opus 5.5 --- atlas/atlas.baseline.json | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 atlas/atlas.baseline.json diff --git a/atlas/atlas.baseline.json b/atlas/atlas.baseline.json new file mode 100644 index 00000000..3b64ac55 --- /dev/null +++ b/atlas/atlas.baseline.json @@ -0,0 +1,26 @@ +{ + "schema": "atlas.baseline/1", + "violations": [ + "claim:contract:ingest-op/contract/857de262", + "claim:decision:dependency-rule/decision/e8d6f51b", + "rule:module-size|clients/room/src/lib/transcript/families.ts", + "rule:module-size|clients/ts/client.ts", + "rule:module-size|crates/adapters/claude-code/src/normalize.rs", + "rule:module-size|crates/adapters/codex/src/normalize.rs", + "rule:module-size|crates/adapters/copilot/src/normalize.rs", + "rule:module-size|crates/adapters/grok/src/normalize.rs", + "rule:module-size|crates/core/src/timeline.rs", + "rule:module-size|crates/pipeline/src/meta.rs", + "rule:module-size|crates/pipeline/src/replay.rs", + "rule:module-size|crates/rosettad/src/main.rs", + "rule:module-size|crates/rosettad/src/serve.rs", + "rule:module-size|crates/server/src/http.rs", + "rule:module-size|crates/store/src/apply.rs", + "rule:module-size|crates/store/src/queries.rs", + "rule:server-deps|pkg:cargo/rosetta-server|pkg:cargo/rosetta-media", + "rule:server-deps|pkg:cargo/rosetta-server|pkg:cargo/rosetta-pricing", + "rule:server-deps|pkg:cargo/rosetta-server|pkg:cargo/rosetta-search", + "rule:thin-protocol|pkg:cargo/rosetta-protocol|pkg:cargo/rosetta-search", + "rule:thin-protocol|pkg:cargo/rosetta-protocol|pkg:cargo/rosetta-store" + ] +}