Skip to content

fix(root_hygiene): accept LICENSE.adoc / LICENSE.md and a REUSE LICENSES/ dir - #963

Merged
hyperpolymath merged 2 commits into
mainfrom
claude/kind-clarke-aqpe7a
Oct 27, 2026
Merged

hyperpolymath merged 2 commits into
mainfrom
claude/kind-clarke-aqpe7a

Conversation

@hyperpolymath

@hyperpolymath hyperpolymath commented Oct 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

root_hygiene only counts LICENSE or LICENSE.txt as the repo's licence file. The estate template rsr-template-repo ships LICENSE.adoc and a REUSE LICENSES/ directory. So the template, and every repo synced from it, gets a critical "Required file missing" finding, which fails the template's zero-critical Hypatia gate. hyperpolymath/rsr-template-repo#258 shows this, and so does that repo's main.

Change

lib/rules/root_hygiene.ex, in scan_required_missing:

  • More licence file names: LICENSE.md and LICENSE.adoc now satisfy the requirement, alongside LICENSE and LICENSE.txt.
  • REUSE LICENSES/ directory: scan/2 (the CLI path) also accepts a LICENSES/ directory, as long as it holds at least one REUSE licence text.
    • A licence text means a non-empty, non-hidden <SPDX-ID>.<ext> file that isn't a README, as the REUSE spec requires.
    • An empty LICENSES/, or one holding only .gitkeep, a README, an empty file or an extensionless file, declares nothing and still produces the finding.
    • The filename-only scan/1 can't see the directory, because list_root_files drops directories. It behaves as before.
  • No new findings anywhere: every name accepted before is still accepted.

Testing

Mix deps can't be fetched in this environment. As with #960, I compiled the module standalone and ran test/root_hygiene_test.exs with plain Elixir.

  • New tests: four. They cover .adoc and .md, reject LICENSE.adoc.orig, check the REUSE directory both empty and with a licence in it, and reject non-licence files in LICENSES/.
  • This branch: 38 of 38 tests pass, up from 34 on main.
  • Against the old rule: the positive tests fail, and the non-licence-files test fails against the earlier "any file counts" version of this PR.
  • Formatting: mix format --check-formatted passes.

The full mix test runs in CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F

…SES/ dir

The licence requirement accepted only `LICENSE` and `LICENSE.txt`. The
estate template ships `LICENSE.adoc` plus a REUSE `LICENSES/` directory,
so the template and every repo synced from it got a critical "Required
file missing" finding that fails the zero-critical gate.

Accept `LICENSE.md` and `LICENSE.adoc` as alternatives, and in `scan/2`
(where the repo path is known) accept a `LICENSES/` directory that holds
at least one file. Root file lists carry no directories, so the
filename-only `scan/1` cannot see `LICENSES/` and is unchanged there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 37 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 2536bb1f-d090-476e-a9d6-ce25ea550daf
Commits

Reviewing files that changed from the base of the PR and between 789ca60 and e5e0765.

