docs(tasks): decompose wave 3 — the server mappings

Six implementation tasks + the gate under tasks/implementation/,
chained sequentially (the mappings replace stubs in the same Handler
impl and share the decline-matrix test binaries):

- w3-seam-config: the serve-config's per-feature seam handles
  (alktty backends/ownership/selector; the alkgit quartet/pipeline/
  push-options flag) + the optional sibling deps — wave 3's first
  course correction to the wave-1 config surface
- w3-decline-tracing: the wave-2 gate's booked finding — the
  decline/serve-path tracing vocabulary, landed before the mappings
- w3-git-mapping: the full exec grammar replaces the wave-2 probe +
  the serve_duplex_resolved dispatch + the store-and-take handoff
- w3-tty-session: the translation bridge + the exit-derivation
  contract (rules 1-6, the signum table)
- w3-tunnel-direct: the route-1 bridge (accept only after the fabric
  open succeeds) + set_identity propagation + the capacity-across-
  fabric-window mechanics
- w3-tunnel-listen: the -R listen flow + the forwarded-tcpip data
  leg (the POC-untested leg; the stop-and-book provision)
- review-wave-3: the gate

The plan carries the wave-3 decomposition record with four
interpretations flagged for the gate (the serve-config course
correction, the already-discharged server-side sftp bullet, the
w3-tunnel-listen open mechanics, the booked-items' wave-3 homes).
taskgraph validate: 26 tasks, clean.

Sibling pins verified against crates.io at decomposition time:
alktty 0.8.1 / alkgit 0.1.0 / alktunnels 0.4.1 / alkcall 0.10.1.
This commit is contained in:
glm-5.3-flash committed 2026-10-28 12:32:13 +00:00
1 parent 5b2654f492
commit d1f755f2d9
8 files changed
+849 -1

No files matched your search

+124
View File
@@ -0,0 +1,124 @@
---
id: w3-git-mapping
name: Wave 3 — the git mapping (exec grammar + alkgit dispatch)
status: pending
depends_on: [w3-decline-tracing]
scope: moderate
risk: medium
impact: phase
level: 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.