Skip to content

validate/lint: no supported way to make a warning fail a build #28

Description

@jrjsmrtn

Tested against v0.4.0 (darwin_arm64 release tarball; checksums.txt sha256 matched, and
its sigstore bundle verifies with cosign against
https://github.com/okfcli/okf/.github/workflows/release.yml@refs/tags/v0.4.0).

What is missing

Both validate and lint report warnings and exit 0, and neither accepts a flag:

$ okf validate --help
error[usage]: unknown validate flag: --help
$ okf schema | grep -ci 'strict\|severity\|promote'
0

So there is no supported way to make a CI job or a commit hook fail on a warning. A warning that
nobody is obliged to act on is not a gate — it is a log line, and in a repository that anyone
commits to it will eventually be one nobody reads.

I am not arguing these findings should be errors. For the ones I care about — the footnote rules
from #20 — the spec treats footnotes only as the sources join key and requires neither direction,
so warning is the correct upstream default. The judgement is local; the mechanism is not. Today
each project that wants to enforce one has to build the mechanism itself.

What I had to build, and what its shape suggests

I maintain a small wrapper that shells out to okf validate, parses the JSON, and exits non-zero if
a finding's rule ID is in a promoted set. Three things about it are, I think, requirements rather
than preferences — each was learned by getting it wrong first.

1 — Per-rule promotion, not only a global --strict.
Different contexts need different subsets, and one of mine cannot be expressed by a single switch.
The three footnote rules are rendering faults caused by the diff in front of you, so I promote them
on every commit. okf/lifecycle/stale is not: it arrives because a date passed, on a repository
nobody has touched. Failing a commit hook on it blocks whoever commits next for something they did
not do — and still misses the case that matters, the untouched repository where no commit ever
comes. So it is promoted only in a weekly scheduled run. A global strict mode makes those two
contexts the same.

2 — An unknown rule ID must be an error, not a no-op.
This is the one that decides whether the feature is safe. If a promoted ID is silently ignored, then
a typo — or a rule renamed upstream — turns the gate into a pass while enforcing nothing, and it
reports success while doing it. My wrapper has to refuse an okf older than the release that
introduced the rules for exactly this reason: on an older binary they are never emitted, so the gate
would pass while checking nothing. If the flag validates its arguments against the known rule set,
no consumer needs that guard.

3 — Additive and replacing forms are not interchangeable.
If a default promoted set ever ships, a caller wanting one more rule will reach for the replacing
form and thereby stop enforcing anything added to the defaults later — silently, and reporting a
pass. I ended up with both spellings and a note explaining which to prefer. Worth deciding
deliberately upstream rather than discovering it downstream.

Possible shapes

No preference between these; the requirement is only that (1) and (2) above survive.

  • --fail-on <severity> / --min-severity <severity> — simplest, but cannot express the
    per-context split in (1) on its own.
  • --promote <rule-id> (repeatable) — expresses everything I need; must reject unknown IDs.
  • A distinct exit code for "no errors, but warnings present" — leaves the policy to the caller and
    needs no new flags, though it is coarser than per-rule.

The raw material is already there: findings carry stable rule IDs, and okf schema publishes CLI
metadata, so the set of valid IDs is machine-readable.

Relation to #26

#26 asks for broken links to be reclassified as ERROR. Reclassifying a rule moves the line for
everyone, and each such request has to be argued on its merits. A promotion mechanism lets each
project draw its own line and settles the whole class of request rather than one instance — so I
think this is the more general fix, and would leave #26 as a question about the default rather
than about enforceability.

Why I care

The wrapper is about 400 lines of Python and its README says, in as many words, that it exists to be deleted
the day okf can be made to exit non-zero on a warning. I would rather delete it than maintain it,
and I would rather other projects did not each write their own.

Happy to send a PR if you would like one, and happy to be told this belongs in a caller instead.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions