Files
alkgit/docs/architecture/decisions/009-bounded-resources-budget.md
T
glm-5.3-flash e76f91f6d7 docs(architecture): resolve OQ-04 — receive-pack state machine (ADR-013)
- ADR-013: V0-framed push machine grounded in real git 2.43.0 captures
  (file://, git://, smart-http mock, raw stdio into real receive-pack):
  V0-shaped ref advertisement (caps on first ref line, capabilities^{}
  sentinel only for empty repos), served capability set, shallow requests
  rejected for v1, thin packs accepted with server-odb bases (no
  capability involved; push.thin default), ingestion bound to
  Bundle::write_to_directory_eagerly + gix-fsck + one gix-ref transaction
  per push (.keep-guarded), unpack-first CAS timing with observed
  upstream order, band-1 pkt-line-framed status report, http framing
  (probe/Content-Length/chunked), v1 update policy (CAS only; deletes
  and force-push allowed)
- docs/research/push-captures.md: the normative push wire record
- transport.md/backend.md/doors.md: receive-pack sections rewritten to
  the decided shapes; backend.md ingestion composition bound; stale
  OQ-04 references resolved
- ADR-003 amended: V2-only governs fetch; push is V0-framed by upstream
  design (fixes the V2-only contradiction found in review)
- ADR-009 amended: haves default reconciled with the client's stateless
  ceiling (16384); blocking-pipeline budget covers generation+ingestion
- OQ-04 resolved; tracker task closed; CAS-fail-fast optimization
  tracked (tasks/architecture/oq-13-cas-failfast.md)
- research index: poc findings + capture docs listed

Verification: cargo test / clippy -D warnings / fmt --check / doc pass
2026-09-25 04:05:07 +00:00

3.3 KiB
Raw Blame History

ADR-009: Bounded-resources budget model

Status

Accepted

Context

Git servers are internet-facing by definition; unbounded loops and buffers are bugs. Each POC surfaced specific unbounded surfaces that need budgets:

  • Negotiation rounds / haves count (fetch can loop forever without done).
  • receive-pack POST body size (push can be arbitrarily large; alkhttp custom routes get hyper's unbounded stream — POC-3).
  • Session wall-clock (long-lived ssh/git sessions).
  • Blocking-pool usage: pack generation runs on spawn_blocking; a thundering herd of fetches can starve the pool (POC-2 follow-up 2).
  • Sideband chunk size is bounded (65000) but max pack size per fetch is still unbounded above it.
  • alkcall channels carry their own backpressure limits (alkcall ADR-040); git sessions ride raw duplex streams, so those limits do not automatically apply.

Decision

Every session carries a Limits value, constructed by the door/adapter from assembler config and handed to the wire layer as part of the session tuple (ADR-002). On the native path the producer adapter is in-crate (GitAdapter), but Limits still originates from assembler config — the adapter's constructor takes it. Defaults are crate constants; overrides are assembler config.

Budget Applies to Default direction
max negotiation rounds fetch (V2, no done) tens
max haves per round fetch thousands (the client's stateless doubling can legitimately reach 16384 — set the default at or above that)
max pack size receive-pack config-bound (tens of MB v1)
max request body http POSTs (receive-pack especially) same as max pack size
session wall clock all sessions (enforced by transport's session loop on every door — it is the one component all doors hand the session to; alkcall channel caps add a second bound where channels exist) tens of minutes
max concurrent blocking pipeline tasks server-wide (blocking-pool budget; covers pack generation and pack ingestion alike — both are spawn_blocking consumers) small count
sideband chunk size fetch streaming 65000 (fixed, per protocol)
max advertisement refs ls-refs response config-bound

On breach: the session ends with a substrate-appropriate error — pkt-line error band + close on duplex; on http, client-fault budgets (request body size) map to 413, server/session budgets (wall clock, rounds, generation concurrency exhaustion) map to 503. Budgets are fail-closed.

Max pack size on fetch is not budgeted in v1 (the pack is a function of the repo, not the request); receive-pack is the untrusted-input path and gets the hard cap.

Consequences

  • No adapter can forget a budget: transport refuses to start a session without Limits (part of the tuple, ADR-002).
  • Streaming stays O(counts) regardless of budgets; budgets bound aggregate work, not internal buffering.
  • The blocking-pool budget is enforced at assembly/acceptance time (reject/slow-path excess concurrent generations), not per-byte.

References

  • docs/research/vision.md §"Guiding principles" 7
  • docs/research/poc2-findings.md follow-ups 2–3; docs/research/poc3-findings.md follow-up 3
  • alkcall ADR-040 (channel backpressure — the thing git sessions bypass)
  • ADR-002 (session tuple), transport.md §Limits