Files selected for processing: 2
  • lib/rules/root_hygiene.ex
  • test/root_hygiene_test.exs
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review 🔄 Running since 2026-10-27T01:39:10.667892Z e5e0765 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 28138025c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lib/rules/root_hygiene.ex Outdated
Comment thread lib/rules/root_hygiene.ex
# `LICENSES/`), so the AsciiDoc and Markdown spellings count too.
%{
file: "LICENSE",
alternatives: ["LICENSE.txt", "LICENSE.md", "LICENSE.adoc"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update the default scorecard scan for LICENSE.adoc

When a repository relies on the newly accepted LICENSE.adoc, a default CLI scan still runs the :scorecard module and Hypatia.ScorecardIngestor.check_license/2, whose candidate list omits this spelling, so the same invocation reports SC-010 "No LICENSE file found" even though root hygiene now accepts it. Add LICENSE.adoc to that checker as part of introducing the new accepted name so the default product does not return contradictory license results.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid. This PR had already merged, so the fix is in #970. SC-010 now accepts LICENSE.adoc and calls root hygiene's REUSE check (RootHygiene.reuse_licenses?/1, now public), so the two checks can't disagree.


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Oct 27, 2026

Copy link
Copy Markdown
Contributor

Add Carrot credits or activate Agent usage billing to use Autofix

The REUSE branch accepted a LICENSES/ directory holding any regular
file, so a repo with no licence but a stray `.gitkeep` or README there
passed. The REUSE spec requires non-empty licence texts named
`<SPDX-ID>.<ext>`. Count only non-empty, non-hidden files with an
extension, and not READMEs. This matches the same tightening in
rsr-template-repo's licence checks (rsr-template-repo#258, 9b0ceeb).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F
hyperpolymath added a commit to hyperpolymath/rsr-template-repo that referenced this pull request Oct 27, 2026
…enSSF check (#258)

## Summary

This PR fixes two problems in the estate template's CI.

1. **Hypatia's GitHub-API rules got no token.** Hypatia's scan step ran
without credentials, so its rules that query the GitHub API quietly
returned nothing in every repo synced from this template. When
hyperpolymath/echo-types re-synced to this workflow
(hyperpolymath/echo-types#354), its finding count fell from 38 to 3,
mostly for this reason.
2. **The licence checks rejected `LICENSE.adoc`.** That's the file this
template ships, so the checks were red on the template and in every repo
minted from it.

## Changes

**`hypatia-scan.yml`**
- **Token:** pass `GITHUB_TOKEN: ${{ github.token }}` to the scan step
only, not the whole job. It's `github.token` rather than
`secrets.GITHUB_TOKEN`, so the scan doesn't raise its own harden-runner
finding (RE001).
- **Permissions:** grant `vulnerability-alerts: read`, which Hypatia's
Dependabot-alert rules need. `security-events` only covers code-scanning
alerts. The key is valid in GitHub's workflow schema (SchemaStore): the
edited workflow passes, and a made-up key fails.
- **Cache key:** the key now reads `${{ env.HYPATIA_SHA }}` instead of
repeating the SHA. Before, bumping the pin without also editing the key
would reuse the old scanner build.

**Licence checks** (`ossf-best-practices.yml`, and `just verify` /
`self-assess` in `build/just/assess.just`)
- They now accept `LICENSE`, `LICENSE.txt`, `LICENSE.md` or
`LICENSE.adoc`.
- They also accept a REUSE `LICENSES/` directory, but only if it holds a
non-empty, non-hidden `<SPDX-ID>.<ext>` licence text (not a README).
- `just verify` runs inside `just repo-init`, so its old `LICENSE`-only
check failed every mint. That's the Proven suite's template-mint E2E
lane.
- No licence file is changed.

## What the token can and can't reach

- **Code-scanning alerts:** reachable, through `security-events`.
- **Dependabot alerts:** reachable, through `vulnerability-alerts:
read`, which this PR adds.
- **Secret-scanning alerts:** not reachable. No `GITHUB_TOKEN`
permission covers them; they need a GitHub App or PAT. That's an owner
decision (it would add a secret to the template), so it's left out.
- **Branch protection:** may be refused for the job token.
- **When a call is refused:** the rule logs a warning and returns `[]`,
so a refusal costs coverage but never produces a false finding. BH001
("no protection at all") fires only on a genuine "Branch not protected"
answer.

## Evidence from CI

Compared with `main` (`0d3fcfe`), the Hypatia Security Scan on this PR:
- **no longer logs the three "GITHUB_TOKEN not set" warnings.** That
shows the alert queries ran with a token. It doesn't show they all
succeeded; see the section above.
- **reports 30 findings instead of 5.**
- **restored the same cache entry**, because the new key resolves to the
same string.

OpenSSF Compliance is now green.

## Still red, and why

Details are in [this
comment](#258 (comment)).

- **Hypatia critical finding (missing `LICENSE`):** waits on
hyperpolymath/hypatia#963. Once it merges, I'll bump `HYPATIA_SHA` here
and in the two other workflows that pin the same SHA. That bump also
brings in hyperpolymath/hypatia#960, which clears the old
`0-AI-MANIFEST` high finding.
- **estate-rules, Central Estate CI/CD Audit, and the `root_shape`
benchmark:** fixed by #260.

## Testing

- **Licence checks:** run against seven layouts, and all three checks
agree on each.
  - Pass: `LICENSE.adoc`, and `LICENSES/MPL-2.0.txt`.
- Fail: `.gitkeep` only, README only, an empty licence file, a dotfile
only, and no licence at all.
- **`just verify` on this tree:** the old recipe exits 1 with the CI
message; the new one exits 0.
- **shellcheck 0.9.0:** clean.
- **SchemaStore workflow schema:** both edited workflows validate.

## RSR Quality Checklist

### Required

- [x] No banned language patterns
- [x] SPDX license headers present on all modified files (unchanged)
- [x] No secrets, credentials, or `.env` files included. The scan uses
the job token, not a secret.
- [x] Linter is clean (Workflow Security Linter green)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F

---------

Signed-off-by: Jonathan D.A. Jewell <6759885+hyperpolymath@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
@hyperpolymath
hyperpolymath merged commit 9584188 into main Oct 27, 2026
67 checks passed
@hyperpolymath
hyperpolymath deleted the claude/kind-clarke-aqpe7a branch October 27, 2026 02:04
hyperpolymath added a commit to hyperpolymath/rsr-template-repo that referenced this pull request Oct 27, 2026
#261)

## Summary

The template pins Hypatia at `d73f76b`. That version predates two fixes:
- hyperpolymath/hypatia#960: accepts the repo deed `<repo>_chora.deed`
as the AI manifest.
- hyperpolymath/hypatia#963: accepts `LICENSE.adoc` and REUSE
`LICENSES/` as the licence.

Under the old pin the template raised a **critical** "Required file
missing: LICENSE" and a **high** `0-AI-MANIFEST` finding, so the
zero-critical gate in the Hypatia Security Scan and the Static Analysis
Gate failed. This was the bump promised in
#258.

## Changes

- The pin moves from `d73f76b` to Hypatia `main` at `7c10e5d`, which
includes #960, #963 and #969.
- It's updated in all three workflows that carry it, so they stay on one
version: `hypatia-scan.yml` (`HYPATIA_SHA`), `static-analysis-gate.yml`
and `dogfood-gate.yml`.
- `hypatia-scan.yml`'s cache key reads `HYPATIA_SHA` since #258, so it
follows on its own.
- The other two have the SHA in their cache keys, and those are updated
in place.

## Testing

- **Root hygiene at `7c10e5d`:** I ran the rule against this repo's root
and it returns **no findings**. At `d73f76b` it returned the critical
and the high.
- **Schema:** all three workflows validate against the GitHub workflow
schema (SchemaStore).
- **Old pin:** no copy of the old SHA remains in the tree.
`actions.lock` is unaffected, because this SHA is a git ref, not an
action pin.
- **Full scan:** I couldn't build the full scanner locally because Hex
is unreachable. This PR's Hypatia Security Scan and Static Analysis Gate
runs are the end-to-end check.

## Follow-up

hyperpolymath/hypatia#970 (open) makes SC-010, a Low-severity finding
that doesn't block, accept `LICENSE.adoc`. It also adds pagination and a
timeout to the secret-scanning sweep. Once it merges, a second small
bump picks it up.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F)_

Co-authored-by: Claude <noreply@anthropic.com>
hyperpolymath added a commit to hyperpolymath/echo-types that referenced this pull request Oct 27, 2026
…pe, 7c10e5d (Hypatia findings, part 2) (#356)

## Summary

Part 2 of fixing the Hypatia findings on echo-types. Part 1 (#354) took
the template's `hypatia-scan.yml` as it stood then. That version
silently dropped every API-backed rule, because it passed the scanner no
token. The fix went into the template first
(hyperpolymath/rsr-template-repo#258 and #261). This PR takes the merged
result, byte-identical.

| Change | Why |
|---|---|
| The scan step gets `GITHUB_TOKEN: ${{ github.token }}`, scoped to that
step | Hypatia's code-scanning, Dependabot, branch-protection and
baseline-health rules return `[]` without a token. It is `github.token`,
not `secrets.*`, so RE001 stays clear. |
| `vulnerability-alerts: read` on the job | Lets the Dependabot (DA)
rules read alerts. `security-events: write` covers only code scanning. |
| `HYPATIA_SHA` `d73f76b` → `7c10e5d` | This scanner accepts
`LICENSE.adoc` and REUSE `LICENSES/` (hyperpolymath/hypatia#963). It no
longer demands `0-AI-MANIFEST.a2ml` (hyperpolymath/hypatia#960). |
| Cache key `…-${{ env.HYPATIA_SHA }}` | A pin bump now rebuilds the
scanner. Before, it reused the old build under a hard-coded key. |
| `upload-sarif` moves to codeql-action v4.39.1 (`9b32540`) | Matches
the template. |

`actions.lock` is updated to match. `hypatia-scan.yml` now lists
codeql-action at `9b32540`, and that pin has its dependency entry. The
v4.38.0 entry stays, because the `metadatastician/standards` reusable
workflows still pin it.

## What this does not cover

- **Secret-scanning alerts.** No `GITHUB_TOKEN` permission can read
them, so the per-repo scan never sees them. The workflow comment says
so. They will come from a separate, private estate-wide sweep with a
dedicated read-only token, not from this repository.
- **The finding count will go up.** The API-backed rules are scanning
again, so expect findings that were unscanned under #354, not new
problems.

## Verification

- `hypatia-scan.yml` is `cmp`-identical to `rsr-template-repo@4f4536e`.
That file passed GitHub-workflow schema validation and the template's CI
in #258 and #261.
- `actions.lock` parses, and its `hypatia-scan.yml` list now names
exactly the four pins the workflow uses.
- I could not run `gh actions-lock` here. The lock edit mirrors the
format that tool writes, and this PR's CI run is the check that matters.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F)_

Co-authored-by: Claude <noreply@anthropic.com>
hyperpolymath added a commit that referenced this pull request Oct 27, 2026
…LICENSE.adoc (#970)

## Summary

This fixes three defects from #963 and
#969. Codex flagged them after those PRs had
already merged. It also tightens the REUSE licence rule after
CodeRabbit's review here.

## Changes

`lib/rules/secret_scanning_alerts.ex` (from #969)
- **Pagination:** the fetch read only the first page of 100 alerts, so
`sweep/2` returned `status: ok` with older open, stale or badly
dismissed alerts missing. It now follows GitHub's `Link: rel="next"`
header until the last page.
- It only follows links on `api.github.com`, because the token goes with
every request.
- Past 50 pages (5,000 alerts) it returns an error rather than a
truncated list.
- **Timeout:** the curl call had no deadline, so one stalled repository
could hang an estate-wide sweep. Each request now has `--max-time 30`,
and a timeout (curl exit 28) is reported as `GitHub API request timed
out after 30s`.
- **Header parsing:** responses are read with `curl -i` so the `Link`
header is available. If an HTTPS proxy adds its own "Connection
established" header block first, it's skipped.

`lib/scorecard_ingestor.ex` and `lib/rules/root_hygiene.ex` (from #963)
- **SC-010 now agrees with root hygiene.** The local Scorecard licence
check still rejected `LICENSE.adoc` and REUSE `LICENSES/`, so a single
scan reported both "licence present" (root hygiene) and "No LICENSE file
found" (SC-010). It now accepts `LICENSE.adoc` and calls root hygiene's
REUSE check.
- **`RootHygiene.reuse_licenses?/1` is now public.** The REUSE rule has
one definition instead of two copies that could drift apart.

REUSE strictness (e5f5a7a, owner decision on CodeRabbit's finding)
- **The problem:** a `LICENSES/` file used to count if it was any
non-empty `<name>.<ext>`. So `LICENSES/notes.txt` alone passed both
licence checks.
- **The fix:** its stem must now be on the SPDX License List or be a
REUSE `LicenseRef-` id. Matching is exact.
- **Why a list, not a filename rule:** real identifiers such as `curl`
and `blessing` are lowercase, so no heuristic separates them from
`notes`. The list is vendored instead:
- `priv/spdx/license-ids.txt` holds SPDX License List 3.29.0: 740
identifiers, deprecated ones included. Its header records the source
tag.
- `scripts/refresh-spdx-license-ids.sh <tag>` regenerates the file and
refuses a suspiciously short download.
- `Hypatia.SPDX` reads the file at compile time, so the escript needs no
`priv/` at run time.
- **Owner action:** the vendored list is third-party data. I have not
added a `.reuse/dep5` entry for it, because licence records are yours to
make.

## Testing

Run with plain Elixir 1.14 and a local JSON stand-in, as for #969. Hex
isn't reachable from here, and CI's `mix test` uses the real Jason.

- **Overall:** 84 of 84 pass across `secret_scanning_alerts_test.exs`,
`root_hygiene_test.exs`, `scorecard_ingestor_license_test.exs`,
`scorecard_ingestor_security_policy_test.exs` (regression guard) and the
new `spdx_test.exs`.
- **Behaviour tests fail on the old code.** The 10 tests from the first
commit fail against `main`. The two `notes.txt` tests fail against the
previous `reuse_licenses?/1`.
- `parse_page/1`: the next link, the last page, the proxy header block,
and an error page.
- `collect_pages/2`: two pages joined, a next link to another host
refused, a failed later page failing the whole fetch, the page cap
raising an error, and exactly 50 requests before the cap.
- SC-010: `LICENSE.adoc`, REUSE and `LicenseRef-` pass. A
`.gitkeep`-only `LICENSES/` and a `notes.txt` fail.
- `Hypatia.SPDX`: listed, lowercase, deprecated and `LicenseRef-` ids
count; other names and case variants do not.
- **Formatting:** `mix format --check-formatted` passes on every changed
file. `shellcheck` passes on the refresh script.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01P4yFkxW5Heb8p2YW7ZYs9F)_
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