Skip to content

fix(ci): clear the last two red workflows + drop stray AGPL licence - #87

Merged
hyperpolymath merged 2 commits into
mainfrom
fix/ci-green-three
Jul 27, 2026
Merged

fix(ci): clear the last two red workflows + drop stray AGPL licence#87
hyperpolymath merged 2 commits into
mainfrom
fix/ci-green-three

Conversation

@hyperpolymath

Copy link
Copy Markdown
Owner

Closes the two remaining red workflows on main and removes a stray licence
file. Three independent defects, each root-caused against the live runs.

1. comprehensive-quality.yml — duplicate top-level key (parse-dead)

The file declared permissions: read-all twice, at lines 4 and 12. GitHub
Actions rejects a workflow with duplicate top-level keys, so it never parsed.

The tell: the run reported as the raw path .github/workflows/comprehensive-quality.yml
instead of its name: (Comprehensive Quality Gates) — GitHub never read far
enough to find the name — with zero jobs and log not found.

Removed the second occurrence. Both were read-all, so no semantic change.

Linting note worth keeping: yaml.safe_load reports this file as
perfectly valid, because PyYAML applies last-wins on duplicate keys. Any
workflow linter built on a stock YAML parser is blind to this entire class of
defect. Detection needs a loader with an explicit duplicate-key constructor.

2. scorecard.yml — reusable permission escalation (startup_failure)

A job-level permissions: block replaces the default grant rather than
extending it. Granting only security-events: write + id-token: write therefore
left contents at none.

The reusable (standards/.github/workflows/scorecard-reusable.yml@81dbf2dd)
declares permissions: contents: read at workflow_call level, and a reusable
may not request more permission than its caller holds — so the job never started.

Added contents: read, with an inline comment so it doesn't regress.

This is the estate-wide pattern tracked in standards#527. Note that #75 aimed at
this symptom and did not clear it; this addresses the actual grant.

3. LICENSES/AGPL-3.0-or-later.txt — removed

Nothing in the repo declares AGPL-3.0. The canonical pair is
MPL-2.0 + CC-BY-SA-4.0, normalised deliberately in #69, and every manifest
agrees (Cargo.toml, codemeta.json, CITATION.cff; LICENSE is the MPL-2.0
text). The repo's own machine-readable policy is explicit:

.machine_readable/6a2/AGENTIC.a2ml:23
# - Never use AGPL license (use MPL-2.0)

Sweep residue from b9aaa6b, landed on main via #83.

Verification

  • Strict duplicate-key scan passes across all 19 workflow files (was 1 failing).
  • comprehensive-quality.yml parses to Comprehensive Quality Gates, 11 jobs.
  • scorecard.yml job analysis resolves to
    {contents: read, security-events: write, id-token: write}.
  • The real test is CI on this PR — please confirm both workflows now start.

Out of scope, but found — worth a follow-up

  • RSR_COMPLIANCE.adoc:56 and RSR_OUTLINE.adoc:74,162 describe the licensing
    as "LICENSE.txt (AGPL + Palimpsest)". That contradicts the repo's actual
    MPL-2.0 licensing and its own no-AGPL policy, and LICENSE.txt does not
    exist (the file is LICENSE). Looks like unedited RSR template boilerplate.
    Left alone deliberately — rewriting compliance docs is a judgement call.
  • Three Pages workflows (pages.yml, casket-pages.yml, jekyll-gh-pages.yml)
    all deploy on push to main — three deployers racing for one environment.

Draft pending CI. Context: dev-notes/betlang-sitrep-2026-07-27.md.

🤖 Generated with Claude Code

Three independent defects, each measured against the live runs on main.

1. comprehensive-quality.yml — duplicate top-level `permissions:` key
   (lines 4 and 12). GitHub Actions rejects a workflow with duplicate
   top-level keys, so the file never parsed: the run reported as the raw
   path `.github/workflows/comprehensive-quality.yml` instead of its
   `name:`, with zero jobs and no retrievable log. Removed the second
   occurrence; both were `read-all`, so there is no semantic change.

   Note for future linting: PyYAML's safe_load accepts duplicate keys
   (last wins) and reports this file as valid. Detecting it requires a
   loader with an explicit duplicate-key constructor.

2. scorecard.yml — reusable permission escalation -> startup_failure.
   A job-level `permissions:` block REPLACES the default grant rather
   than extending it, so granting only security-events + id-token left
   `contents` at none. The reusable declares `permissions: contents:
   read` at workflow_call level, and a reusable may not request more
   permission than its caller holds, so the job never started. Added
   `contents: read` with a comment explaining why it must stay.

