Files
alkssh/tasks/implementation/w3-git-mapping.md
T
glm-5.3-flash 7aac2d11a2 feat(git): land the git mapping — the exec grammar + the alkgit dispatch (w3-git-mapping)
- dispatch: the full exec grammar replaces the wave-2 skeleton probe
  (quoted/unquoted paths, the .git normalization decision, unknown-
  service / service-only decline-with-reason class; the probe's unit
  rows migrate with their pinned verdicts preserved)
- git_map: the git row dispatches serve_duplex_resolved over the
  channel duplex (repo id validated, never resolved; the exit-status
  mapping 0/1 decided per POC-3 §A.5; status before close from the
  spawned task), with the resolved identity riding the seam
- handler: the store-and-take channel handoff (stored at the open,
  taken in the spawned serving task, released at close) and the
  max_exec_command_len pre-parse cap
- config course correction: GitSeam carries the assembly-supplied
  alkgit Limits (the ADR-009 budget alkgit's contract assigns the
  assembler)
- tests: tests/git_mapping.rs (byte-level process-mode advertisement,
  ls-refs round + exit 0, the collapsed-denial byte-order + exit 1,
  the identity-ride authorize-gate witness, the packless CAS push,
  the grammar/cap decline rows, the sequential-serve store row, and
  the real-tool git 2.43 clone smoke over a TCP mount with a
  skip-with-reason posture); dispatch_core/observe rows updated to
  the new decision set; tests/common/git_doubles.rs (the hand-rolled
  recording alkgit quartet)

Verification: cargo test (default), --features session, --features
git, --all-features; clippy -D warnings ×2; fmt --check;
cargo doc --no-deps; taskgraph validate
2026-10-28 20:03:10 +00:00

13 KiB
Raw Blame History

id, name, status, depends_on, scope, risk, impact, level
id name status depends_on scope risk impact level
w3-git-mapping Wave 3 — the git mapping (exec grammar + alkgit dispatch) completed
w3-decline-tracing
moderate medium phase implementation

Description

