zuka
zuka/AGENTS.md

worklyn / zukapublic

Agent-first git hosting. One Rust binary: git over HTTP and SSH, a REST API, MCP, CI, and multi-tenant isolation.

Get a copy: git clone https://zuka.worklyn.com/worklyn/zuka.git
zuka/AGENTS.md
MDAGENTS.md6.9 KBFormattedDownload
1# AGENTS.md — zuka
2
3Read [`../AGENTS.md`](../AGENTS.md) first; conventions cascade. This file records the
4exceptions and the rules that only apply here.
5
6Before working on a topic, read the doc that owns it: [`SPEC.md`](SPEC.md) for
7architecture, API, security and data model; [`ROADMAP.md`](ROADMAP.md) for sequencing,
8risk and open decisions; [`README.md`](README.md) for what the product is and is not.
9
10## Exception: the runtime is Rust, not Deno
11
12`product/AGENTS.md` states "Runtime is Deno. TypeScript throughout." **zuka is
13Rust**, and this is a stated override rather than a silent one.
14
15Why: the workload is git object manipulation, subprocess supervision and long-lived
16byte streams, and it ships as a single self-hostable binary a stranger can drop on a
17VM. A Deno service would need a runtime installed and would still shell out for every
18git operation. The two existing Rust services in `yangu/` (`atlas`, `kazi`) set the
19in-house precedent this follows.
20
21Deno still applies to **tests** — the live suite in `test/live/` is TypeScript,
22spawning the compiled binary and driving it with a real `git` CLI and real `fetch`.
23
24The service now has one UI: the read-only browser viewer in `src/web/` (SPEC §4.9).
25It is server-rendered from the binary with no bundler and no CDN, because zuka ships
26as a single self-hostable file — so Motheo tokens and Worklyn Sans, which arrive
27through `@ori/motheo` and a font pipeline, do **not** apply to it. It uses a system
28font stack and its own small stylesheet. The marketing page in `yangu/website/`
29follows the design system there, where a build step is available.
30
31The viewer's audience is people new to code, not developers: a repository's front
32page is its README rendered as a document (with a small Markdoc-style block-tag
33dialect — see SPEC §4.9 and the README's "Your README is the website"), tabs say
34Files/History/Branches, and navigation never resets the current ref. When touching
35`src/web/`, keep the words plain and keep all state in the URL.
36
37If that ever stops being true — if the viewer grows enough to justify a build — this
38exception is the thing to revisit first.
39
40## Rust conventions
41
42Follow `yangu/atlas` and `yangu/kazi`, whose patterns are already load-bearing in
43production:
44
45- `hyper` 1.x + `tokio`, no web framework. `anyhow` internally, one typed `Error` at
46 the HTTP boundary (SPEC §8.3). `serde` on the wire.
47- `edition = "2021"`, standalone crate, committed `Cargo.lock`, `.gitignore` = `target/`,
48 `[profile.release] opt-level = 3`, `lto = "thin"`.
49- Config is `std::env::var` with defaults gathered into `Config::from_env() ->
50 Result<Config>` that fails boot loudly on an invalid value. Prefix `ZUKA_`.
51- Tests are inline `#[cfg(test)] mod tests` with sentence-named cases
52 (`rejects_ref_write_without_if_match`), not `test_`-prefixed.
53- CI gates on `cargo test` → `cargo clippy --all-targets -- -D warnings` →
54 `cargo build --release`, following `yangu/.github/workflows/build-kazi.yml`.
55
56**The live suite must run in CI.** It self-skips without `ZUKA_LIVE_TEST`, and a
57proof that never executes is not a proof — SPEC calls the live tests the place the
58wire protocol and REST contract are actually proven, so the CI job sets the flag and
59installs `git`.
60
61### Deviation: nested `src/`, not flat modules
62
63`yangu/atlas` and `yangu/kazi` keep everything in one `main.rs` (1,158 and 3,050
64lines). zuka nests by concern (SPEC §8) because it carries four protocol surfaces
65against atlas's one. This is a judgment call, recorded as such — do not restate it as
66a measured failure of the flat pattern.
67
68## Rules specific to this service
69
70- **`git::exec::Git` is the only way to invoke git.** Not `Command::new("git")`. It
71 clears the environment and sets `GIT_CONFIG_NOSYSTEM`, so host configuration cannot
72 change what our git calls do. Five wrappers with three different policies existed
73 before it, and `/etc/gitconfig` applied to the ref rule and the quota accounting but
74 not to the wire.
75- **Exit 1 is an answer; anything higher is a fault.** `Git::query` encodes this.
76 Collapsing every non-zero exit into `404` once meant a corrupt repository reported
77 as empty and nothing paged anyone.
78- **Git work never runs on a runtime worker.** Use `git::exec::blocking`. A fork+exec
79 on a tokio worker stalls unrelated requests including `/healthz`, and the pool is
80 only as wide as the core count.
81- **Shared operations live in `core/`.** REST and MCP each had their own repository
82 create and delete once; the copies drifted in four ways before anyone noticed. If a
83 facade needs a rule, the rule goes in `core/` or `git/` and the facade calls it.
84- **`AppState::open_repo` is how a repository is resolved.** It carries the access
85 check, the ready check and — for `Access::Write` — the disk reserve and the storage
86 quota, so no write path can forget them.
87
88- **Validation in SPEC §2.5 is normative.** Ref names, repo names, tree paths and file
89 modes are security controls, not input hygiene. A ref name reaching the filesystem
90 unvalidated is remote code execution. Never hand-roll ref validation; use
91 `gix-validate`.
92- **Every ref mutation goes through a `gix-ref` transaction** with the precondition
93 enforced *inside* it (SPEC §1.3). Check-then-write is a race against `receive-pack`.
94- **One rule, one implementation.** The `update` hook and the REST ref endpoint call
95 the same core function. If a rule appears in both a hook and a handler, it is in the
96 wrong layer.
97- **git is the only writer.** No endpoint and no MCP tool modifies a repository's
98 contents or moves a ref; that is `git push`. Adding a write path means a second
99 home for the fast-forward rule, a concurrency model and a merge story, all of
100 which git already has (SPEC §1.3). If a task seems to need one, the answer is
101 almost certainly that the caller should run git.
102- **Facades translate; they do not decide.** `api/` and `mcp/` may fill defaults and
103 reshape payloads. They may not carry authorization logic — that lives on
104 `Identity`, so REST, MCP and SSH cannot drift. A facade must never call into
105 another facade.
106- **The git paths are not REST.** `/{account}/{repo}.git/*` has its own error contract:
107 `WWW-Authenticate` on `401`, `application/x-git-*` content types, never
108 `problem+json` (SPEC §4.7).
109- **Scheduled work lives in `jobs/`** and its own systemd unit, never the web
110 entrypoint.
111- **CI isolation claims must name a mechanism.** "No network" means a network
112 namespace; "isolated" means a uid, a cgroup and a scrubbed environment. An intention
113 is not a control (SPEC §6.3).
114
115## The gate
116
117Run all four before pushing; CI runs the same set and gates on every one.
118
119```sh
120cargo fmt --check
121cargo clippy --all-targets -- -D warnings
122cargo test
123deno task test:live
124```
125
126## Status
127
128Standalone is complete: M0–M5 and M7. M6 (multi-tenant control plane) is deferred and
129unbuilt — see `ROADMAP.md` D9.
130
131`SPEC.md` describes how the service works; `ROADMAP.md` says what exists and why. When
132they disagree about what is built, the roadmap is right.