3. LICENSES/AGPL-3.0-or-later.txt — removed. Nothing in the repo
   declares AGPL-3.0; the canonical pair is MPL-2.0 + CC-BY-SA-4.0
   (normalised in #69) and every manifest agrees. The repo's own policy
   at .machine_readable/6a2/AGENTIC.a2ml states "Never use AGPL license
   (use MPL-2.0)". The file was sweep residue from b9aaa6b, landed via
   #83.

Verified: a strict duplicate-key scan passes across all 19 workflow
files; comprehensive-quality.yml now parses to "Comprehensive Quality
Gates" with 11 jobs; scorecard's analysis job resolves to
{contents: read, security-events: write, id-token: write}.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hyperpolymath
hyperpolymath marked this pull request as ready for review July 27, 2026 13:32
@hyperpolymath
hyperpolymath merged commit 6661683 into main Jul 27, 2026
1 check passed
@hyperpolymath
hyperpolymath deleted the fix/ci-green-three branch July 27, 2026 13:32
@hyperpolymath

Copy link
Copy Markdown
Owner Author

Correction: the duplicate permissions: key was necessary but not sufficient

The first commit here (af95abd) did not clear comprehensive-quality.yml. CI on
that commit still produced a run with zero jobs and no check run — i.e. the
file was still being rejected at parse time.

My verification was inadequate and I want to be explicit about why. I checked
the file with a strict YAML duplicate-key loader and concluded it parsed. That
only proves YAML validity. GitHub Actions applies an additional layer of
context/expression rules on top of YAML, and a file can be perfectly valid YAML
while Actions refuses it outright.

The actual second defect

.github/workflows/comprehensive-quality.yml:156:9: calling function "hashFiles"
is not allowed here. "hashFiles" is only available in "jobs.<job_id>.steps.*"
contexts

hashFiles() sat in a job-level if:. That is not a permitted context, and
its presence rejects the whole file — which is why none of the 11 jobs ever
materialised. It was also meaningless there: at job level nothing is checked out
yet, so the glob could never match anything.

Fixed in 72bc28b by moving the guard onto the two steps that need it, where it
evaluates after checkout.

Verification (properly, this time)

actionlint .github/workflows/*.yml now reports zero syntax or expression
errors across all 19 workflow files. The only remaining output is shellcheck
style/info (SC2086, SC2129, SC2035, SC2162, SC2034) in unrelated workflows —
none parse-fatal, none touched here.

Already confirmed green by CI on this branch

Governance · Workflow Security Linter · CodeQL · Secret Scanner ·
Hypatia Security Scan · SPARK Theatre Gate · Proofs — including the
scorecard.yml contents: read fix and the AGPL removal.

Estate-wide takeaway

A strict YAML scan is not sufficient to prove an Actions workflow parses.
Given the estate has ~187 unparseable workflow files tracked, actionlint is
the right instrument — it catches this class in seconds and it was already
installed.

hyperpolymath added a commit that referenced this pull request Jul 27, 2026
#88)

The final red workflow on `main`. Everything else is green as of
`29abb4c`.

## Why #87 did not finish the job

#87 fixed a genuine defect — a **duplicate top-level `permissions:`
key** in
`comprehensive-quality.yml` — but that was necessary, not sufficient.
The file
was rejected at parse time for a **second, independent** reason, so it
still
produced runs with **zero jobs and no check run** afterwards.

(#87 was squash-merged just before the follow-up commit landed on its
branch,
so that second fix never reached `main`. This PR carries it.)

## The remaining defect

```
.github/workflows/comprehensive-quality.yml:156:9: calling function "hashFiles"
is not allowed here. "hashFiles" is only available in "jobs.<job_id>.steps.*"
contexts
```

`hashFiles()` sat in a **job-level `if:`**. That is not a permitted
context and
it makes Actions reject the **entire file** — which is why none of the
11 jobs
ever materialised, and why the workflow still shows in the UI as the raw
path
`.github/workflows/comprehensive-quality.yml` rather than its name
(`Comprehensive Quality Gates`): GitHub has never parsed far enough to
read it.

It was also meaningless where it was — at job level nothing is checked
out yet,
so the glob could never match. The guard now sits on the two steps that
need it,
evaluating after checkout, which is both valid and actually correct.

## Verification

`actionlint .github/workflows/comprehensive-quality.yml` → **0
findings**
(was 1 fatal expression error). Across all workflows, the only remaining
actionlint output is shellcheck style/info in unrelated files.

Expect this run to produce **11 jobs**. Job count is the discriminator:
zero
jobs means still parse-dead, non-zero means it parses and any failure is
a real
quality-gate result to triage on its merits.

## Method note worth keeping

A strict **YAML** duplicate-key scan is not sufficient to prove an
Actions
workflow parses — YAML validity and Actions validity are different
things, and
my earlier YAML-only check passed this file while GitHub still rejected
it.
`actionlint` models the real rules and catches this class immediately.
Given the
estate tracks ~187 unparseable workflow files, it is the right
instrument for
that sweep.

Context: `dev-notes/betlang-sitrep-2026-07-27.md`.

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

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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