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.5 KBPlain textDownload

AGENTS.md — zuka

Read ../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 for architecture, API, security and data model; ROADMAP.md for sequencing, risk and open decisions; 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.

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 testcargo clippy --all-targets -- -D warningscargo 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.

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.