Land the git branch of the spine (ADR-006 decision 2, exec-only) — the alkforge primary purpose: the full exec grammar replaces the wave-2 skeleton probe, and the git row becomes a real dispatch to the published alkgit seam. Everything POC-3 stage A proved, re-landed as contract.

  • The full grammar (replaces dispatch::classify_exec — the wave-2 interpretation's named replacement; its 7 probe unit rows migrate): git-{upload,receive}-pack '<path>' — quoted/unquoted paths; strip the quotes and the leading / (POC-3 stage A rows: what real OpenSSH git sends); unknown services (git-send-pack, git-upload-archive) → decline-with-reason, the honest surface; service-only (no repo) → decline-with-reason — POC-3 §A: SSH exec must carry the repo; per-length limits consumed at last — Limits::max_exec_command_len enforced before any parse or allocation (decline-first, alloc-never; the wave-2 carried-not-enforced reservation). Decide the wire shapes per decline class (open/reply vocabulary, whether "unknown service" and "not git at all" share a reason shape) and record.
  • Repo path → repo_id, no existence oracle: the parse validates via alkgit::validate_repo_id (1–2 segments, charset, ≤ 100 bytes) and hands the id through — alkssh never resolves; the collapsed generic denial before any protocol byte is serve_duplex_resolved's SubstrateError::AccessDenied posture, contract-pinned here: over the SSH channel, no pack/ref byte precedes the denial band (the POC-3 §A.5 byte-order evidence, now a test row). Decide the .git-suffix normalization against the alkgit registry contract (what vanilla git sends vs validate_repo_id's grammar) and record.
  • The dispatch — the git row becomes real: the taken channel (the store-and-take handoff lands here first — the accepted channel is stored at the open, channels.remove(&id) → into_stream() in the spawned serving task; the session task reuses the store) → alkgit::serve_duplex_resolved(repo_id, identity, service, duplex, limits, registry, refs, packgen, ingest, push_options_negotiated, pipeline) — the serve-config's git seam handle (w3-seam-config), the resolved identity riding the identity param. Process-mode dialect: alkgit speaks first (the V0 advertisement, no service prefix) — nothing alkssh-side; verify the dialect holds over the channel duplex with a byte-level row.
  • Exit-status both directions (POC-3 §A.5): clean serve end → exit-status(0)... decide the code mapping (SubstrateError outcomes → the code — POC evidence: denied → the client observed ExitStatus{1}; vanilla git exits 0 on success) — sent before close, from the spawned task via the cloned Session::handle() (the wave-2 mechanics reused). Record the decision.
  • Identity — the parked wave-2 deferred item discharges here: the resolved identity (the bridge's auth_succeeded ride-along) reaches the seam — assert it live (the authorize-path/witness row the harness can observe; decide the cheapest row) so the visible-surface invariant is observable through the git row.
  • Contract rows through the harness, per build — the tests/dispatch_core.rs skeleton rows upgraded: git-only serves the git row through the doubles (clone/push semantics over the in-mem composition), declines plain exec/pty/shell (parse-then-fail, no resource alloc); session-only still declines git-parseable exec; none-build still declines at open; under --all-features the routing holds. The declined rows keep their wire shapes (channel_failure, channel usable / serving-path failure+close per class).
  • The real-tool smoke — one git clone (2.43+) through the harness against hand-rolled in-mem alkgit doubles (alkgit's Mem* are test-private — alkssh hand-rolls the minimal quartet per alkgit/src/backend.rs:164-254's noop shapes; never sibling-checkout paths, never copies of the doubles' code). Decide the tooling posture (require git in the environment vs conditional-skip — skipped-with-reason at most, never silently green) and record.

Acceptance Criteria

  • The full grammar replaces the probe (quoted/unquoted, unknown- service vs non-git decline rows, service-only decline, the max_exec_command_len pre-parse cap); the probe's unit rows migrate/extend
  • The git row dispatches to serve_duplex_resolved over the channel duplex; process-mode dialect verified byte-level; declined routes and the AccessDenied-before-protocol-bytes order pinned by contract rows
  • The store-and-take handoff lands (channel stored at open, taken in the serving task), structured so the session task reuses it
  • The exit-status mapping (clean end / terminal error) decided + recorded; status before close from the spawned task
  • The identity ride asserted live through the seam (the parked wave-2 item discharged here)
  • The decline-matrix rows per build green through the harness (git-only / session-only / none / all-features), each with its contract stamp; the real-tool clone smoke row
  • cargo test, cargo test --features git --test <git rows>, cargo test --all-features, clippy ×2 (-D warnings), fmt, cargo doc --no-deps pass

References

  • docs/architecture/mapping.md (the git channel-type row; §Session- channel dispatch — decline-first-alloc-never; §the structural constraint)
  • docs/architecture/decisions/006-mapping-bridge-and-dispatch.md (decision 2 — exec-only; decision 4 — spawn-and-return)
  • docs/architecture/decisions/004-auth-dispositions.md (the identity ride), docs/architecture/auth.md §Server direction (step 4)
  • docs/research/poc-3-mapping-bakeoff.md §Stage A (the grammar rows, the denial byte-order, the exit evidence, the spawn rule)
  • docs/research/mapping-table.md (the validated research ancestor)
  • alkgit 0.1.0: serve_duplex_resolved (src/duplex.rs:1412), SubstrateError (src/substrate.rs:116), validate_repo_id (src/registry.rs:132), the trait shapes (src/backend.rs:164-254)

Notes

To be filled at implementation time.

Decisions recorded (implementation, 2026-10-28)

  • Course correction to w3-seam-config: GitSeam gains limits: alkgit::Limits — serve_duplex_resolved requires the alkgit ADR-009 session budget, which the landed handle had no field for. alkgit's own contract shapes this ("constructed by the door/adapter from assembler config"), so the budget rides the seam (assembly-supplied, Copy), not a crate default. First seam consumer; landing it here, recorded on the field and in the config docs.
  • The grammar (dispatch.rs, the probe's rows migrated): exact serving-service token + single separating space + path-shaped argument (the wave-2 probe's pinned verdicts preserved verbatim — git-upload-pack rel and double-space arguments are not git); quoted shape is fully '-wrapped (unterminated → pipe, the probe's non-path-shape verdict); leading / stripped after quotes; one trailing .git suffix stripped (the vanilla-clone idiom; the alkgit registry grammar excludes ., so the door normalizes the idiom rather than asking registries for dotted keys; an empty remainder — /.git — is kept and the git row's validation declines it, never a silent emptying). Validation: alkgit::validate_repo_id at the git row (git_map.rs, feature-gated — the parse core stays feature-free); a shape-invalid id declines invalid-repo-id alkssh-side; a shape-valid id is handed through untouched and alkssh never resolves (resolution, authorize, and the collapsed generic denial live at the seam). Unknown services (git-send-pack, git-upload-archive) and service-only (including an empty argument) route to ExecRoute::Declined — build-independent, off the pipe row, per POC-3 §2's honest-surface verdict.
  • Wire shapes per decline class (recorded): SSH request replies carry no text, so the reason lives only in the event vocabulary; the git declinables (unknown-service / service-only / invalid-repo-id / exec-oversize) share the request-level reply shape with parse-then-fail — channel_failure, channel alive (POC-3's decline shape, POC-1 §5 posture); the reply vocabulary is the decision ("unknown service" vs "not git at all" differ in nothing observable on the wire — only the event). Serving-path declines (the row owned the channel) keep failure+close: seam-unconfigured (feature on, seam unset — the unset-declines contract; keeps the wave-2 decline-matrix rows' shape in builds without a configured seam), plus the unreachable-in-practice guards no-identity and channel-not-held. The serve's own denials are not alkssh declines: unknown/ungranted repo flows to alkgit, whose collapsed generic denial writes ERR access denied: <repo> as the first byte on the channel (pinned byte-level in tests/git_mapping.rs), then exit-status(1), then close.
  • Exit mapping (POC-3 §A.5, decided): Ok(()) → exit-status(0) (vanilla git exits 0 on success; the clean-end wire row exercises the flush-only request's Ok(None) → Ok(()) shape); every SubstrateError → exit-status(1) — AccessDenied per the POC's wire evidence, Io/Protocol the same (the service ran and failed), the machinery-misuse variants defensively 1 (they never arise from the serving entry). 255 never fabricated (OpenSSH's could-not-start semantic this door never claims). Status before close, from the spawned task via the cloned handle; a failed report send is the vocabulary's one warn (the wave-2 stub mechanics). A serve Err is not a decline event — the substrate already mapped its own error onto the wire.
  • Store-and-take handoff: SessionHandler.channels (plain std HashMap, single-writer — every mutation on the packet loop's task). Stored at the confirmed open (accept then insert); taken by the git row (remove → into_stream() in the spawned serving task); released by channel_close (the cap is released there too — one hook, both budgets). The session rows still hold until their wave-3 tasks take them. A routed exec answers channel_success inline (the POC-3 §A.5 shape — the request routed; the serving outcome reports through exit-status).
  • Identity (w2-auth-bridge's parked ride-along, discharged): the resolved identity is cloned into the serving task and passed on serve_duplex_resolved's identity param. The cheapest live witness row: alkgit's authorize evaluation — two otherwise-identical rows (private repo granted to the riding id → the advertisement flows; the same repo ungranted → the denial band + exit 1); no direct identity introspection needed, and the deny row doubles as the authorize-path witness. None can never occur post-auth and declines serving-path.
  • Push semantics through the doubles: the packless CAS path (alkgit ADR-020 §1) is the pack-free receive-pack row — commands + flush (caps NUL-attached on the first command line, report-status requested), no pack; the wire layer applies via GitRefs::apply_updates and writes the report (unpack ok / ok <ref>). The duplex discipline drains the raw tail packless — the row had to signal client-side EOF after the report flush (observed; the real client-eof shape), which is also why vanilla delete-pushes end cleanly. GitPackIngest::prepare never fires packless — asserted.
  • Per-build matrix: unchanged rows in tests/dispatch_core.rs (their observables hold — the config-driven decline keeps the failure-then-close shape in seam-less builds; unknown services keep failure-then-alive), docs/stamps updated for the new decision set; the git row's serving + grammar-decline rows live in the new tests/git_mapping.rs (empty module in non-git builds, compile-time feature isolation). tests/observe.rs's git-row event row updated to the seam-unconfigured reason.
  • Real-tool smoke posture (recorded): the clone smoke (a real git clone of an unborn-HEAD repo over a TCP-mounted adapter — the same loopback server stack, the accept loop as the assembly's mount) requires git ≥ 2.43 and ssh in the environment; missing/older tooling is skipped with a printed reason — skipped-with-reason at most, never silently green. The empty-repo clone exercises the full chain (exec parse → dispatch → the seam advertisement → ls-refs → exit-status(0)) with zero pack bytes: the unborn-HEAD line is what real git 2.43's clone consumed (POC-3's OQ-19 rider shape), so the hand-rolled doubles stay noop-faithful.
  • Verification: cargo test (default build), cargo test --features git --test git_mapping, cargo test --features session, cargo test --all-features, cargo clippy --all-targets --all- features -- -D warnings ×2 (default + all-features), cargo fmt --check, cargo doc --no-deps — all green.