Skip to main content

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. az, 09, 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.html or /deploy-gate.md.
  • A trailing slash on a leaf page. The canonical form is /rules/pg-l2-index-not-concurrent/, and the portal answers 404 without 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 $id under this host — gets none: a slash after .json would 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 200 are 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.