Task — engineering-spec@1

Run the test suite in CI on ubuntu

doneTASK-IMP-128
module improvement · class product · priority p1 · created 2026-07-20 · shipped null
depends on none · blocks none

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:

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

Success Metrics

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

Dependencies

None blocking. Adjacent to TASK-IMP-127: both concern guarantees the release makes that are not mechanically enforced.

AI Authorship Disclosure

1. Description (normative)

2. Acceptance criteria

3. Edge cases

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:

  1. 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.
  2. 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.
  3. 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)

ClauseVerb demandsCited test assertsVerdict
1.1 MUST run on ubuntu for push/PRexecution evidenceAC 1: workflow YAML declares the jobINSUFFICIENT — ISS-005
1.2 failing test MUST fail the job, MUST NOT be maskednon-zero propagation and absence of suppressionAC 2: fixture failure exits non-zero and no step swallows itsufficient on both halves
1.3 MUST execute rather than self-skipthe skip branch is not taken on LinuxAC 3: asserts assertions run instead of the GNU-tar skipsufficient
1.4 MUST report pass/fail/skip countsthree counts on stdoutAC 4: asserts all three emittedsufficient

§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.