docs: typed-output direction set — russh-sftp shape, (a) vs (b) fork

- branch 1 output contract targets typed Rust structs (no Value
  materialization): the kernel-fusion move, alkminer precedent
- russh-sftp protocol-type shape as target (plain structs, field
  order = decode order, shared cursor codec core)
- fork: (a) serde-derive + generic Deserializer (interop) vs
  (b) generated direct codecs (zero overhead, the 72ns shape)
- follow-up #1 sharpened: typed-output POC renders Packet both ways,
  benchmarks against the raw-decode baseline
This commit is contained in:
glm-5.3-flash committed 2026-09-09 07:23:56 +00:00
1 parent b86dadcf77
commit a52c8a6b37
1 file changed
+47 -4
+47 -4
View File
@@ -71,6 +71,47 @@ Interpretation:
is where the 20x-scale gains live; that is an output-contract
decision for Phase 1 (see §Follow-ups).
**Typed output direction set 2026-09-08 (POC debrief): branch 1's
output contract should be typed Rust structs, in the russh-sftp
protocol-type shape.** The decomposition above made the `Value`
contract's ceiling visible; the next target is to generate the
hand-rolled shape itself — helpers + templates producing direct
wire codecs over typed structs, no `Value` anywhere. Conceptually
the alkminer-v1 kernel-fusion move: the schema compiles to native
code instead of driving a runtime walk.
Target shape (`/workspace/russh-sftp/src/protocol/*.rs` precedent):
per-type plain structs with named fields (`pub struct Close { pub
id: u32, pub handle: String }` + macro impls), field order = decode
order, one shared cursor-based codec core. BAST kinds map to Rust
types cleanly (`uint32`→`u32`, `string`→`String`, struct→struct,
TUnion→enum). russh-sftp rides serde derive with a generic wire
`Deserializer`; BAST's per-field endianness and TUnion byte-
discriminators don't fit serde's derive directly, so the typed path
forks:
- **(a) serde-derive architecture** (russh-sftp's): one generic
`Deserializer` + per-type `Deserialize` impls — derived where the
wire is homogeneous; generated impls (or endian newtype wrappers)
where it isn't; TUnion → generated enum with a custom impl. Pro:
ecosystem interop (`serde` consumers, `de::from_bytes::<T>`
ergonomics). Con: serde machinery overhead vs direct code; the
endian/union cases get wrapper-heavy.
- **(b) generated direct codecs**: `fn decode(buf: &mut Cursor)
-> Result<Packet, GenError>` per type — literally the hand-rolled
72 ns shape, template-generated. Pro: zero overhead, full
control; the POC's error-returning cursor helpers carry over
directly. Con: serde interop for typed output needs a bridge (or
a later feature generating impls).
Either way, the benchmark target is the raw-decode baseline (72 ns):
typed generated decode should land within striking distance,
recovering the ~20x claim at the typed contract. This is branch 1's
output-contract ADR (Phase 1's biggest), now with a concrete target
shape and two implementation strategies. A typed-output POC
(follow-up #1) can resolve (a) vs (b) cheaply by rendering the same
Packet schema both ways and re-benchmarking.
## Parity semantics the generated code must reproduce
Discovered by exercising the interpreter, all pinned in the parity
@@ -206,10 +247,12 @@ a design constraint.
## Follow-ups (Phase 1 input)
1. **Output contract ADR** — `Value`-materializing vs typed-struct
output. The POC + benchmark shows Value-shape is mature (near
hand-rolled parity) and typed output is where the 10–20x lives.
This is the biggest Phase 1 ADR.
1. **Output contract ADR** — typed-struct output (russh-sftp shape,
§"Typed output direction") vs `Value`-materializing. The typed
direction is set; the ADR decides the serde-derive (a) vs direct
codec (b) fork. A typed-output POC resolves it by rendering the
Packet schema both ways and re-benchmarking against the 72 ns
raw-decode baseline.
2. **Typed error shape for generated code** (F-2) — mirror
`AlkTypeError` variants without the alktype dep, or a
generated-code-local enum.