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 existingkorrinbinary, or a new one? - What parses
korrin.toml/korrin.lock, and does that dependency belong in the publishedcrates/korrinlibrary? - How does package-fetching talk to git — a C library, a pure-Rust
reimplementation, or the system
gitbinary?
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 git | git2 (libgit2) | gix | |
|---|---|---|---|
| New dependency weight | none | a C library behind unsafe FFI bindings, a heavier transitive tree | pure Rust, no C dependency, but large and comparatively immature |
Against unsafe_code = "forbid" | no tension | genuinely 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 code | no tension |
| New environmental assumption | none — this project's own contributor workflow already assumes a working git on PATH | an entirely separate git implementation alongside the one already on every contributor's machine | same concern as git2 |
| Auth for private repos | inherits the user's already-configured git auth (SSH agent, credential helpers) for free | needs its own auth story, or falls back to delegating to system git anyway | same 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/korrinstays exactly as dependency-lean as ADR 0013 asks — this entire feature adds nothing to itsCargo.toml.korrin-cligains one new dependency (korrin-pkg, path-local) rather than three or four (a TOML parser, a git library, error-handling glue).- Every
korrin pkgoperation that touches the network depends on the user's localgitbehaving 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-lspor another consumer,korrin-pkgis 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-pkgbinary. 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 throughassert_cmd. git2orgixfor 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.