5335d0213c
deliverable.md — task output. Audit table is in deliverable-audit.md; both files are committed to the feature branch so the PR carries them.
85 lines
9.6 KiB
Markdown
85 lines
9.6 KiB
Markdown
# 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'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: 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 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.
|