gdp-ts makes authorization proofs compile-time requirements
The gdp-ts library, linter, and AI skill require typed proofs that authorization checks ran before sensitive calls. Its rules also flag forged proofs and unsafe escape hatches.

TL;DR
- gdp-ts makes sensitive functions uncallable without typed authorization evidence, a contract rauchg's announcement presents for human and agent-written TypeScript.
- The mechanism gives runtime values compile-time identities and requires proofs about those exact values, as ctatedev's summary breaks down.
- TypeScript assertions can bypass the type checker, so the package adds lint rules against forged proofs and proof constructors outside
proofs/, a risk rauchg's reply calls out withas any. - The project frames cheaper code generation as a reason to spend more on deterministic verification, an argument cramforce's post makes in the release discussion.
The gdp-ts README uses a Vercel-like password-protection API to show how the contract works in real code. Ultracite's gdp preset documentation adds the lint layer that catches the escape hatches the type checker cannot see.
Proofs at the call site
Conventional authorization often leaves the check and the sensitive operation as separate calls. gdp-ts changes the operation's signature so the caller must present evidence that the relevant check passed, according to the gdp-ts README.
The example changes a function resembling setPasswordProtection(projectId, password) into one that accepts a named project, the password, and proofs such as UserIsProjectAdmin and PlanIncludesPasswordProtection. A missing proof, a proof for another project, or a raw ID becomes a type error.
The result is a contract enforced where the mutation happens. The README's model lets a route, Server Action, or job call the data layer without moving the authorization check into every caller.
The announcement record also contains a separate short reply from rauchg's reply that reads only “@maria_rcks Jan ’27”; the implementation detail is in the repository rather than that reply.
Named values bind the proof
The README describes three mechanics for connecting a check to the exact operation it authorizes:
name(x, k)assigns a compile-time-only identity to a runtime value. Two strings can both beProjectId, while the type checker still treats them as different named projects.- A trusted module in
proofs/runs the database or policy check and returns a typedProof, ornull. Its proof type carries the identities of the user and project it checked. - The sensitive function demands that proof in its parameter list, so a proof about another named value does not satisfy the signature.
The proof object is deliberately not a second authorization system. The check still runs in the trusted module, while the type checker verifies that its result reaches the function that needs it. The gdp-ts README describes this as a way to layer relationship proofs on top of ordinary branded IDs.
The linter closes escape hatches
TypeScript's type checker cannot distinguish an honest proof from a forged assertion such as {} as UserIsProjectAdmin<U, P>. gdp-ts therefore ships ESLint and Oxlint presets that add checks around the type system.
The lint rules cover three concrete bypasses:
- Forging a proof with a type assertion.
- Calling
defineProofoutside the trustedproofs/directory. - Treating the proof parameter as an ordinary unused-variable error, even though the sensitive function intentionally never reads it at runtime.
Ultracite exposes the preset as ultracite/eslint/gdp or its Oxlint equivalent. Its setup spreads the preset after the core configuration, and Ultracite's documentation says the plugin is already shipped inside @gdp-ts/core.
Ghosts at runtime
The proofs are “ghosts” in the runtime sense. The README says they are frozen objects with a small runtime representation, while the safety property comes from type checking and the only application-level cost is a tiny wrapper around checked IDs.
The repository reports roughly 0.3 milliseconds per authorized call site on a cold TypeScript 7 check. It also describes the cost as growing with the number of authorized call sites rather than the overall size of the codebase.
The package does not make the authorization decision itself. A proof function can call a policy engine or plain SQL, then return a proof only for the values it checked. The README explicitly lists forged proofs, stale proofs, and other limits of TypeScript among what the pattern does not guarantee.
Deterministic checks
The release lands in a broader argument about what software teams do when code generation becomes cheap. cramforce described the shift as becoming “verification engineers” and connected the proof pattern to reducing IDOR bugs on a platform cramforce's post.
unclebobmartin argued that critical, load-bearing code may still deserve line-by-line scrutiny unclebobmartin's reply. In a follow-up, he described a workflow that disengages from syntax and surrounds AI output with deterministic measures of software quality and test coverage unclebobmartin's follow-up.
Those comments put gdp-ts in a narrower category than a general style rule. It checks one security invariant at the boundary where an operation consumes the authorization result.
The AI skill and package
Installation has two parts: npx skills add rauchg/gdp-ts installs the agent-facing skill, and pnpm add @gdp-ts/core installs the library and linter. The package requires TypeScript 5.4 or newer, according to the gdp-ts README.
The skill is plain Markdown, so it also serves as a human reference manual. Its six-step recipe is:
- Brand IDs.
- Put one trusted module for each fact in
proofs/. - Represent policies as unions.
- Make sensitive functions demand proofs.
- Name and prove values in the handler.
- Enable the lint preset.
The repository includes three progressively realistic examples: basic uses in-memory data, express-basic adds Express and the ESLint preset, and express-drizzle uses Drizzle with in-process Postgres, SQL-backed proofs, HTTP tests, and the Oxlint preset on TypeScript 7.