Skip to content

tools: REUSE gate checks the inline SPDX header - #87

Merged
futurebuildai merged 3 commits into
refactor/v1from
refactor/reuse-inline-header
Oct 10, 2026
Merged

futurebuildai merged 3 commits into
refactor/v1from
refactor/reuse-inline-header

Conversation

@futurebuildai

Copy link
Copy Markdown
Contributor

Problem

A review found two new Go test files merged with no SPDX header, yet the License (REUSE) job passed. The cause: REUSE.toml annotates whole trees (core/**, web/apps/**, web/packages/** and so on), so reuse lint counts every file as licensed whether or not it carries the matching identifier and copyright lines inline. The house rule (BRIEF-COMMON: every new file carries the SPDX header its directory's licence row in LICENSE-MAP.md gives) is therefore invisible to the existing gate.

Fix

.github/scripts/reuse_gate.py now runs a second pass over git ls-files. A tracked file whose name ends in one of .go, .ts, .tsx, .js, .mjs, .sql, .py or .sh must carry an SPDX-License-Identifier: line and an SPDX-FileCopyrightText: line somewhere in its first ten lines. Anything that misses one or both is reported under a new missing_inline_header category and makes the gate exit 1. The module docstring was extended to explain why the inline check exists (so a future reader does not delete it thinking it duplicates reuse lint).

.github/workflows/ci.yml now runs the unit tests under the License (REUSE) job, before the gate itself.

Exempt list

Path Reason
web/packages/api-client/src/schema.d.ts generated by npm run generate (openapi-typescript); the generator owns the whole file and would overwrite any inline header on every run. The licence is supplied by the web/packages/** annotation in REUSE.toml.

Adding another generated file later is a one-line addition to INLINE_EXEMPT with a one-line reason.

Flagged list on origin/refactor/v1 before this change

Exactly one tracked file was missing the inline header: web/packages/api-client/src/schema.d.ts (the exempt file above). Nothing else was flagged.

Test

.github/scripts/test_reuse_gate.py (its own SPDX header copied from reuse_gate.py) uses unittest only and a throwaway git repository. The five cases are:

  1. a file with both lines present passes;
  2. a file missing the identifier line fails;
  3. a file missing the copyright line fails;
  4. an exempt file at the production exempt location passes;
  5. a file in a non-matching extension (README.md, schema.json) is ignored.

Run it with python3 .github/scripts/test_reuse_gate.py from the checkout root, or with the discover form (python3 -m unittest discover -s .github/scripts -p test_reuse_gate.py from the same root).

🤖 Generated with Claude Code

REUSE.toml annotates whole trees (core/**, web/apps/**, web/packages/**
and so on), so plain `reuse lint` already counts every file as
licensed whether or not it carries the matching identifier and
copyright lines inline. A new file with no header at all is still
reported as compliant, which silently breaks the house rule
(BRIEF-COMMON: every new file carries the SPDX header its directory's
licence row in LICENSE-MAP.md gives). The License (REUSE) job therefore
needs its own second pass.

.reuse_gate.py adds a missing_inline_header pass over git ls-files.
Files whose name ends in one of .go, .ts, .tsx, .js, .mjs, .sql, .py
or .sh must carry an identifier line and a copyright line somewhere in
their first ten lines; anything else is reported under a new
`missing_inline_header` category and makes the gate exit 1. The
generated schema.d.ts (openapi-typescript output) is the one tracked
file that cannot carry the inline header and is exempted explicitly,
with a one-line reason; future generated files get added to the same
table.

.test_reuse_gate.py covers the four contract cases (both lines
present, identifier missing, copyright missing, exempt file) plus a
negative case for a non-matching extension. It builds a throwaway git
repo per test and never touches the host's git config. The CI
workflow now runs it under the License (REUSE) job, before the gate
itself, so a regression in the inline-header logic blocks the merge
even if the rest of the tree still passes `reuse lint`.

Signed-off-by: colton <colton@futurebuild.ai>
python3 -m unittest .github/scripts/test_reuse_gate.py fails on the
image's Python 3.12 with 'Empty module name', because the leading-dot
path form is not a valid module identifier to unittest's loader. The
discover form (start in .github/scripts, pattern test_reuse_gate.py)
works on every Python 3 it has been tested against, so it is the one
the workflow runs.

Signed-off-by: colton <colton@futurebuild.ai>
… lines

Signed-off-by: colton <colton@futurebuild.ai>
@futurebuildai
futurebuildai merged commit e684fba into refactor/v1 Oct 10, 2026
9 checks passed
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.

1 participant