Skip to content

Add the gate as one entry point with one named leg per thing it checks - #70

Merged
iderex merged 1 commit into
mainfrom
ci/gate-entry-point
Aug 8, 2026
Merged

iderex merged 1 commit into
mainfrom
ci/gate-entry-point

Conversation

@iderex

@iderex iderex commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Closes #18.

The board has a module, a package and a suite now, and no job that compiles or
runs any of it. This is the leg that gates the work: build, test, format, from
decisions/means.md, behind one command.

The shape

go run . gate runs every leg in order and stops at the first failure.
go run . gate <leg> runs one. .github/workflows/gate.yml is one job per leg
and every step in it is that second form, so the workflow file decides nothing a
shell would decide differently. internal/gate holds the legs as data and the
runner; main.go is the entry point.

The means is Go, which is the answer decisions/means.md already gives and the
reason it gives it applies here directly: each of the three properties below is a
function reading something and returning a verdict, so each one is run against a
planted input in the suite rather than proven by breaking the tree on purpose.
No dependency is added, go.mod is untouched, and there is still no go.sum.
The YAML reader is a line reader over a two-space block rather than a parser, for
that reason; what it cannot read it refuses.

Three things it refuses, each watched refusing

A partial run reading as a whole one. Every run ends by naming every leg and
saying how many of the set it examined. Each job in CI runs exactly one leg, so
this is the shape of nearly every run there.

go run . gate test
gate: test: a suite with a failing test (go test ./...)
ok      flowfin.dev/hub 0.819s
ok      flowfin.dev/hub/internal/gate   1.253s
ok      flowfin.dev/hub/manifest        1.274s

gate examined 1 of 3 legs.
  build    not asked for
  test     passed
  format   not asked for

With a compile error planted in manifest/planted.go:

go run . gate
gate: build: a tree that does not compile (go build ./...)
# flowfin.dev/hub/manifest
manifest\planted.go:3:29: cannot use "not an int" (untyped string constant) as int value in return statement

gate examined 1 of 3 legs.
  build    FAILED: go build ./...: exit status 1
  test     not reached, because build failed first
  format   not reached, because build failed first
gate refused: build
exit status 1

With a failing test planted instead, so the first leg passes and the second does
not:

gate examined 2 of 3 legs.
  build    passed
  test     FAILED: go test ./...: exit status 1
  format   not reached, because test failed first
gate refused: test

A format leg that passes on an unformatted tree. gofmt -l prints the files
it would rewrite and exits zero whether it printed any or not, so the verdict
comes from the output. Deleting that branch from Verdict:

go test ./internal/gate -run TestVerdictRefusesGofmtListingAFileDespiteExitZero -count=1
--- FAIL: TestVerdictRefusesGofmtListingAFileDespiteExitZero (0.00s)
    gate_test.go:39: a file name on standard output with exit zero was read as a pass
FAIL

A leg with no job, or a job with no leg. The check-run names are what a
ruleset in #48 would require, so the suite reads the workflow file and compares
its jobs against the leg list. Deleting the format: job:

go test ./internal/gate -run TestWorkflowDeclaresOneJobPerLeg -count=1
--- FAIL: TestWorkflowDeclaresOneJobPerLeg (0.01s)
    gate_test.go:232: leg format has no job in .github/workflows/gate.yml reporting as "Gate: format"
FAIL

and adding a lint: job that no leg declares:

--- FAIL: TestWorkflowDeclaresOneJobPerLeg (0.01s)
    gate_test.go:237: job lint in .github/workflows/gate.yml reports as "Gate: lint", which is no leg of the gate
FAIL

All five runs above are at 6fc3898, on Windows, which is why the compiler prints
a backslash in the planted path. The tree was restored after each and the suite
is green:

go test ./... -count=1
ok      flowfin.dev/hub 0.669s
ok      flowfin.dev/hub/internal/gate   0.905s
ok      flowfin.dev/hub/manifest        0.913s

Two things #18 asked to be settled rather than assumed, and how they are settled

The job names carry a prefix. Three of the six check runs on main are
produced by the Pages deployment, which declares no file in this tree, and one of
them is called build:

