docs: cross-org Makefile standard plan (below the fold) - #3
Conversation
|
Reviewed against the repos rather than against the RFC — recomputed every Baseline — all verified
Criterion 1's doppler target is not reachable
Subtracting the standard's 36 from doppler's actual targets (mapping just-makeit's remainder is 3 ( Every one of doppler's 20 looks genuinely local to me — I would not collapse any Two gaps of the
|
|
All three findings confirmed by recomputing from the two Makefiles, and fixed in Criterion 1 — you are right, and the error is instructiveI reproduced your 20 exactly: My Both gaps closed
Also took the Derived numbers, with both closedCriterion 1 is now ≤18 / ≤1. I did not collapse any of doppler's 18 to hit a Criterion 10That last derived line became a criterion: targets shared by two or more Three misses of the same class ( Your StatusReady for your approve on substance. Still holding on P0 until explicitly |
|
Verified 1. "Universal (8)" is now wrong — it is 9
Worth fixing precisely because this file is about to become the spec someone 2. just-makeit's ≤1 is contingent, and the dependency is invisibleMeasured today, just-makeit has 2 local targets, not 1: Your ≤1 is right only after the Either note the dependency inline ("≤1, assuming P0 collapses OtherwiseBoth gaps closed correctly, criterion 10 is a good addition, and deriving the Approve on substance once (1) is corrected; (2) is your call on how to Still holding on P0/P2 until explicitly approved. |
|
Added a Required files table (
Checked against the repos, not written from the designTwo corrections fell out of doing that, which is becoming the pattern: Neither repo has a Adoption adds exactly one file.
Still holding on P0. |
|
Checked the Required-files table against both repos. Every factual claim So "adoption adds exactly one file" is literally true — One row overstates, and contradicts your own Non-goals
That is not reachable, and not intended. Both repos pin non-Python tools that
And criterion 7 is correctly scoped — "hand-pinned Worth tightening to "resolves no versions for lock-managed tools; non-Python OtherwiseThe table is a good addition — the file-owns-one-concern framing is what makes Approve on substance with the two open nits: |
|
Heads up — this PR cannot be merged as things stand, and neither can any The ruleset requires a status check named
So the required check can never be reported on a pull request, and the PR waits Why it looks fine at a glancePRs #1 and #2 merged cleanly — but on 2026-06-29, and the ruleset was
Which is, with some irony, this plan's own thesis: a config duplicated into a Options
I would take (1) — but it is a change to org branch protection and a workflow, |
|
Checked Canonical home — verified, and it sharpens my own ruleI only ruled out repos that consume the standard by naming doppler and
New risk: the cache fallback makes a hard gate soft
The offline-developer half is right. The CI half assumes the failure mode is If that reports-and-continues, one bad deploy silently disables the drift gate Suggest making it explicit: when Both nits still open — and I got one wrong
The pre-commit row is not rescoped — I said it was in my last comment.
Which remains untrue for OtherwiseNothing else in |
The `main` ruleset (created 2026-07-29) requires a status check named "CI passed". No workflow in this repo produced it, and the only workflow here had no `pull_request` trigger at all — so nothing ran on a PR and the required check could never arrive. Every pull request was permanently unmergeable. It went unnoticed because both merged PRs (#1, #2) landed on 2026-06-29, a month before the ruleset existed; #3 is the first PR to meet the rule. "CI passed" is doppler's aggregator job name, so the ruleset appears to have been copied from doppler's without the workflow that makes the name true. Two changes: - `pull_request` trigger, with NO path filter. The required check must report on every PR, so the job has to run on every PR — a path filter would skip it on an unrelated change and reproduce the same deadlock. - a `ci-passed` job named "CI passed", gating on every other job, mirroring doppler's ci.yml aggregator so the org has one shape. `if: always()` so it still reports when a dependency fails, rather than being skipped and leaving a red PR indistinguishable from an unmergeable one. This workflow runs on its own PR (pull_request events use the merge ref), so the fix is self-bootstrapping: the PR that adds the check is the first to be gated by it.
One `standard.mk` every repo includes, per-repo variation as configuration. Design RFC is doppler-dsp/doppler#555; this records the plan, the measured baseline, and the success criteria in the org's existing planning surface rather than starting a second one. The baseline is what makes the criteria checkable later: 50 vs 27 targets with 18 shared, `make help` covering 60% / 81% of them, and 12/83 vs 4/71 CI steps routing through make. Three of the nine criteria (help completeness, no ghost targets, drift) are enforced by gates rather than by review — none of the problems this fixes were decided, they accumulated, so review is demonstrably not the control. Placed above the Decision log so that section stays last, per the file's existing shape.
…rion 10 Review recomputed criterion 1 from the two Makefiles and found ≤14 unreachable. Reproduced independently: doppler has 50 targets of which 30 map onto the standard, leaving **20** local — not 14. My number came from 50 − 36, which assumed doppler already had all 36 standard targets; it has 30. Arithmetic from memory rather than from the data, which is the same error that produced the other two misses below. Two coverage gaps of the `release-branch` class, both closed: - `test-examples` is in BOTH repos and was in no feature group — now `HAS_EXAMPLES`. - No system-dependency target existed at all. `setup` is project deps in both repos; system deps are `jbx install-deps`. That gap was pointed, because this plan's own hosting rationale is that just-buildit is already in every repo's CI bootstrap — which is true precisely because of the target the standard did not define. Now universal, a no-op where a repo declares none. With both closed and `install` collapsed into `setup` (a strict subset of it in the same file), the derived numbers are: standard 38, doppler 18 local, just-makeit 1 local, and **no target shared by both repos left outside the standard**. That last line is now criterion 10, checked by script rather than reasoned about — three misses of the same class is enough evidence that the list must be derived from the measured union, not assembled from memory. Also records the reviewer's evidence for gates-over-review: `make wheel` was exiting 0 with no rule in a repo that already had a lint gate, CI on every PR, and a help entry advertising it.
Extends the convention's "each file owns one concern" model to the files the standard actually introduces, so an adopting repo can see the whole surface in one place rather than inferring it from prose. Checked against both repos rather than listing what a standard "should" need: `Makefile`, `pyproject.toml`, `uv.lock`, `.pre-commit-config.yaml` and `jb.toml` all exist in each today, so **adoption adds exactly one file** (`standard.mk`); `local.mk` is optional and so far unneeded by either. Two accuracy notes from that check. System packages are folded into `jb.toml` under `[dev.<manager>]` in both repos — neither carries a standalone `jb-deps.toml` — so the table names `jb.toml` as the real location and mentions `jb-deps.toml` as the alternative. And `uv.lock`'s row states why it is on the list at all: it is what makes local and CI resolve identically, which is the mechanism that lets dispatch close the environment-drift class rather than merely narrow it.
Two decisions the plan had left implicit. **Canonical home is just-buildit.github.io**, served at https://just-buildit.github.io/standard.mk. The RFC had resolved this only as far as "the org", and the obvious candidate turns out to be disqualified by our own rule: `just-buildit/just-buildit` has a Makefile, so it consumes the standard exactly as doppler and just-makeit do. The org-pages root is a non-consumer whose charter is already "small static resources the toolchain depends on", and serving over the CDN keeps the gate to one curl -- no clone, no auth, and no raw.githubusercontent rate limit, which is the documented reason the jbs/ libs were moved there. **The gate compares against a cache**, refreshed when missing or stale, falling back to the cached copy when the fetch fails. Offline is therefore not a broken make lint, while a real difference still fails. Fresh clone with neither cache nor network reports that it could not run; CI always has both, so the gate is authoritative where it is enforced.
The cache fallback made a hard gate soft. It was reasoned from the failure mode "no network", but the likelier one is the fetch failing while the network is fine — a CDN outage, a bad deploy to the pages repo, a 404 after a rename. Under the cached design that degrades silently into "compared against something older", and on a CI runner (a fresh clone, so no cache) it lands on the "could not run" path. One bad deploy would disable the drift gate across every adopting repo at once, with nothing going red — which is the warning-nobody-reads failure this plan already rejects, arriving through the distribution mechanism instead of the gate's wording. No cache: `make lint` fetches https://just-buildit.github.io/standard.mk every time and fails if it cannot. A gate that cannot reach its reference has not passed — it has not run, and it should say so. Same reasoning as the gate failing rather than warning. Offline `make lint` is the cost, and it is the right one to pay: the gate is enforced in CI, which has the network, and a developer who cannot reach the CDN has not been told their vendored copy is clean — only that it could not be checked.
Two review nits, both cases of a summary line disagreeing with the detail it summarises. `Universal (8)` listed eight names while `install-deps` was declared universal in the very next paragraph — and the stated cap of 38 only reconciles with nine (9 universal + 3 lint-<tool> + 2 aggregates + 24 across the feature groups). The list and the count now agree, and both agree with the cap. The `.pre-commit-config.yaml` row claimed it "resolves no tool versions itself". That is not reachable and was never intended: clang-format and cmake-format are not Python dev-group tools, so pre-commit must resolve them, and both repos pin them with a `rev:` today. Criterion 7 and the Non-goals were already correctly scoped to lock-managed tools; only this row stated the absolute — and it is the summary table someone would most likely write the gate from, where a gate built on it would fail on clang-format in both repos permanently.
389ceef to
b6d8b1e
Compare
P0 of the cross-org Makefile standard (RFC doppler-dsp/doppler#555, plan just-buildit/.github#3). One canonical standard.mk that every repo in just-buildit and doppler-dsp includes, with per-repo variation expressed as configuration rather than as a fork. just-makeit is the prototype adopter and its Makefile now defines zero targets; examples-clean, its one local target, lives in local.mk. Three gates hang off `lint`, which is what CI runs, and each was sabotaged to prove it fails: standard-check (drift; fails rather than skips when it cannot reach canonical, and is inert until P1 publishes it), help-check (every target documented, every rule listed), ghost-check (no .PHONY entry without a recipe or prerequisites). A flag turned on with its command variable empty is a parse-time error, because that case is invisible to every gate. Renames, none with a caller outside the Makefile: install folded into setup, check-version -> version-check (and it now requires the version manifests to agree with each other, not just with VERSION=), build -> wheel. Found by prototyping in one repo first: standard.mk rejected the GNU make 3.81 that macOS ships, and the gates parse a database whose wording changed in 3.82. Both would have shipped to every adopter. Found by review: HAS_EXAMPLES carried an examples-clean that doppler cannot supply, HAS_COVERAGE was missing coverage-gate, and the 38-target cap agreed with the plan by coincidence rather than construction — now 35 user-facing + N dispatch + 3 enforcement, checked against the RFC group table target-for-target.
Adds a
## Makefile standard — cross-org plansection to the internalhalf of
README.md, above the Decision log.Design RFC and full rationale: doppler-dsp/doppler#555. This records the plan, the measured baseline and the success criteria — the RFC argues the design, this tracks the work.
Why here
The repo already carries the org's planning surface below the fold — Design conventions, Schemas, Status, Decision log. Adding
plans/*.mdwould have created a second planning surface next to a maintained one, which is the same duplication the standard exists to remove.Placed above the Decision log so that section stays last, per the file's existing shape.
What it contains
helpat 30/50 and 22/27, CI at 12/83 and 4/71) plus the live consequences: noformatin doppler,bench-*naming split,zensical build --strictin three disagreeing places, docs built twice per doppler PR, andmake wheelexiting 0 with no rule behind it.lint-<tool>dispatch + feature groups, capped at 36 targets; required dispatch, generatedhelp,releasereserved,<noun>-<qualifier>naming,local.mkmay only add.Note on the criteria
Three of the nine (help completeness, no ghost targets, drift) are enforced by gates rather than review. That is deliberate: none of the problems this fixes were decided, they accumulated — so review is demonstrably not the control that would have caught them.
Verification
mdformat(the repo's own pinned config: gfm + gfm-alerts + mkdocs) run over the file — clean, 95 insertions with zero modifications to existing lines.check-readme-content.shis unaffected: the workflow runs it againstprofile/README.mdonly, and the public profile is untouched by this PR.🤖 Generated with Claude Code
https://claude.ai/code/session_01XLuSsmv88aheh7mNQgBDQK