Skip to content
Skip to main content
Novel Systems home
Decision log
D-019August 3, 2026Accepted

tsx strips types; it does not check them

Decided

flip the tamper byte with writeUInt8/readUInt8 rather than a compound index assignment, and reorder backend's verify so that environment-dependent steps run last.

Affects: backend/package.json, backend/test/integrations.test.ts Fixes a defect introduced by D-018's own fix.

CI run 30873274486 failed the API job with exit code 2 and a single error: Object is possibly 'undefined' at integrations.test.ts:252. The line was bytes[0] ^= 0xff — the byte-level flip introduced one commit earlier as the fix for D-018. This project compiles with noUncheckedIndexedAccess, under which an index read is number | undefined, and undefined ^= 0xff does not typecheck.

The interesting part is not the error, it is why it reached CI. The test suite runs under node --test --import tsx. tsx strips type annotations; it does not check them. A test file can therefore pass npm test cleanly and still be rejected by tsc --noEmit. Locally the suite was green, 331 of 331, at the moment CI went red. Two commands that feel like they answer the same question do not.

The command that *would* have caught it — npm run verify, which begins with typecheck — existed and was not being run, for a reason worth recording. prisma:validate sat second in the chain, and Prisma fetches its query-engine binary at run time. In a network-restricted sandbox that fetch returns 403, so the whole script aborted on step two and never reached anything downstream of it, including the tests. A verification script that hard-fails in the only environment available to run it in is a verification script that stops being run, and then the compile check it was carrying stops happening too.

So the chain is now typecheck && openapi:check && test:coverage && prisma:validate: cheap and hermetic first, network-dependent last. Nothing was removed — prisma:validate still runs, and still runs in CI where the network is open and its signal is real. It simply no longer stands between the developer and every other check.

The alternative considered and rejected: bytes[0]!, a non-null assertion. Rejected because it silences the compiler on precisely the case worth keeping — a zero-length field, which would mean the envelope never had the shape this test claims to be tampering with. readUInt8(0) throws there, loudly and in the right place; ! would XOR undefined and carry on.

Also rejected: dropping prisma:validate from verify entirely to make the script portable. It catches genuine schema errors, and the environment that cannot run it is the sandbox, not CI. Reordering keeps the check and removes the obstacle; deleting it would have removed both.

This would be wrong if the sandbox gained access to Prisma's binary host, at which point the ordering stops mattering for portability. It would still be the right order — hermetic checks before networked ones is how a chain should be built regardless of whether any link currently fails.