Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

33. The korrin-pkg crate and its dependency tier

  • Status: Accepted
  • Date: 2026-09-05

Context

ADR 0031 and ADR 0032 settled what packages are and how import finds them. This ADR is the mechanical question every new crate in this workspace has answered once already (ADR 0026 for korrin-lsp): where does the code that implements this live, what does it depend on, and what dependency tier does it sit in under ADR 0013?

Three sub-questions, each with a real choice:

  • Does korrin pkg ... live on the existing korrin binary, or a new one?
  • What parses korrin.toml/korrin.lock, and does that dependency belong in the published crates/korrin library?
  • How does package-fetching talk to git — a C library, a pure-Rust reimplementation, or the system git binary?

Decision

A new library crate, crates/korrin-pkg/, with no [[bin]]

Manifest parsing, lockfile handling, git access, and dependency resolution live in crates/korrin-pkg/, a library korrin-cli depends on — not inside crates/korrin itself, and not inside korrin-cli directly.

This follows the mechanical pattern docs/internals/architecture.md already documents from adding korrin-lsp in Milestone 4: a new crates/<name>/, added to the workspace's members, its own [lints]\nworkspace = true, a korrin = { path = "../korrin" } dependency, a new tier section in docs/internals/dependencies.md. No existing crate needs to change, and no CI job needs to change (--workspace jobs pick it up automatically).

A separate crate rather than folding this into korrin-cli directly: it keeps manifest/lockfile/git logic unit- and integration-testable against local fixture git repos without spawning the CLI binary through assert_cmd; it keeps korrin-cli's own dependency list from growing a TOML parser and a git-access strategy it would otherwise need directly; and it leaves room for korrin-lsp to eventually resolve installed packages for hover or go-to-definition without depending on the whole CLI binary crate.

crates/korrin, the published library, is unaffected — it gains a set_packages(HashMap<String, PathBuf>) seam (already-resolved paths in, nothing about manifest or lockfile formats known to it) and no new dependency at all.

korrin pkg is a subcommand of the existing korrin binary

korrin pkg add/install/remove/update/list are new variants on korrin-cli's existing Command enum, the same shape as run/tokens/ ast/bytecode. This is different from korrin-lsp, which is a separate program — a persistent server an editor spawns and speaks a wire protocol to over stdio, never typed directly by a person (ADR 0026). korrin pkg add ... is something a developer types at a prompt; a second binary would only fragment the CLI for no benefit.

Manifest/lockfile parsing: toml_edit

toml_edit, not plain toml, specifically for in-place, comment-preserving edits — korrin pkg add/remove rewrite korrin.toml without disturbing whatever else is in it, the same reason Cargo itself uses toml_edit for cargo add/cargo remove rather than the plain toml crate it also depends on elsewhere.

Git access: shell out to the installed git binary

Package fetching runs git as a subprocess (std::process::Command) rather than linking a git implementation.

shell out to gitgit2 (libgit2)gix
New dependency weightnonea C library behind unsafe FFI bindings, a heavier transitive treepure Rust, no C dependency, but large and comparatively immature
Against unsafe_code = "forbid"no tensiongenuinely awkward — this workspace's own crates never write unsafe, and a dependency whose entire purpose is unsafe FFI to a C library sits against that identity, even though the lint itself only fires on this workspace's own codeno tension
New environmental assumptionnone — this project's own contributor workflow already assumes a working git on PATHan entirely separate git implementation alongside the one already on every contributor's machinesame concern as git2
Auth for private reposinherits the user's already-configured git auth (SSH agent, credential helpers) for freeneeds its own auth story, or falls back to delegating to system git anywaysame concern as git2

Shelling out costs zero new dependencies, introduces no unsafe anywhere in this feature's dependency graph, and inherits the user's already-working git configuration — a real advantage for private package repositories that either library alternative would otherwise need to reimplement or delegate back to system git for anyway. The cost is requiring git on PATH, already true of every contributor to this repository.

Concretely: a tag/branch-pinned dependency uses git clone --depth 1 --branch <ref> <url> <dest> (shallow, fast). An explicit rev (a commit SHA) first tries a shallow git fetch --depth 1 origin <rev> (works on hosts that allow fetching an arbitrary SHA — GitHub and GitLab both do, with that server-side option) and falls back to a full git clone plus git checkout <rev> if the shallow fetch is rejected — a documented, real git-hosting inconsistency handled explicitly rather than assumed away.

semver is not added

Dependency versions are exact git refs, not ranges (ADR 0031). Nothing in this design compares or constrains version numbers, so semver has no load-bearing job. Noted here, and in docs/internals/dependencies.md, so a later reader doesn't wonder whether it was simply missed.

Consequences

  • crates/korrin stays exactly as dependency-lean as ADR 0013 asks — this entire feature adds nothing to its Cargo.toml.
  • korrin-cli gains one new dependency (korrin-pkg, path-local) rather than three or four (a TOML parser, a git library, error-handling glue).
  • Every korrin pkg operation that touches the network depends on the user's local git behaving the way this design assumes (shallow clones, shallow SHA fetches where supported). A git host with unusual restrictions could need a narrower fallback path added later; the two-step shallow/full fallback already anticipates the most common case.
  • If Milestone 6 or later ever wants Rust-native package logic embedded directly into korrin-lsp or another consumer, korrin-pkg is already shaped as a library ready to be reused, not something to extract out of a binary-only crate later.

Alternatives considered

  • A separate korrin-pkg binary. Rejected — see "Decision" above. korrin-lsp's separateness comes from being a different kind of program (a server, not a CLI a person types); packaging commands have no such property.
  • Folding manifest/lockfile/git logic directly into korrin-cli. Simpler on paper, but couples CLI argument parsing to file-format and subprocess logic that has nothing to do with argument parsing, and loses the ability to test that logic without going through assert_cmd.
  • git2 or gix for git access. See the comparison table above — both are reasonable engineering choices in general; neither earns its cost against a workspace this dependency-conscious when shelling out solves the same problem for free.