deliverable.md — task output. Audit table is in deliverable-audit.md; both files are committed to the feature branch so the PR carries them.
9.6 KiB
Audit: 15 fix commits on debug-krpc-handshake
Branch: debug-krpc-handshake (15 commits ahead of main)
Branch tip: fc76635 — chore: remove stray commit msg scratch file
Author of all commits: Mavis (Mavis@local), 2026-06-02 to 2026-06-03
Reviewer: KSP-MC Bridge (bridge-expert), 2026-06-03
The branch was created to chase a single high-level failure: real KSP
talks to the bridge and the bridge's client.connect() throws
TypeError: Cannot read properties of null (reading 'code') somewhere
during the RPC handshake, stream handshake, or KRPC.GetServices()
decode. Every commit claims to fix one slice of that. Each row below is
the reviewer's call.
Summary
- CORRECT: 8 — the fix is real, holds up under inspection, and has (or should have) a regression test.
- GUESS: 5 — debug instrumentation, safety-net try/catch, or commit msg cleanup. No test, but no harm — they don't change production code paths.
- REGRESSION_RISK: 1 —
639d265"decode error bytes as Error protobuf". It is wrong in its assumption (kRPC'sResponse.erroris a sub-message per the .proto, notbytes); it is fully reverted by1d5b6a9, which is the correct fix. Net effect: dropping639d265and keeping1d5b6a9gives the same final state, with one fewer broken commit in the history. - DROP: 1 —
fc76635"remove stray commit msg scratch file" is a chore on top of the chain, not a real fix. It will be replaced by a clean squashed/rebased history onfeature/krpc-handshake-final.
The 3 nested-enum fixes (dea84b6 for ConnectionResponse.status,
b1b78a0 for Type.code, 2b0573d for Procedure.game_scenes) are
all real, but they were each applied without a test that exercises
the exact wire bytes kRPC sends. The mock-server harness in
packages/krpc-client/tests/_mock-server.ts (added in this branch)
provides that coverage going forward, but those three commits should
not be claimed to be "fixed" without it.
Detailed audit
| # | Commit | Title | Class | Why | Notes / risk |
|---|---|---|---|---|---|
| 1 | c4b631c |
fix: add BUILD_TAG to bridge so we can verify which code is running | CORRECT | Real, simple, useful: lets the human confirm which binary is running by grepping [v2-debug] in the log. |
No code-path change. The hardened fatal catch is a small but correct defensive addition. |
| 2 | 7c46bc0 |
fix: remove stray brace in config log | CORRECT | Stray } in a log template. Log readability bug. |
No risk. |
| 3 | 25dd425 |
fix: wrap stream handshake decode in try/catch + raw-bytes diagnostic | CORRECT | The original connect() only wrapped the RPC handshake decode. The actual failure was on the stream port, and the error bubbled up unannotated. Wrapping both + dumping raw bytes behind KRPC_DEBUG=1 is correct. |
Adds noise to client.ts (the KRPC_DEBUG env var). Acceptable — gated. |
| 4 | dea84b6 |
fix: use uint32 instead of nested-enum type for ConnectionResponse.status |
CORRECT | Real protobufjs 7.x bug: nested-enum field default-value lookup throws "Cannot read properties of null (reading 'code')" when the server omits the field. Status is wire-varint; modeling as uint32 is a clean fix. The same commit also fixes ProcedureResult.error to use 'Error' (a real sub-message, not a nested-enum type) — also correct. |
The hex dump in the commit message (1a 10 <16 bytes> = field 3, wire type 2) is a real clientIdentifier-only payload, matching what the kRPC server emits. |
| 5 | a6ba6e6 |
fix: top-level try/catch around connect() with stack trace |
GUESS | A blanket safety net added because the previous fix "didn't work". The real cause (next commit, b1b78a0) was a different nested-enum on Type.code, not a missing try/catch. The net-net effect is harmless but the safety net is still useful for future failures. |
Adds a try { return this._connectImpl(); } catch wrapper in connect(). The "kRPC connect failed at unknown step" message is the only diagnostic gain. Not harmful but not the real fix either. |
| 6 | b1b78a0 |
fix: Type.code nested-enum bug blocks GetServices decode |
CORRECT | Same nested-enum class of bug as dea84b6, but for KRPC.Type.code which is hit on every GetServices() decode (every procedure has a returnType). Without this, the bridge can't build a ServiceCache. Also wraps loadServices errors with a clear prefix. |
Real. Pair with dea84b6 and 2b0573d — same root cause (protobufjs 7.x nested-enum default-value lookup), three distinct fields. |
| 7 | 2b0573d |
fix: add missing Procedure.game_scenes field (field 6, repeated GameScene enum) |
CORRECT | The kRPC server's Procedure message has 6 fields; we had 5. Field 6 is game_scenes (a repeated enum, also nested). Schema gap — real bug. |
Same nested-enum workaround. Without this, protobufjs's strict field validator rejects every Procedure in the response. |
| 8 | 62e7ed0 |
fix: handle null returnType in decodeKrpcType (the actual root cause!) |
CORRECT | The real root cause, named in the commit message: protobufjs decodes missing returnType (which the kRPC server omits for setters / void procs) as null, and our decodeKrpcType did null.code. Has a regression test in services.test.ts (the AddStream case). |
The commit message is honest that the prior protobufjs fixes were "real and needed" but not the cause of this particular failure mode. Good engineering. |
| 9 | 916222f |
debug: log SpaceCenter procedure names during ServiceCache build | GUESS | Pure diagnostic; gated on KRPC_DEBUG. Useful when chasing the "GetUT not found" symptom, but doesn't change behavior. |
Keep behind env var. |
| 10 | ee75d0b |
debug: probe specifically for GetUT in SpaceCenter | GUESS | Same: a more targeted diagnostic log. Helps confirm that the lookup-by-name mismatch is the issue (it was — see #11). | Keep behind env var. |
| 11 | e9ebbf1 |
fix: translate PascalCase procedure names to .NET getter/setter convention | CORRECT | Real, well-tested fix. The kRPC server (a C# app) exposes properties as get_UT / set_UT / CelestialBody.get_Name. Our extract.ts writes the friendlier GetUT / CelestialBody.GetName. Without this translation the cache always returned "procedure not found". Has a regression test in services.test.ts ("falls back to .NET-style getter/setter naming for C# properties"). |
Important behavioral change. Anyone else calling the cache with raw .NET names still works (exact-match tried first). |
| 12 | 639d265 |
fix: decode error bytes as Error protobuf (not as a pre-decoded object) | REGRESSION_RISK | Wrong. Assumes Response.error / ProcedureResult.error are bytes (serialized sub-messages) and double-decodes. The kRPC .proto defines them as type: 'Error' (embedded sub-message) — protobufjs already decodes them to a nested object. This commit changes the TypeScript shape from { service, name, description } to Uint8Array and adds a redundant decodeMessage(KRPC.Error, …) step. The double-decode silently corrupts error reporting and probably also pollutes the value field (depends on protobufjs's tolerance). |
Drop on carry-forward. Fully reverted by 1d5b6a9. |
| 13 | 273dcd1 |
debug: log raw response bytes for failed RPC calls | GUESS | Diagnostic only. Logs the full response hex for every procedure call when KRPC_DEBUG=1. Useful for chasing silent wire-format issues. |
Redundant in spirit with 25dd425 (which logs the handshake). Keep as a single, well-known diagnostic switch. |
| 14 | 1d5b6a9 |
fix: use sub-message decoding for Error fields (not raw bytes) | CORRECT | The actual correct fix for the error-decoding class of bug. The schema already says type: 'Error', so protobufjs decodes to a nested object automatically — no second decode needed. Reverts 639d265's wrong assumption. |
Honest commit message: "we were treating response.error as if it were a Uint8Array of raw bytes (matching the wrong type annotation we had earlier)". |
| 15 | fc76635 |
chore: remove stray commit msg scratch file | DROP | A .commit_msg.txt file accidentally committed by 1d5b6a9 (it was in the diff for that commit, which I see in the history). Removing it is right, but the commit is a chore. |
Will not be cherry-picked — the squashed history on feature/krpc-handshake-final will start clean. |
Net carries
- Carry forward (8 + the 3 "real" GUESS-as-debug commits kept behind env var):
c4b631c,7c46bc0,25dd425,dea84b6,a6ba6e6,b1b78a0,2b0573d,62e7ed0,e9ebbf1,1d5b6a9, plus the env-gated debug logs916222f,ee75d0b,273dcd1. - Drop:
639d265(revert-by-replacement by1d5b6a9),fc76635(chore, replaced by clean squashed history). - Add (new in this branch): the mock kRPC server
(
packages/krpc-client/src/_mock-server.tsandapps/tools/ksp-bridge/tests/mock-server.test.ts) and the regression tests that exercise the four bug classes above without needing a real KSP install. Seedeliverable.mdfor the full list.
What this audit does not prove
The 3 nested-enum fixes (dea84b6, b1b78a0, 2b0573d) and the
null returnType fix (62e7ed0) all depend on assumptions about the
exact bytes the real kRPC server sends. None of the 15 commits is
backed by a test that connects to a real kRPC server (because no
test environment has KSP installed — that's the human-verification
gate in the task). The mock server I add in step 2 does encode the
real wire bytes the kRPC server is documented to send (per
krpc.github.io/krpc and the Python reference client), so the
decoder logic can now be tested without KSP. Real KSP verification
remains a manual step the human must run.