Skip to main content

Registry Gates

NONE of these lanes is reached by CI today (measured 2026-09-07)

Everything on this page describes intended behaviour. It is not what happens. Measured against the Azure DevOps API on 2026-09-07: all 30 pipeline definitions in project KeyNova were enumerated with the YAML file each one points at, and not one points anywhere under .azuredevops/pipelines/; the hmp-docs repository carries zero branch policies. The identical policy call returns 4 configurations for hmp-infrastructure, so that zero is a measured zero and not an empty response or a permissions artefact.

A pipeline YAML is an inert file until a pipeline definition points at it, and — for an Azure Repos Git repo — a pr: block is not a trigger: pull-request validation is a Build branch policy. Both limbs are missing here, so no check below starts by itself, on a diff or on a schedule. Run them by hand:

npx --prefix tools/registry tsx tools/registry/src/cli.ts check-decision-register
npx --prefix tools/registry tsx tools/registry/src/cli.ts check-lane-registration

The measurement, the exact API calls that produced it, and the two operator steps that would make a lane real are recorded in .azuredevops/pipelines/lane-registration.json (AB#13541). This page was itself the last thing still saying otherwise — see the note at the bottom.

The Platform Registry's checks are written in two modes. Both descriptions are about what happens inside a run, which is a separate question from whether anything starts the run — see the warning above.

  • Blocking gates — the step fails the run: no continueOnError, no || true.
  • Report-only checks — post a comment, never fail the run.

Promotion history​

The program's roadmap (SP-6) promotes gates one at a time from report-only → blocking, each with a 1-week bake period where the gate runs but failures are only a warning.

Status here is the step's semantics inside a run, not its reach. A gate marked BLOCKING has no continueOnError and no || true — it fails whatever run executes it. Since 2026-09-07 we know that nothing executes it: see the warning at the top, and the "Actual reach today" column below. The Since dates record when the step was written that way, not when anything started enforcing it.

GateStatus (in-run)Written that way sinceBake result
validate-yamlBLOCKING2026-04-24Bake skipped — SP-1 already had this fail-loud on path-matching PRs; SP-6 widened trigger to all PRs.
check-cross-linksBLOCKING2026-04-24Bake skipped — same rationale.
no-prod-mocks (AP-CC-4)BLOCKING2026-04-28Bake skipped — gate is a single grep over two compose files; false-positive risk is zero. Spec: _data/gates/no-prod-mocks.yaml.
drift-checkreport-only—Will promote after cycle-1 stabilises (est. 4-6 weeks). Only missing-with-refs (high severity) findings will block.
coverage-checkreport-only—Will not promote until coverage bakes below ~500 unclaimed (currently 1251).
detect-unused-exportsreport-only—Too noisy to block today. Candidate after re-export + Next.js fixes settle.
detect-dead-routesreport-only—Candidate after 2-3 cycles of triage.

Current pipelines​

Reach column measured 2026-09-07 and recorded in .azuredevops/pipelines/lane-registration.json. Written is what the YAML says; actual is what Azure DevOps does with it.

LaneWritten to doActual reach today
.azuredevops/pipelines/registry-validation-gate.ymlfail the run on validate-yaml + check-cross-links (and, since AB#13541, check-decision-register + check-lane-registration) for any diff to masternone — UNREGISTERED, no definition points at it, no branch policy names it
.azuredevops/pipelines/registry-pr-comment.ymlpost a drift + coverage comment when a diff touches registry/tooling/docs pathsnone — UNREGISTERED; no comment has ever been posted from it
.azuredevops/pipelines/registry-nightly.ymlrefresh scanner outputs + write findings at 18:00 UTC, never failingnone — UNREGISTERED, so the cron has never fired from this file
.azuredevops/pipelines/story-contract-gate.ymlcheck-story-contract on a daily 17:00 UTC schedule (AB#13555)none — UNREGISTERED
azure-pipelines.ymldocusaurus build + secret scanners + publish to S3REGISTERED as definition 383, but trigger: none / pr: none — it runs on a manual az pipelines run and executes no registry gate

An unregistered scheduled lane is worth its own sentence, because it is the trap that reads best: it is silent, not red. In a status table "no failures from the nightly" and "the nightly has never run" look identical.

Roll-back protocol​

If a promoted gate causes 3+ false-positive blocks in a week:

  1. Edit the corresponding pipeline YAML, change the blocking step to continueOnError: true or wrap in set +e so it becomes a warning.
  2. Open a Finding in registry/findings/ documenting the rollback + why.
  3. Investigate the false-positive pattern. Fix in a follow-up PR.
  4. Re-promote in the next sprint.

Never skip a gate with --no-verify at commit time — that bypasses the entire policy and leaves the registry silently broken.

Bake process (for future gates)​

  1. Week 0 — add the check as continueOnError: true. Post a PR comment noting failures but don't block.
  2. Week 1 — audit the week's failures. If 0-2 were false positives, proceed. If 3+, investigate root cause, fix, return to Week 0.
  3. Week 2 — flip continueOnError: false. Gate is now blocking.
  4. Week 2+1 — audit first week of blocking. If any real dev work was blocked unfairly, consider reverting to Week 0.

Why this page was wrong until 2026-09-07 (AB#13541)​

Recorded here rather than quietly corrected, because the shape of the mistake matters more than the sentence.

QA-42 built check-lane-registration for exactly this: a gate that fails on any live document claiming a CI reach the measured ledger does not back. It found and corrected 17 such claims across 8 files, reported that none remained, and was believed complete.

It was not. Its claim-surface glob enumerated the file types it expected to find — docs/platform-registry/**/*.md among them — and this page is .mdx. Docusaurus publishes .md and .mdx identically, so the one claim site actually rendered to readers on docs.keynova.ai was the one the gate could not see. The gate was green because it was not looking.

Re-measured with the corrected scanner against the same commit (f34ac53), the real population was 19 unbacked claims in 9 files, not 17 in 8. The two it had no way to reach were both on this page. So the count that was reported as complete was a lower bound produced by an instrument nobody had checked — which is the failure mode one level up from the one this card was opened for.

Two things changed as a result, and the second is the one that generalises:

  1. The sentence was corrected — that is the whole of the top warning and the "Actual reach today" column above.
  2. The scanner no longer enumerates its surface. It reads every file in the checkout, decides text-versus-binary by reading the bytes rather than by extension, and carries a short ignore list where every entry has a written reason. An omission from an ignore list is a reviewable act; an omission from an allowlist is invisible until someone measures. A regression test places the claim in an .mdx file specifically, and a coverage invariant walks the checkout independently of the linter's own globs and asserts that no claim-bearing file falls outside what the linter scans.

The lesson is not "remember .mdx". It is that a gate defined by what it expects to find will always be one new file format behind the thing it guards.