A new invoice-PDF feature using the legacy document-get endpoint. The endpoint has been marked `@deprecated since v2.4` and is on the removal list for next quarter. Mesrai flagged it.
The vulnerable diff
// services/invoice-pdf.ts
import { api } from "../client";
export async function generateInvoicePDF(invoiceId: string) {
// BUG: getDocument is @deprecated. Should use api.documents.get
const doc = await api.legacy.getDocument(invoiceId);
return renderPDF(doc);
}
// api/client.ts (the legacy method definition):
/**
* @deprecated since v2.4 — use api.documents.get instead.
* Will be removed in v3.0 (target: 2027-01).
*/
export const getDocument = (id: string) => fetch(`/api/legacy/documents/${id}`);What is wrong
Deprecation works only if new code avoids deprecated APIs. Most JSDoc-based deprecations rely on IDE warnings that developers ignore or don't see. Without enforcement, the deprecation timeline keeps slipping because new callers keep appearing. The discipline is: every PR review checks for new uses of deprecated APIs, and the deprecation owner blocks PRs that add them.
The attack
Symptom:
# Three quarters after deprecation:
$ grep -r "api.legacy.getDocument" src/ | wc -l
> 24 callers (added across 18 PRs since deprecation announced)
# Removal blocked indefinitely.Compound cost — every new caller widens the migration the team will eventually have to do.
Mesrai's review comment
mesraipilot · Bot · reviewed 1 min ago
[mesrai] [code-review] [Logic] [Deprecated-API] [medium]
`api.legacy.getDocument` is marked @deprecated since v2.4 and is
on the removal list for v3.0 (2027-01). This PR adds a new caller.
Use the replacement:
await api.documents.get(invoiceId);
If the replacement is missing functionality the legacy method
provides, flag the gap explicitly so the deprecation owner can
prioritize parity work — don't paper over by adding new callers.
To enforce: enable the TypeScript `@typescript-eslint/no-deprecated`
rule (recently stable). Mesrai will flag new uses regardless.The fix
// services/invoice-pdf.ts — fixed
import { api } from "../client";
export async function generateInvoicePDF(invoiceId: string) {
const doc = await api.documents.get(invoiceId);
return renderPDF(doc);
}Use the canonical replacement. If feature parity is missing, file an issue rather than adding a deprecated caller.
Why human review missed it
Deprecation hygiene is hard to enforce without tooling. IDE warnings are ignored; lint rules are partial. Mesrai catches every new caller of an `@deprecated`-tagged API at PR time.
Related rules + further reading
Mesrai rule pack: logic/no-new-deprecated-callers — flags PRs that add new uses of @deprecated symbols.
TS-ESLint: @typescript-eslint/no-deprecated.
Common deprecation-debt source in long-lived codebases.
Takeaway
Deprecated means: no new callers. Mesrai blocks new ones at PR time, deprecation owner can plan removal.