ADR 0001: Selective enforcement (select/report.select) and dashboard push (report.to)¶
Status: Accepted, implemented (internal/config, cmd/vlotpipe/main.go, internal/pushreport).
Context¶
vlotpipe check's exit code was, until this decision, all-or-nothing:
any blocker-severity finding, from any rule, failed the build. That's
fine for a repo adopting vlotpipe from a clean slate. It's a real
barrier for an existing repo with debt — turning check into a
required status check would fail every open PR on day one, which is
usually enough to get the whole idea reverted before it gets a fair
trial. ESLint's per-rule error/warn split and ruff's own --select
solve this the same way: let a team narrow which rules actually gate,
independent of what's still visible.
Separately, there's a standing product direction — the sibling
vlotpipe-dashboard prototype — for a hosted layer aggregating findings
across a team's repos and branches. That prototype only ever pulls
data (collect runs vlotpipe scan + the GitHub API locally and emits
one JSON file); its own README names "no push/webhook ingestion" as a
known gap. Closing that gap needed a client-side contract to build
against.
Decision¶
Three independent, optional .vlotpipe.yml settings:
select(top-level list) — which rule codes/prefixes can failcheck. Empty/omitted = every rule can gate (unchanged default behavior). Matching is prefix-based ("SEC001"matches only that code,"SEC"matches everySEC*code) — ruff's own--selectconvention, reused rather than inventing a separate "category" concept.report.select(named section) — which rule codes/prefixes appear inscan/check's own output. Deliberately does not fall back toselect— the two are parallel, independently-defaulting sections (matching ruff's own separatelint/formatconfig), not one inheriting from the other. This is what makes "gate on a small set, see the full backlog" possible: narrowing the gate must never silently narrow what's visible.report.to(same named section) — a URL to POST the complete, unfiltered finding set to after a scan, never narrowed by eitherselectfield above. Resolved flag →VLOTPIPE_REPORT_TOenv var → config, first non-empty wins. The auth token has no config-file field at all —VLOTPIPE_REPORT_TOKEN(env var) only, since.vlotpipe.ymlis a committed file and a hardcoded token there is exactly theSEC002pattern this tool itself flags in pipeline YAML.
Implementation shape: cmd/vlotpipe/main.go's run() collects every
finding once (everything, filtered only by ignore:/inline
suppression — those mean "not a real problem," which should hold
everywhere), then derives three independent views from it: all
(display: severity floor + report.select), blockers (gate: blocker
severity + select), and the push payload (everything, always
complete). Push failures are logged as a warning and never change
check's exit code or fail the scan — an optional, separately-operated
dashboard being down must never break the actual lint gate.
Consequences¶
- A
.vlotpipe.ymlwith neitherselectnorreportset is unchanged from pre-ADR behavior — this was verified, not assumed (full existing test suite green before any new test was added). internal/pushreportdefines the client-side payload contract (Payload{Repo, Branch, Commit, ScannedAt, FilesScanned, Violations}, JSON, optional bearer auth) with no real server to talk to yet. Seevlotpipe-dashboard's own ADR 0001, which records the corresponding server-side commitment.select/report.selectare a new, third suppression-adjacent mechanism alongsideignore:(per-code/path suppression with a reason) and inline# vlotpipe: ignorecomments. They compose:ignore:always applies first, inside each rule check, before eitherselectfilters the remainder.