Run the test suite in CI on ubuntu
TASK-IMP-128: Run the test suite in CI on ubuntu
Summary
No workflow runs scripts/tests/run_all.sh. The suite is enforced only by a local pre-commit hook, on one macOS laptop, and only for commits that trigger it. One suite file has consequently never executed anywhere. Add an ubuntu job that runs the suite, converting a local convention into an enforced gate and giving the suite its first run on a platform other than the author's machine.
Problem
Two facts, both measured on 2026-07-20 against HEAD:
- No file under
.github/workflows/invokesscripts/tests/run_all.sh. - No file under
.github/workflows/invokestools/install/tests/test_release_assets.sh.
The second compounds the first. test_release_assets.sh self-skips on macOS with the message "needs GNU tar (BSD/macOS host); runs on ubuntu/CI". It does not run on ubuntu/CI, because CI does not run the suite at all. The test names a CI that would execute it, and that CI does not exist - so the file has never run on any machine, while reporting a skip that reads like coverage deferred rather than coverage absent.
The wider consequence is the point. The pass figure this release was judged against is enforced only by the local pre-commit hook: on the committer's machine, and only for code changes. A contributor without the hook, or any docs-only commit, bypasses it entirely. Every "suite green" claim in the 1.0.0 release rests on a human having run it by hand on one macOS host.
That single-platform enforcement has a measured cost. Five bash-3.2 and BSD-userland defects were fixed on 2026-07-19 - including a sed -i misuse that made a tamper-detection test pass unconditionally on macOS. A suite that ran on ubuntu would have failed on the GNU-vs-BSD divergence rather than silently passing.
Proposed Solution
Add a job to the existing CI that checks out the repo on ubuntu-latest and runs bash scripts/tests/run_all.sh. On ubuntu the suite executes all 37 files including test_release_assets.sh, giving that file its first execution and giving every other file its first run on a non-macOS host.
Alternatives Considered
- A matrix across ubuntu and macOS. Deferred, not rejected: ubuntu is where the never-executed file runs and where the userland differs from the development host, so it is the higher-value half. A macOS leg can be added once the ubuntu leg is green and its runtime is known.
- Rely on the pre-commit hook and document that contributors must install it. Rejected: the hook is machine-local and untracked, skipped for docs-only commits, and bypassed by
--no-verify- which was itself used during this release. A convention enforced only by the honour system on one machine is the condition this task exists to end. - Run only
test_release_assets.shin CI. Rejected: it fixes the one file that has never run and leaves the other 36 still enforced by a single laptop.
Success Metrics
- Primary: the suite runs on
ubuntu-lateston every push and pull request, and a deliberately broken test fails the workflow. Baseline today: zero suites execute in CI and no test failure can fail a workflow. - Guardrail:
test_release_assets.shreports an executed result rather than a skip on the ubuntu leg - the file that has never run, runs.
Scope
In scope: one CI job invoking the existing suite entrypoint, and whatever minimal fixes the suite needs to pass on ubuntu.
Out of scope / Non-Goals
- Rewriting or restructuring the suite - this task runs what exists.
- A macOS or Windows leg (deferred; see Alternatives).
- Coverage measurement or thresholds.
- Removing or weakening the local pre-commit hook - it stays as the fast local signal.
Dependencies
None blocking. Adjacent to TASK-IMP-127: both concern guarantees the release makes that are not mechanically enforced.
AI Authorship Disclosure
- Tools used: Claude (Fable 5) running the CyberOS task-author skill inside Cowork.
- Scope: spec drafted from the 2026-07-19 B4 verification pass. The two absence claims are grep results against
.github/workflows/at HEAD, re-run on 2026-07-20. An earlier draft of this finding stated that a release dispatch would givetest_release_assets.shits first execution; that was wrong - the payload job runsrelease-assets.sh, the producer, not the test - and the corrected claim (no workflow runs it, so it has never executed) is what this spec carries. - Human review: scope and granularity approved at the 2026-07-20 PLAN gate; both HITL gates are recorded human verdicts.
1. Description (normative)
- 1.1 A CI job MUST run
bash scripts/tests/run_all.shonubuntu-latestfor every push and pull request to the default branch. - 1.2 A failing test MUST fail the job - the suite's non-zero exit MUST propagate to the workflow conclusion, and MUST NOT be masked by
continue-on-error,|| true, or an ignored exit code. - 1.3
tools/install/tests/test_release_assets.shMUST execute rather than self-skip on the ubuntu leg, and its result MUST be included in the suite outcome. - 1.4 The job MUST report the per-suite pass, fail, and skip counts in its log so a reader can see which files ran without re-running them locally.
2. Acceptance criteria
- [x] AC 1 (traces_to: #1.1) - the workflow file declares a job on
ubuntu-latestwhose run step invokesscripts/tests/run_all.sh, triggered on push and pull_request - test:tools/install/tests/test_ci_runs_suite.sh::t_suite_job_declared - [x] AC 2 (traces_to: #1.2) - a deliberately failing test in a fixture suite causes the runner to exit non-zero, and no step in the job path swallows that exit code - test:
tools/install/tests/test_ci_runs_suite.sh::t_failure_propagates - [x] AC 3 (traces_to: #1.3) - on a Linux host
test_release_assets.shruns its assertions instead of taking the GNU-tar skip branch - test:tools/install/tests/test_ci_runs_suite.sh::t_release_assets_executes_on_linux - [x] AC 4 (traces_to: #1.4) - the suite entrypoint emits pass, fail, and skip counts to stdout - test:
tools/install/tests/test_ci_runs_suite.sh::t_counts_reported
3. Edge cases
- Suites that assume BSD userland may fail on first ubuntu run. That is the finding, not a blocker: each failure is either a real portability defect to fix or a test to make platform-explicit. The job MUST NOT be merged with failures suppressed to make it green.
- A suite requiring network or credentials MUST skip explicitly with a stated reason rather than fail, and the skip MUST be visible in the counts required by 1.4.
- Suite runtime on a cold ubuntu runner is unknown; if it exceeds the job default timeout the timeout MUST be raised rather than the suite trimmed.
run_all.shinvoked from a different working directory MUST still resolve its suite paths - CI checks out to a path unlike any local one.- Security-class: the job runs repository test code on a CI runner with the default token. It MUST NOT require elevated permissions, and MUST NOT be granted secrets - no suite in
run_all.shneeds them, and granting them would expose secrets to test code on pull requests from forks.
Audit
§1 — Verdict summary
Four §1 clauses, four ACs, five edge cases including one security-class row. Machine floor clean after one structural fix. Three of four clauses pass TRACE-006. §1.1 does not, and the gap is not a drafting slip that can be fixed by rewording — it is a genuine bootstrap limitation that needs an operator decision. This audit HALTS rather than resolving it, per the rubric's clause_verb_untested routing.
§2 — Findings
ISS-001 — TRACE-003: cited test file existed in neither new_files nor the repo (RESOLVED)
All four ACs cite tools/install/tests/test_ci_runs_suite.sh. The file does not exist and frontmatter new_files read - (none), so every AC traced to a path nothing would ever create. Caught by the machine floor at four line offsets. Material: all four ACs were untestable as written. Resolved: new_files now declares the file.
ISS-002 — the original finding this task came from was factually wrong (RESOLVED)
An earlier statement of this defect claimed a release dispatch would give test_release_assets.sh its first execution. That was false — the payload job runs release-assets.sh, the producer, not the test. Carrying the wrong claim into the spec would have made the Problem section unfalsifiable in the reader's favour. Resolved: the corrected claim (no workflow invokes it, so it has never executed anywhere) is what the spec carries, and the correction is disclosed in AI Authorship rather than quietly substituted.
ISS-003 — "suite green" needed its actual enforcement named (RESOLVED)
The spec initially described the CI gap without stating what the 1.0.0 pass figure actually rests on. That is the consequence a reader needs. Resolved: Problem now states the figure is enforced only by a machine-local pre-commit hook, on one macOS host, bypassed by docs-only commits and by --no-verify — which was itself used during this release.
ISS-004 — the job must not be merged green-by-suppression (RESOLVED)
First ubuntu run will likely surface real BSD-vs-GNU failures. Without a bound, the path of least resistance is continue-on-error to get the PR merged, which would ship a job that cannot fail — the exact defect class this task exists to end. Resolved: §1.2 forbids masking, and §3 states explicitly that the job MUST NOT be merged with failures suppressed.
ISS-005 — TRACE-006: §1.1's verb is not assertable by any in-repo test (OPEN — needs_human)
Clause verb-demand. §1.1 says a CI job "MUST run bash scripts/tests/run_all.sh on ubuntu-latest for every push and pull request". The verb is run, and per RUBRIC.md §9 a run-verb demands evidence of execution.
Cited test assertion. AC 1 (t_suite_job_declared) parses the workflow YAML and asserts a job is declared with the right runner, invocation and triggers. That is a declaration check. It would pass identically against a workflow that GitHub never executes — a syntactically valid job on a disabled workflow, in a repo with Actions turned off, or gated behind a condition that is never true.
Comparison. Declaration is strictly weaker than execution. The assertion does not exercise the clause's verb.
This is not fixable by rewording without making it worse. Weakening §1.1 to "MUST declare a job" would produce a clause whose test passes while no test ever runs in CI — a rule that documents its own non-enforcement, which is the precise pattern this task and its two siblings were authored to eliminate. Strengthening the test is not available either: no test inside the suite can assert that CI ran the suite, because the suite only runs if CI runs it. The task that makes CI enforcement real cannot be enforced by CI until it has landed.
Operator decision required. Three options, none auto-selectable:
- Keep §1.1's verb and accept that its evidence is the workflow's first green run on the merge commit — an operator-verified step recorded at the acceptance gate, not an AC. Honest, and puts the evidence where it actually exists.
- Split §1.1 into a declaration clause (testable, AC 1 as written) plus a separate operator-attested criterion for the first observed run. Preserves a 1:1 clause-to-test mapping at the cost of an extra clause.
- Accept AC 1 as sufficient on the grounds that declaration is the only lever this repo controls, and record the limitation in §3 rather than as an open finding. Fastest; leaves a known verb/evidence gap in the corpus.
I have not chosen. Option 1 is what I would recommend, but the rubric routes clause_verb_untested to the operator and the whole point of this batch is that gates which cannot fail must not be waved through by the party that authored them.
§3 — TRACE-006 semantic sufficiency (per clause)
| Clause | Verb demands | Cited test asserts | Verdict |
|---|---|---|---|
| 1.1 MUST run on ubuntu for push/PR | execution evidence | AC 1: workflow YAML declares the job | INSUFFICIENT — ISS-005 |
| 1.2 failing test MUST fail the job, MUST NOT be masked | non-zero propagation and absence of suppression | AC 2: fixture failure exits non-zero and no step swallows it | sufficient on both halves |
| 1.3 MUST execute rather than self-skip | the skip branch is not taken on Linux | AC 3: asserts assertions run instead of the GNU-tar skip | sufficient |
| 1.4 MUST report pass/fail/skip counts | three counts on stdout | AC 4: asserts all three emitted | sufficient |
§4 — Resolution
Four findings resolved, one open and routed to the operator. Score = 9/10. Status remains draft; this audit does NOT authorise the draft -> ready_to_implement transition. Re-audit after the ISS-005 decision is recorded in source_decisions.
End of TASK-IMP-128 audit.