Skip to content

chore(policy): forbid compiling a third-party tool from source (CLOUD-86) - #225

Merged
wenzowski merged 1 commit into
mainfrom
wenzowski/cloud-86-add-prebuilt-lint-for-compiled-artifacts-the-repo-did-not
Aug 10, 2026
Merged

wenzowski merged 1 commit into
mainfrom
wenzowski/cloud-86-add-prebuilt-lint-for-compiled-artifacts-the-repo-did-not

Conversation

@wenzowski

Copy link
Copy Markdown
Contributor

Closes CLOUD-86. Config-only — two [[rule]] rows in this repository's own
batten.toml as consumer #1, plus their bats suite. No crate change.

The property

A tool built here is a tool nobody attested: the build runs on whatever toolchain
the runner had and produces a binary no checksum covers. Every tool this repo uses
is pinned in mise.toml as a prebuilt artifact with a locked url and a
per-platform checksum — CLOUD-281 is the instance where the one tool not pinned
that way was the one installed from an unverified download.

Two rows, because a forbid pattern is a literal substring and the two files
spell the same mistake differently:

rule file pattern
no-source-built-tool mise.toml "cargo:
no-cargo-install-in-ci .github/workflows/*.yml cargo install

The opening quote in the first is load-bearing: TOML bare keys cannot contain
:, so a backend key is always quoted, and the quote is what keeps the row off
prose that merely says "cargo".

The exemption half is why this was blocked on CLOUD-208

An exemption is a [[waiver]] carrying a reason and an expiry — never
severity = "allow", which records no reason and lapses never. That is acceptance
case (c), and the suite covers the whole shape rather than only the happy path: a
waived entry passes with a pointer-only audit line on stderr, a lapsed one is
red again with nobody having acted, an exemption with an empty reason is exit
1 (bad input, not a verdict), and a second un-waived violation still blocks.

One reworded comment, stated rather than quietly narrowed

release-artifacts.yml explains CLOUD-259 by narrating install-action's fallback
chain, which ended in the literal cargo install — the single hit on a clean
tree. The comment now says "a from-source crate build" and says why. Narrowing the
pattern to run: cargo install was the alternative and was rejected: it would
have missed the block form (run: | with the command on its own line), which is
coverage that looks total and isn't. A gate that fires on its own documentation is
a gate people delete.

Not gated here

Whether a prebuilt artifact is attested. That is CLOUD-90's [[provision]]
manifest and CLOUD-281's github:-vs-ubi: distinction; a row firing on ubi:
would be answering a different question. The issue's stated assumption 1 held up.

Amendment to the Ready block

§5 said a violation exits 1. It exits 2 — the policy verdict, on every surface
that renders one. Measured, asserted, and recorded on the issue.

Verification

mise run fmt (hk fix --all, ending in batten-check over these very rows)
green, and all 9 cases of tests/prebuilt-lint.bats pass.


Generated by Claude Code

@linear-code

linear-code Bot commented Aug 10, 2026 •

Copy link
Copy Markdown
CLOUD-86 Add prebuilt-lint for compiled artifacts the repo did not build

Why
The rule and check engine is missing a general supply-chain hygiene gate: never compile or accept prebuilt artifacts the repo did not produce, with configuration-driven exemptions.

Acceptance

  • Prebuilt-lint exists as a rule or check
  • Exemptions are configuration-driven

Refinement — Ready (forbid rules over build config catch compile-from-source; exemptions wait on the waiver surface)

Refinement gate: Definition of Ready & Done. This body carries only specializations.

  • Source of truth (§1). This repo's batten.toml (consumer ci: check in the main-branch protection ruleset #1): [[rule]] forbid rows over the build-config globs (mise.toml, .github/workflows/*.yml). No engine change; the sanctioned alternative a finding points at is the pinned provisioning path (relatedTo CLOUD-90).
  • Computable predicate (§2). Policy-engine-first, and expressible today: each compile-from-source shape is a kind = "forbid" literal row (e.g. a cargo: backend in mise.toml; cargo install in a workflow), evaluated by batten check via mise run batten-check — already the last hk gate step and in CI. What today's rule model cannot express is the exemption half: configuration-driven exemptions with a required reason are the per-rule waiver surface CLOUD-208 specifies.
  • Effect (§3). Runs under check (read); no new command, no effect-table change.
  • Output & exit (§5). The engine's existing contract: pointer-only path:line rule-id, sorted, byte-stable; violation exits 1.
  • Commit / bump (§6). chore → no bump — consumer-config rows in this repo's batten.toml plus a bats fixture; no crate change.
  • Test obligation (§7). tests/prebuilt-lint.bats: (a) a fixture tree whose mise.toml carries a compile-from-source backend fails batten check naming the rule id; (b) a clean fixture passes; (c) an exempted entry passes only through a waiver carrying a reason — the case that lands after CLOUD-208.
  • Blockers (§8). blockedBy CLOUD-208 (the waiver surface that makes exemptions configuration-driven). Refinable now; the forbid rows can land first, the exemption half after CLOUD-208.

Stated assumptions (flagged rather than blocking)

  1. Deny set. Assumption: the gate flags compile-from-source of third-party tools (backend prefixes in mise.toml, source builds in CI workflows); acceptance of prebuilt binaries is governed by the [[provision]] manifest (CLOUD-90), not by this lint. Flag if the intent was instead to gate unattested prebuilt artifacts.

Review in Linear

CLOUD-86. Two `[[rule]]` rows in this repository's own batten.toml, as
consumer #1 — no engine change. A tool built here is a tool nobody
attested: the build runs on whatever toolchain the runner had and
produces a binary no checksum covers, while every tool this repo uses is
pinned in mise.toml as a prebuilt artifact with a locked url and a
per-platform checksum. CLOUD-281 is the instance where the one tool not
pinned that way was the one installed from an unverified download.

Two rows because a `forbid` pattern is a literal substring and the two
files spell the same mistake differently: `"cargo:` in mise.toml (the
opening quote is load-bearing — TOML bare keys cannot contain `:`, so a
backend key is always quoted, which keeps the row off prose that merely
says "cargo"), and `cargo install` in a workflow.

The exemption half is what this waited on CLOUD-208 for: an exemption is
a `[[waiver]]` carrying a reason and an expiry, never
`severity = "allow"`, which records no reason and lapses never. The
suite covers the whole shape — a waived entry passes, a lapsed one is red
again with nobody having acted, an exemption with no reason is exit 1
rather than applied, and a second violation still blocks.

release-artifacts.yml's CLOUD-259 comment narrated install-action's
fallback chain ending in the literal command, so it was the one hit on a
clean tree. Reworded, because a gate that fires on its own explanation is
a gate people delete. Narrowing the pattern to `run: cargo install` was
the alternative and would have missed the block form — coverage that
looks total and isn't.

Not gated here: whether a PREBUILT artifact is attested. That is
CLOUD-90's manifest and CLOUD-281's `github:`-vs-`ubi:` distinction; a
row firing on `ubi:` would be answering a different question.
@wenzowski
wenzowski marked this pull request as ready for review August 10, 2026 20:33
@wenzowski
wenzowski force-pushed the wenzowski/cloud-86-add-prebuilt-lint-for-compiled-artifacts-the-repo-did-not branch from 4aee6b7 to 4a94f05 Compare August 10, 2026 20:33
@wenzowski

Copy link
Copy Markdown
Contributor Author

/fast-forward

@wenzowski
wenzowski merged commit 4a94f05 into main Aug 10, 2026
6 checks passed
@wenzowski
wenzowski deleted the wenzowski/cloud-86-add-prebuilt-lint-for-compiled-artifacts-the-repo-did-not branch August 10, 2026 20:38
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.

2 participants