Documentation URL conventions
A documentation URL is a promise. From the first rule on, every finding SQLens reports can carry a link to the page that explains it, and a link that a shipped finding already carries can never be changed without breaking that finding in the field. So the shape of these URLs is fixed here, before the first page is written, as a rule rather than a list of examples.
The rule
Every documentation page has exactly one canonical URL, built this way:
- Lowercase.
a–z,0–9, and hyphens only. - Hyphen-separated. Words in a path segment are joined with
-, never underscores, spaces, or camelCase. - No file extension.
/deploy-gate, never/deploy-gate.htmlor/deploy-gate.md. - A trailing slash on a leaf page. The canonical form is
/rules/pg-l2-index-not-concurrent/, and the portal answers404without it — measured against the live site, not assumed. The portal's own navigation links the slash-less form and gets away with it because client-side routing never asks the server; a link this package hands a user is followed directly, and dies. A path naming a file — the bundled JSON schemas carry an$idunder this host — gets none: a slash after.jsonwould name a directory that does not exist. - No language segment in a technical path. The documentation is English; a path
is
/rules/..., never/en/rules/.... Localized content, if it ever exists, is negotiated by content, not baked into the canonical path. - One canonical URL per page. Where more than one address could reach a page
(a moved page, an alternate spelling), every non-canonical address redirects
to the canonical one. Duplicates that both return
200are not allowed — they split search ranking and make "the" URL ambiguous.
The rule id, and the address it maps to
A rule id has three parts, separated by dots:
PG.L2.INDEX_NOT_CONCURRENT
│ │ └─ the name, upper snake case
│ └──── the strictness level the rule runs at, L0 through L9
└─────── the area
The area is the engine for a rule about one — PG, MY — or the family for everything
else: GEN for a rule that applies to both engines, SEC for security and privacy, CAP
for the capture layer, LINT / AUDIT / DEPLOY / DEBT for a suite's own run notices.
A security id carries its area in the middle instead of a level, because the level gate
never measures it — SEC.AUTH.HBA_TRUST, SEC.RLS.NOT_FORCED. That is the same split the
strictness levels page describes: two axes, and an id says which one
it answers to.
The address is derived, never written by hand
The documentation URL is computed from the id, so a rule and its page cannot drift:
lowercase the id, then replace every `.` and `_` with `-`
PG.L2.INDEX_NOT_CONCURRENT → /rules/pg-l2-index-not-concurrent/
SEC.AUTH.HBA_TRUST → /rules/sec-auth-hba-trust/
That address is what every finding carries and what a shipped baseline entry outlives, which is why the shape is fixed here rather than left to a convention somebody remembers. It obeys the rule above by construction: lowercase, hyphenated, extensionless, one canonical address per rule.