Skip to content

Generate unique check IDs and detail routes#16

Merged
maquchizi merged 7 commits into
mainfrom
generate-unique-check-ids-and-detail-routes
Jul 16, 2026
Merged

Generate unique check IDs and detail routes#16
maquchizi merged 7 commits into
mainfrom
generate-unique-check-ids-and-detail-routes

Conversation

@maquchizi

Copy link
Copy Markdown
Collaborator

What & why

Public check IDs were derived from slugify(job) alone, but Grafana defines a check's identity as job name + target (documented here: "all checks include the Job name and Target options, which together uniquely identify the check"). As a result, two checks sharing a job name with different targets - or two job names that normalize to the same slug (e.g. Public API and Public-API) - collided onto one ID. That single ID was then used as the React list key and the /site/<id> link on the overview, and as the lookup key in getSiteHistory() (which returns the first match), so colliding checks got duplicate keys/links and at least one had no independently addressable detail page.

This change gives every check a unique, deterministic ID: `<job-slug>-<hash>`, where the hash is a stable digest (FNV-1a) of the full (job, target) identity. Because the hash depends only on the check's own identity, an ID never changes when some other check is added, removed, or renamed - it only changes if that check's own job or target changes. The readable slug leads so /site/<id> URLs stay scannable and sort/autocomplete by service name.

Closes #10

Type of change

  • Bug fix
  • New feature
  • Documentation
  • Refactor / chore

Checklist

  • I read AGENTS.md and CONTRIBUTING.md
  • pnpm check passes
  • pnpm test passes
  • pnpm test:types passes
  • pnpm build succeeds
  • Added/updated tests for new logic
  • Updated docs (README / docs/) if behaviour or configuration changed
  • No secrets committed and no Grafana credential given a NEXT_PUBLIC_ prefix

Notes for reviewers

Breaking: all /site/<id> URLs change. Every ID gains a hash suffix (pesacheck-orgpesacheck-org-1a2b3c), so existing bookmarks/deep links will 404 and must be regenerated. This was a deliberate trade - always-appending gives per-check ID stability that never depends on sibling checks, at the cost of URL compatibility. The alternative (append the hash only on collision, keep bare slugs otherwise) preserves non-colliding URLs but lets a check's URL flip the moment a colliding sibling appears. Migration note is documented in docs/architecture.md. In production the change lands after the ISR REVALIDATE_SECONDS window (or a redeploy); in local dev you may need to clear .next/cache since the pre-change overview is cached.

Where to look:

  • lib/format.ts - new checkId(job, target) (pure, always-append) plus shared checkIdentity / fnv1a helpers.
  • lib/synthetics.ts - listChecks() assigns checkId per deduped (job, instance); a duplicated local key() helper was removed in favour of checkIdentity.
  • lib/mock.ts - derives IDs via the same checkId; adds Public API / Public-API fixtures that collide after slug normalization, so the mock (and therefore the credential-free e2e run) exercises disambiguation end to end. The fixture RNG is now seeded on the stable (job, target) identity rather than the public ID.

Testing: unit tests cover same-job/different-target and slug-normalization collisions (lib/format.test.ts, lib/synthetics.test.ts, lib/mock.test.ts); e2e asserts colliding rows get distinct, working links (e2e/home.spec.ts) and resolves detail URLs from the overview rather than hardcoding slugs (e2e/site.spec.ts). Verified against live Grafana data: 49 services, 49 unique IDs, zero duplicate links.

@vercel

vercel Bot commented Jul 15, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
probeview Ready Ready Preview, Comment Jul 16, 2026 7:45am

Request Review

@maquchizi maquchizi changed the title Generate unique check ids and detail routes Generate unique check IDs and detail routes Jul 15, 2026
@maquchizi
maquchizi requested a review from a team July 15, 2026 13:29
@kilemensi

Copy link
Copy Markdown
Member

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12b82f623f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/format.ts
* leads so URLs stay scannable and sort/autocomplete by service name.
*/
export function checkId(job: string, instance: string): string {
return `${baseCheckId(job, instance)}-${shortHash(checkIdentity(job, instance))}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use collision-resistant route IDs

When an installation has many checks sharing the same job slug (for example one job name monitoring many targets) or a crafted pair of targets, this 32-bit FNV suffix can collide, producing duplicate /site/<id> links and React keys; getSiteHistory() then resolves the first matching check, leaving the other target without an addressable detail page. Use a longer/collision-resistant digest or include an encoded target component so the public ID is actually unique for the (job, target) pair.

Useful? React with 👍 / 👎.

@maquchizi maquchizi Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the chance of a collision at 32-bit is "highly unlikely", moving to 64 bit makes it "practically impossible" at the cost of a longer (uglier?) hash. I think that's a fair trade off so I've made the change.

  * We use (job, target) to generate a unique, deterministic ID for each
    check that we use in the URL and as an ID in the frontend
  * I considered only appending the ID to the URL in cases where there
    were collisions but that ended up being more unstable and generating
    edge cases like: What happens when there was no collision then
    someone adds a check that collides with another? To which check do
    we append the ID? What happens when the collision goes away? Do we
    drop the ID? This is not good for deeplinking and I went with
    stability and functionality over beautiful URLs here.
  * Also included a migration note as this change will break previously
    existing URLs
  * Because we need to use Bigint for the 64 bit hash
@maquchizi
maquchizi force-pushed the generate-unique-check-ids-and-detail-routes branch from e64d245 to e029b5a Compare July 15, 2026 16:44

@kilemensi kilemensi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!


Not sure if these URLs should show up as page title though... I assumed the title would be some combination of name, group, org for the URL.

Image

* Use the job and target to build the page title. Dedup them if they're
  the same
* Fallback to raw ID if construction fails for any reason
* Also added some unit tests to validate this behaviour
@maquchizi

Copy link
Copy Markdown
Collaborator Author

@kilemensi That's what happens when you test on a browser with a thousand tabs open, you never get to see page titles.

Good catch, I've fixed that.

@maquchizi
maquchizi merged commit 78128e7 into main Jul 16, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Generate unique check IDs and detail routes

2 participants