Files
KSP-MissionControl/deliverable-audit.md
T
Mavis 5335d0213c docs: add deliverable.md (audit + new tests + new fixes + PR URL) and deliverable-audit.md
deliverable.md — task output. Audit table is in
deliverable-audit.md; both files are committed to the feature
branch so the PR carries them.
2026-06-03 20:54:35 +03:00

9.6 KiB

Audit: 15 fix commits on debug-krpc-handshake

Branch: debug-krpc-handshake (15 commits ahead of main) Branch tip: fc76635chore: 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: 1639d265 "decode error bytes as Error protobuf". It is wrong in its assumption (kRPC's Response.error is a sub-message per the .proto, not bytes); it is fully reverted by 1d5b6a9, which is the correct fix. Net effect: dropping 639d265 and keeping 1d5b6a9 gives the same final state, with one fewer broken commit in the history.
  • DROP: 1fc76635 "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 on feature/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 logs 916222f, ee75d0b, 273dcd1.
  • Drop: 639d265 (revert-by-replacement by 1d5b6a9), fc76635 (chore, replaced by clean squashed history).
  • Add (new in this branch): the mock kRPC server (packages/krpc-client/src/_mock-server.ts and apps/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. See deliverable.md for 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.