Skip to content

Refuse a source file that does not carry the licence it is under [#18] - #188

Merged
iderex merged 1 commit into
mainfrom
ci/every-source-file-carries-its-licence-header
Aug 24, 2026
Merged

Refuse a source file that does not carry the licence it is under [#18]#188
iderex merged 1 commit into
mainfrom
ci/every-source-file-carries-its-licence-header

Conversation

@iderex

@iderex iderex commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What was wrong

The licence text is at the root and nothing else in this repository said what it
is. No source file carried the identifier, and the README said so in its own
words while pointing at the entry that had to settle it first:

git grep -c 'SPDX-License-Identifier' 65353c6 -- '*.go' '*.sh' ; echo "exit=$?"
exit=1

Run 2026-08-25. 65353c6 is the mainline this branch is cut from, and it is
named in the command so the reading reproduces from anywhere rather than from a
working tree.

That leaves the terms a property of the place a file was found rather than of
the file. A file lifted out of this tree into another one arrives carrying
nothing, and reads as something anybody may do anything with.

GOVERNANCE.md carried the same gap one step worse. Its continuity section said
that nobody may fork this repository, on a reading of a licence file that did
not exist when the reading was taken, and the file has existed since 2138d27.

The entry that had to answer first has answered. Entry 1 of #7 is settled as
copyleft, and the spelling is AGPL-3.0-or-later, decided fleet-wide and
carried on Flowfin/hub#1:

gh issue view 1 --repo Flowfin/hub --json comments       --jq '.comments[].body' | grep -n 'Entry 1, license'
134:- Entry 1, license: AGPL-3.0-or-later.

Run 2026-08-25.

What this does

internal/licence holds the identifier and reads the comment a source file
opens with. Two answers rather than one, because a file carrying no header and a
file carrying the wrong one are different mistakes with different repairs.

The invariant table gains source-carries-its-licence-header over a new
population, every tracked source file. It refuses an absent header and it
refuses one naming an identifier this repository does not publish under, which
is the near miss worth paying for: the bare AGPL-3.0 the platform reports is
deprecated on the published list, and AGPL-3.0-only is a different permission
rather than a different spelling.

Every tracked source file gains the header, 55 of them, and the run reads them.

README.md states the identifier and what the two current spellings differ on.
GOVERNANCE.md's paragraph is re-taken against the file that now exists.

One thing about this row that is unlike every other row in the table, and it
is stated here rather than found later. The others refuse the PRESENCE of
something, so a body of the wrong population carries none of it and passes
without being asked. This one refuses an ABSENCE, and an absence is what a
produced page, a copy of the design tokens and a document all have. So the row
reads the shape of the body a second time and passes what is not source. The
population is cut by extension and the shape is read from the bytes, and a file
of a language nobody has taught it to recognise is passed silently. That bound
is in the package's own words and the suite carries a case over this tree that
reports one, shown red below.

Closes

Closes #18.

What was run

The gate, at the commit being pushed, 2a73e8b8e58303612dddd1c6f681bb7662bbcf51:

go run . ci
gate: 7 legs, in order: format, vet, test, build, links, sitemap, invariants
  needs-network was not asked for. Asking costs a request to the public name from whatever machine runs it, and a verdict that moves when somebody else's service does rather than when this tree changes. Ask with: go run ./harness/needs-network
  format: ok, 56 file(s)
  vet: ok
  test: ok, 27 test file(s)
  build: ok, 8 file(s)
  links: every reference that stays inside this site resolves to a file the build wrote
  sitemap: every page the build wrote is listed once, and every entry has a page behind it
  invariants: ok, 39 rule(s) decided, 1 owed and not decided
7 of 7 legs ran. None was skipped.

The row over the tree:

go run . invariants | grep 'source-carries-its-licence-header:'
  source-carries-its-licence-header: ok, 55 file(s) of every tracked source file

No test was skipped, and none needed a display or elevation.

The row bites, in both directions it is written for

The header taken off one file, which is what a new file looks like:

go run . invariants
  source-carries-its-licence-header: REFUSED, 1 violation(s)
    it refuses a tracked source file whose opening comment declares no licence identifier, and one declaring an identifier this repository does not publish under
    internal/site/site.go: the comment this file opens with declares no SPDX-License-Identifier:, and this repository publishes under AGPL-3.0-or-later

The same file carrying the only spelling, which is the near miss a presence
check misses:

go run . invariants
  source-carries-its-licence-header: REFUSED, 1 violation(s)
    internal/site/site.go: the comment this file opens with declares SPDX-License-Identifier: AGPL-3.0-only, and this repository publishes under AGPL-3.0-or-later

Both edits reverted; the diff against them is empty.

The suite goes red when the row is switched off

The row's decide made to return nothing:

go test ./internal/invariant/ -run 'TestEveryRowRefusesItsOwnViolationAndPassesTheNeighbour|TestTheLicenceRow'
--- FAIL: TestEveryRowRefusesItsOwnViolationAndPassesTheNeighbour (0.00s)
    invariant_test.go:314: row source-carries-its-licence-header passed its own violation
--- FAIL: TestTheLicenceRowRefusesAnIdentifierThisRepositoryDoesNotPublishUnder (0.00s)
    invariant_test.go:358: the row reported 0 violation(s) for a file declaring AGPL-3.0: []
--- FAIL: TestTheLicenceRowMovesOnTheHeaderAndNothingElse (0.00s)
    invariant_test.go:409: the file with no header refused [] of the rows reading every tracked source file, want only source-carries-its-licence-header
FAIL

The floor under the shape reading goes red too

The recogniser made to miss the interpreter line, which is the case that exists
so the bound above is reported rather than silent:

go test ./internal/licence/ -run 'TestEverySourceFileThisTreeTracksIsRecognised'
--- FAIL: TestEverySourceFileThisTreeTracksIsRecognised (5.63s)
    licence_test.go:107: .github/scripts/required-check-names.sh is tracked as a source file and IsSource does not recognise it, so the rule that reads the header passes it silently
FAIL

All runs 2026-08-25, each edit reverted before the next.

The means

Go, and the invariant table this repository already carries, because the rule is
a property of tracked bytes and that is what the table reads. It carries a rule
a machine refuses, the guard is shown to bite above by switching it off and
watching three cases redden, and every claim here carries the command behind it.
Nothing is added to the module graph: the package imports bytes and nothing
else, and the suite imports the toolchain and this module.

The alternative considered and refused was a REUSE-style external checker, which
would add a runtime and a dependency this tree does not carry, for a property
one function decides.

On the size of the diff. 59 files and 522 insertions, and 55 of the files
are the same two lines. One property holds across every changed byte, which is
the shape a reader checks instead of the diff: each source file opens with the
identifier this repository publishes under. The rule and its suite are the part
to read.

Who read it

Nobody but me. The ruleset requires no approving review, so this is merged by
whoever opened it, and the evidence above stands in place of a second reader.

The licence at the root says what this repository is under and travels with
nothing. A file lifted out of this tree into another one arrived carrying no
terms, and read as something anybody may do anything with. The header is what
makes the terms a property of the file rather than of the place it was found.

`internal/licence` holds the identifier and reads the comment a source file
opens with. The identifier is `AGPL-3.0-or-later`, settled in entry 1 of #7 and
carried fleet-wide by Flowfin/hub#1. The bare `AGPL-3.0` the platform reports is
deprecated on the published list, and `AGPL-3.0-only` is a different permission
rather than a different spelling, so the row refuses both as well as an absent
header.

The row is `source-carries-its-licence-header` and it reads every tracked source
file, which is 55 of them today. It passes a body that is not source, and that
is the difference between it and every other row in the table: the others refuse
the presence of something, so a body of the wrong kind passes without being
asked, and this one refuses an absence. The population is cut by extension and
the shape is read a second time from the bytes, so a file of a language nobody
has taught it to recognise is passed silently; the suite carries a case over the
tree that reports one.

README.md states the identifier and what the two current spellings differ on.
GOVERNANCE.md said nobody may fork this repository, on a reading taken while
there was no licence file; the reading is re-taken and the paragraph says the
opposite of what it said.

Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com>
@iderex iderex added documentation Improvements or additions to documentation ci labels Aug 24, 2026
@iderex iderex self-assigned this Aug 24, 2026
@iderex iderex added this to the Scaffolding and the gate milestone Aug 24, 2026
@iderex
iderex merged commit 179d4ba into main Aug 24, 2026
17 checks passed
@iderex
iderex deleted the ci/every-source-file-carries-its-licence-header branch August 24, 2026 23:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add the LICENSE file once the licence is chosen

1 participant