# AGENTS.md — zuka

Read [`../AGENTS.md`](../AGENTS.md) first; conventions cascade. This file records the
exceptions and the rules that only apply here.

Before working on a topic, read the doc that owns it: [`SPEC.md`](SPEC.md) for
architecture, API, security and data model; [`ROADMAP.md`](ROADMAP.md) for sequencing,
risk and open decisions; [`README.md`](README.md) for what the product is and is not.

## Exception: the runtime is Rust, not Deno

`product/AGENTS.md` states "Runtime is Deno. TypeScript throughout." **zuka is
Rust**, and this is a stated override rather than a silent one.

Why: the workload is git object manipulation, subprocess supervision and long-lived
byte streams, and it ships as a single self-hostable binary a stranger can drop on a
VM. A Deno service would need a runtime installed and would still shell out for every
git operation. The two existing Rust services in `yangu/` (`atlas`, `kazi`) set the
in-house precedent this follows.

Deno still applies to **tests** — the live suite in `test/live/` is TypeScript,
spawning the compiled binary and driving it with a real `git` CLI and real `fetch`.

The service now has one UI: the read-only browser viewer in `src/web/` (SPEC §4.9).
It is server-rendered from the binary with no bundler and no CDN, because zuka ships
as a single self-hostable file — so Motheo tokens and Worklyn Sans, which arrive
through `@ori/motheo` and a font pipeline, do **not** apply to it. It uses a system
font stack and its own small stylesheet. The marketing page in `yangu/website/`
follows the design system there, where a build step is available.

The viewer's audience is people new to code, not developers: a repository's front
page is its README rendered as a document (with a small Markdoc-style block-tag
dialect — see SPEC §4.9 and the README's "Your README is the website"), tabs say
Files/History/Branches, and navigation never resets the current ref. When touching
`src/web/`, keep the words plain and keep all state in the URL.

If that ever stops being true — if the viewer grows enough to justify a build — this
exception is the thing to revisit first.

## Rust conventions

Follow `yangu/atlas` and `yangu/kazi`, whose patterns are already load-bearing in
production:

- `hyper` 1.x + `tokio`, no web framework. `anyhow` internally, one typed `Error` at
  the HTTP boundary (SPEC §8.3). `serde` on the wire.
- `edition = "2021"`, standalone crate, committed `Cargo.lock`, `.gitignore` = `target/`,
  `[profile.release] opt-level = 3`, `lto = "thin"`.
- Config is `std::env::var` with defaults gathered into `Config::from_env() ->
  Result<Config>` that fails boot loudly on an invalid value. Prefix `ZUKA_`.
- Tests are inline `#[cfg(test)] mod tests` with sentence-named cases
  (`rejects_ref_write_without_if_match`), not `test_`-prefixed.
- CI gates on `cargo test` → `cargo clippy --all-targets -- -D warnings` →
  `cargo build --release`, following `yangu/.github/workflows/build-kazi.yml`.

**The live suite must run in CI.** It self-skips without `ZUKA_LIVE_TEST`, and a
proof that never executes is not a proof — SPEC calls the live tests the place the
wire protocol and REST contract are actually proven, so the CI job sets the flag and
installs `git`.

### Deviation: nested `src/`, not flat modules

`yangu/atlas` and `yangu/kazi` keep everything in one `main.rs` (1,158 and 3,050
lines). zuka nests by concern (SPEC §8) because it carries four protocol surfaces
against atlas's one. This is a judgment call, recorded as such — do not restate it as
a measured failure of the flat pattern.

## Rules specific to this service

- **`git::exec::Git` is the only way to invoke git.** Not `Command::new("git")`. It
  clears the environment and sets `GIT_CONFIG_NOSYSTEM`, so host configuration cannot
  change what our git calls do. Five wrappers with three different policies existed
  before it, and `/etc/gitconfig` applied to the ref rule and the quota accounting but
  not to the wire.
- **Exit 1 is an answer; anything higher is a fault.** `Git::query` encodes this.
  Collapsing every non-zero exit into `404` once meant a corrupt repository reported
  as empty and nothing paged anyone.
- **Git work never runs on a runtime worker.** Use `git::exec::blocking`. A fork+exec
  on a tokio worker stalls unrelated requests including `/healthz`, and the pool is
  only as wide as the core count.
- **Shared operations live in `core/`.** REST and MCP each had their own repository
  create and delete once; the copies drifted in four ways before anyone noticed. If a
  facade needs a rule, the rule goes in `core/` or `git/` and the facade calls it.
- **`AppState::open_repo` is how a repository is resolved.** It carries the access
  check, the ready check and — for `Access::Write` — the disk reserve and the storage
  quota, so no write path can forget them.

- **Validation in SPEC §2.5 is normative.** Ref names, repo names, tree paths and file
  modes are security controls, not input hygiene. A ref name reaching the filesystem
  unvalidated is remote code execution. Never hand-roll ref validation; use
  `gix-validate`.
- **Every ref mutation goes through a `gix-ref` transaction** with the precondition
  enforced *inside* it (SPEC §1.3). Check-then-write is a race against `receive-pack`.
- **One rule, one implementation.** The `update` hook and the REST ref endpoint call
  the same core function. If a rule appears in both a hook and a handler, it is in the
  wrong layer.
- **git is the only writer.** No endpoint and no MCP tool modifies a repository's
  contents or moves a ref; that is `git push`. Adding a write path means a second
  home for the fast-forward rule, a concurrency model and a merge story, all of
  which git already has (SPEC §1.3). If a task seems to need one, the answer is
  almost certainly that the caller should run git.
- **Facades translate; they do not decide.** `api/` and `mcp/` may fill defaults and
  reshape payloads. They may not carry authorization logic — that lives on
  `Identity`, so REST, MCP and SSH cannot drift. A facade must never call into
  another facade.
- **The git paths are not REST.** `/{account}/{repo}.git/*` has its own error contract:
  `WWW-Authenticate` on `401`, `application/x-git-*` content types, never
  `problem+json` (SPEC §4.7).
- **Scheduled work lives in `jobs/`** and its own systemd unit, never the web
  entrypoint.
- **CI isolation claims must name a mechanism.** "No network" means a network
  namespace; "isolated" means a uid, a cgroup and a scrubbed environment. An intention
  is not a control (SPEC §6.3).

## The gate

Run all four before pushing; CI runs the same set and gates on every one.

```sh
cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test
deno task test:live
```

## Status

Standalone is complete: M0–M5 and M7. M6 (multi-tenant control plane) is deferred and
unbuilt — see `ROADMAP.md` D9.

`SPEC.md` describes how the service works; `ROADMAP.md` says what exists and why. When
they disagree about what is built, the roadmap is right.