gh api repos/Flowfin/hub/commits/main/check-runs --jq '[.check_runs[].name] | sort'
["Audit workflows (zizmor)","Reject Trojan Source Unicode","Scorecard analysis","build","deploy","report-build-status"]

Run 2026-08-08 against main at a641c7f. A leg named build would report under
a name this repository does not control, and #48 would end up requiring that one.
The legs are therefore Gate: build, Gate: test and Gate: format, the prefix
is internal/gate.JobNamePrefix, and TestNoLegReportsUnderANameSomethingElseAlreadyUses
refuses a leg whose check-run name collides with one of the three.

The workflow runs on push and on pull request. #18's Done-when reads
commits/main/check-runs, which a pull-request-only workflow never appears in,
and a push-only workflow does not report on a fork's pull request. Both triggers
are declared, so the command in the Done-when is answerable on main and the
check still runs where the issue's first sentence says it should.

Not covered

The format leg is gofmt -l and nothing else. internal/format in #23 is the
half that reaches the HTML, the YAML and the prose, and #68 is open with it; when
that lands, its Formatting leg becomes a step of this leg rather than a second
workflow, which is what its own file comment says it expects. Nothing here
touches that PR's files.

On a clone whose working copy has CRLF, this leg lists Go files nobody has
touched, because gofmt does not normalise before judging and .gitattributes
pins only *.json today:

go run . gate format
gate: format: a Go file gofmt would rewrite (gofmt -l .)
manifest\manifest.go
manifest\manifest_test.go

Run 2026-08-08 on this Windows clone with core.autocrlf=true. The content of
neither file is wrong; the checkout is. #23 is where the pin widens, and
CONTRIBUTING.md says so where somebody meeting the red will read it. The Linux
runner checks out LF, so this leg is expected green in the job, and that is a
claim about the job until the job has run rather than a measurement.

The gate is not required on main and cannot be by this change; that is #48, and
it is deliberately last in its milestone.

This change was not read by a second person. The evidence above is in its place.

Size

930 added lines, of which 375 are the suite and 76 the workflow file. One topic:
the entry point and the legs it runs.

#18)

The five workflows on this board are supply-chain and hygiene checks; none of
them compiles or runs anything. A tree with a module and a suite in it that no
job builds and no job tests is a tree where a green check-run list means less
than a reader takes it to mean.

The legs are build, test and format, which is what decisions/means.md settles
them as. They live in internal/gate as data plus a runner, and the workflow file
is one job per leg calling `go run . gate <leg>`. That way the command a
contributor runs before pushing and the command the job runs are the same
command, and a workflow file cannot decide anything a shell would decide
differently.

Three failures this refuses that a single build job would not.

A run that covered part of the set reading as one that covered all of it. Every
run ends by naming every leg and stating how many of the set it examined, whether
a leg was skipped because it was not asked for or because an earlier one failed.
Each job in CI runs exactly one leg, so this is the shape of every run there.

A format leg that passes on an unformatted tree. `gofmt -l` prints the files it
would rewrite and exits zero either way, so the verdict is derived from its
output. Deleting that branch turns the leg green on a tree gofmt would rewrite,
and TestVerdictRefusesGofmtListingAFileDespiteExitZero is what reds instead.

A leg with no job, or a job with no leg. The check-run names are what #48 would
require, so the leg list and the workflow's job list are compared by the suite
rather than by whoever last edited one of them. The names carry a prefix because
`build`, `deploy` and `report-build-status` are already produced on main by the
Pages deployment, which declares no file in this tree, and a leg named `build`
would report under a name this repository does not control.

CONTRIBUTING.md said the entry point was still to come and named this issue. The
same change that lands it has to correct that, or the document sends a reader to
a tracker for a command that is in the tree.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added this to the 2. Repository scaffolding milestone Aug 8, 2026
@iderex iderex self-assigned this Aug 8, 2026
@iderex iderex added the ci label Aug 8, 2026
@iderex
iderex merged commit 344c038 into main Aug 8, 2026
12 checks passed
@iderex
iderex deleted the ci/gate-entry-point branch August 8, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the CI entry point, with one named leg per thing it checks

1 participant