Skip to content

ci: timeout-minutes on every job of every reusable workflow (BSE-001 wave 1) - #63

Merged
noahgift merged 5 commits into
mainfrom
bse-m1-wave1
Sep 5, 2026
Merged

noahgift merged 5 commits into
mainfrom
bse-m1-wave1

Conversation

@noahgift

@noahgift noahgift commented Sep 5, 2026 •

Copy link
Copy Markdown
Contributor

BSE-001 wave 1 — .github

Spec: paiml/infra docs/specifications/build-system-enhancement.md §4 wave 1 (BSE-05/06). Ticket: PMAT-230 (infra; this repo has no .pmat). Bundled per D4.

Decision record (spec §0, Noah 2026-09-04): "Lets implement entire list of suggestions using paiml-implement and assume fable 5.1 will orchestrate from inside of ~/src/infra, but will delegate all work to sub-agents (i.e. 3 max and heavily use "agy" and focus on minimal token usage)." Prompt line: /paiml-implement docs/specifications/build-system-enhancement.md M1 --budget-turns 60 --quorum auto --status-every 10 D1=yes D2=180/2 D4=bundle D5=3 D6=keep D7=15.

What changed

  • timeout-minutes on every job that is not a reusable-workflow call (a uses: job cannot carry one — GitHub's schema forbids it, measured with actionlint; the callee jobs in paiml/.github carry theirs in that repo's wave-1 PR).
  • Pre-existing explicit timeouts are kept exactly as they were (never lowered, never raised).
  • .github/actionlint.yaml declares the fleet's self-hosted labels so actionlint can serve as the acceptance linter (without it every clean-room runs-on is an unknown-label error).
  • sovereign-ci.yml is the reusable ci / gate every fleet repo calls; callers cannot set a timeout on a uses: job, so these callee timeouts are the only bound on forjar's ci/bench/authorize and every other caller. Pre-existing values (security 10, provenance 5, gate 5, bench 60) are kept as they were. Two pre-existing labels (gpu, lambda-labs on unified-gate.yml gpu-gates) are declared for actionlint but NOT changed: lambda-labs must never be a runner (fleet rule), so that job targets a label with no runner — recorded in the infra receipt as a follow-up, out of this ticket's scope.

Basis (basis=)

PMAT-230 — timeout-minutes for every job in paiml/.github's workflows

Commands run

# actionlint self-hosted label discovery (this repo's runs-on labels)
cd /home/noah/src/.github/.claude/worktrees/bse-m1 && actionlint -shellcheck= -pyflakes=
# → 2 unknown labels found: gpu, lambda-labs (unified-gate.yml:453) — added to
#   .github/actionlint.yaml alongside the brief's [clean-room, perf-solo, merge-queue, intel]

# fleet job-duration gather (2026-07-24 cutoff), one script, six repos:
#   repos/paiml/{aprender,paiml-mcp-agent-toolkit,forjar,whisper.apr,rmedia,infra}
gh api "repos/paiml/$r/actions/runs?per_page=100&status=completed" \
  --jq '.workflow_runs[]|select(.created_at>="2026-07-24")|.id' | head -80
gh api "repos/paiml/$r/actions/runs/$id/jobs" \
  --jq '.jobs[]?|select(.conclusion=="success")|[.name, (((.completed_at|fromdateiso8601)-(.started_at|fromdateiso8601))/60)]|@tsv'
# → /run/user/1000/paiml-implement/wave1/dotgithub-jobs.tsv (1978 rows)

Only rows shaped <caller job> / <callee job> were used as history for a
reusable-workflow job (GitHub's own naming for a uses: call). Bare job
names (no /) are a caller repo's own unrelated job that happens to share
a name (e.g. infra's own ci.yml has jobs literally named validate and
gate, coincidentally identical to this repo's ci.yml — that data was
excluded, since infra does not call paiml/.github/ci.yml; ci.yml here
has no workflow_call: trigger at all and is never invoked by another repo).

T = max(15, ceil(1.5·p99), p99+20), never below an existing explicit value.

ci.yml (own CI, push+pull_request only — never called by another repo)

job basis n p99 (min) T (min) note
validate no-history[U] 0 – 15 no cargo in steps (yamllint/actionlint fetch only)
gate no-history[U] 0 – 15 no cargo (jq needs-check only)

pr-gate.yml (workflow_call)

job basis n p99 (min) T (min)
authorize fleet authorize / authorize 67 1.83 22

max(15, ceil(1.5*1.83)=3, 1.83+20=21.83) → 22.

sovereign-ci.yml (workflow_call) — existing explicit values are floors

job basis n p99 (min) computed T existing applied
test fleet ci / test 102 34.37 55 60 60 (unchanged, computed < existing)
lint fleet ci / lint 105 20.32 41 60 60 (unchanged)
coverage fleet ci / coverage 108 39.97 60 60 60 (unchanged)
bench fleet bench / bench 25 48.22 73 60 73 (bumped)
security fleet ci / security 127 2.97 23 10 23 (bumped)
provenance fleet ci / provenance 139 2.67 23 5 23 (bumped)
gate fleet ci / gate + bench / gate 90 0.92 21 5 21 (bumped)

All ≤ the 90-minute PR/merge-group cap; none is workspace-test; none is
schedule-only.

unified-gate.yml (workflow_call; no fleet caller ran in the 2026-07-24+

window — whisper.apr/release.yml calls it but has 0 completed Release
runs in the sampled window, and paiml-mcp-agent-toolkit's caller is
release.yml.disabled)

job basis invokes cargo? T (min)
lint-gate no-history[U] yes (make -f .../clean-room/Makefile) 30
cpu-gates no-history[U] yes (make -f .../clean-room/Makefile) 30
gpu-gates no-history[U] yes (make -f .../clean-room/Makefile) 30
gate no-history[U] no (aggregator, needs-check only) 15

actionlint.yaml deviation from the brief

Brief's list was [clean-room, perf-solo, merge-queue, intel]. actionlint
still reported gpu and lambda-labs as unknown (unified-gate.yml:453,
the gpu-gates job's runs-on: [self-hosted, gpu, lambda-labs], pre-existing
and unchanged by this ticket). Both were added so the file matches what this
repo's runs-on: actually uses; named in open_questions per the ticket's
own instruction ("If actionlint then reports a self-hosted label this repo
uses that is not in that list, add it and name it in open_questions").

Verification (Fable re-ran every claim)

  • acceptance: for f in .github/workflows/*.yml; do yq -e '[.jobs[]|select(has("uses")|not)|select(has("timeout-minutes")|not)]|length==0' $f; done && actionlint -shellcheck= -pyflakes= → exit 0
  • falsifier: delete one timeout-minutes → acceptance exit 1 (observed, reverted); drop the pool label from one runs-on → exit 1 (observed, reverted)
  • gate: cargo test --workspace is the discovered fallback gate and was not run for a workflow-only diff; CI on the required check is the gate (spec §2.3 rule 7)
  • D1 quorum (3 independent agy lanes over all six wave-1 workflow diffs; conversations 6930430f-b062-4572-80f5-8969355d87b7, ec52f76e-72b6-452c-b8cc-6025f4184648, 3236723d-3717-41eb-84e1-08048457fc4b): Q2 clean 3/3 (no network fetch, unpinned action, secrets reference or host write added); Q1 no check weakened other than the named demotions; Q3 runs-on changes only add the one pool label. Pre-existing whole-repo state the lanes flagged (explicit timeouts above the caps that predate this branch) is left untouched by design: the rule is never lower a value the base had.

Merge

Touches .github/workflows/*: web-UI merge click (spec §2.4). Order: wave 1 merges before infra's wave-2 deploy (spec §5). pv contract: NotRun (workflow YAML, named).

🤖 Generated with Claude Code

https://claude.ai/code/session_01CBLhQPF2KihBqK6Y34Sec1

roadmap-valid (standing brief 2026-09-05, infra PMAT-239)

One step at the end of the security job (bare metal, no container): if docs/roadmaps/roadmap.yaml exists, pmat work validate decides; no roadmap is the only NOT-RUN and prints so; an absent pmat, or a pmat without work validate, is RED — never a silent skip. Measured: pmat work validate exists since pmat v3.0.6 (git grep on tags v3.31.0…v3.37.0), so the fleet pin (3.31.0 today, 3.37.0 after BSE-10b) carries it; pmat exits 2 on an unknown subcommand; the step's shell was executed on the workstation against four fixtures (no roadmap → 0; valid → 0; notes: as a sequence → 1; no pmat on PATH → 1). Unmeasured: whether pmat is on PATH inside this job on the fleet runners — no sovereign-ci job used pmat before — so the step prepends /opt/fleet-bin/bin, ~/.cargo/bin and /usr/local/cargo/bin; if it is absent there, every caller's ci / gate goes RED at merge, by design, and the fix is the fleet-bin deploy, not a skip. Consequence at merge: any caller whose roadmap fails validation is red at ci / gate — paiml-mcp-agent-toolkit master until #1192 merges (its PMAT-671 row), infra main validates. The validate red on this PR is unchanged: a runner ENV defect (infra PMAT-234).

D1 quorum on the step (3 lanes, 3/3 FAIL on placement; conversations 191e5995-56c1-4b73-abb6-fb863a7172ce, 102b7eaa-262c-4e3a-b9ed-8191d406d378, ffc32360-714d-47d1-8686-a8f72f9e7f40), and what changed: the step sat after the if: always() ownership-restore teardown → moved straight after checkout (d592f55). Q4c "a roadmap off the canonical path is a silent pass" → the NOT-RUN line now names every roadmap-like file it found and did not validate (::warning::); it is not made red because eleven sovereign-ci callers carry a root-level roadmap.yaml today (aprender, bashrs, decy, entrenar, pmat, pforge, probar, renacer, simular, trueno-graph, ruchyruchy) and turning them red is Noah's decision, not the step's. Q6 "rehost outside security" → kept: it is the first bare-metal job with a checkout, and pmat's presence inside the containerised jobs is unmeasured; a red there for a missing binary would block the fleet for the wrong reason. Measured blast radius (27 caller checkouts on this workstation, pmat work validate): every caller that has docs/roadmaps/roadmap.yaml validates; no caller goes red for validation at merge. Q2/Q3 clean 3/3. The step's shell was executed against five fixtures after the move (no roadmap → 0; misplaced roadmap.yaml → 0 with the warning; valid → 0; notes: as a sequence → 1; no pmat → 1).

…T-230)

BSE-05 (BSE-001 wave 1): callee jobs of unified-gate.yml, ci.yml, pr-gate.yml (sovereign-ci.yml already timed every job); a uses: caller cannot carry a timeout, so these are the bound on every fleet caller. Pre-existing explicit timeouts untouched; plain insertions.

Pmat-Ticket: PMAT-230
noahgift and others added 4 commits September 5, 2026 12:48
… validate before ci / gate (Refs PMAT-230)

BSE-09c v1.2 scope (infra PMAT-239). Fail-closed: absent pmat or a pmat without 'work validate' is RED; no roadmap is the only NOT-RUN and says so. 'pmat work validate' exists since v3.0.6 (measured: git grep on tags v3.31.0..v3.37.0 in the pmat tree), so the fleet pin 3.31.0 carries it. Whether pmat is on PATH inside this job on the fleet runners is UNMEASURED (no sovereign-ci job used pmat before); the step searches /opt/fleet-bin/bin, ~/.cargo/bin and /usr/local/cargo/bin first. Placed in job 'security' (bare metal, no container).

Pmat-Ticket: PMAT-230

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hat it did not measure (Refs PMAT-230)

D1 quorum 3/3 (conversations 191e5995-56c1-4b73-abb6-fb863a7172ce, 102b7eaa-262c-4e3a-b9ed-8191d406d378, ffc32360-714d-47d1-8686-a8f72f9e7f40): the step sat after the if: always() ownership-restore teardown and so inherited success() behind it; moved above it, straight after checkout. Q4c: a roadmap-like file off pmat's canonical path is now named in a ::warning:: NOT-RUN line rather than silently passed; it is not made RED because eleven sovereign-ci callers carry a root-level roadmap.yaml today (measured) and that decision is Noah's. Measured blast radius at merge: every caller with docs/roadmaps/roadmap.yaml validates (rc=0 across 27 checkouts); no caller goes red for validation. Q2/Q3 clean 3/3.

Pmat-Ticket: PMAT-230

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…l closed (Refs PMAT-234)

The `validate` job's 'Lint workflow YAML' step ran `uvx yamllint -d relaxed
.github/workflows/` on the intel runners and died with `ModuleNotFoundError:
No module named 'yamllint.cli'` — a broken ephemeral uv environment reported
as a lint failure, on every PR to this repo (#63).

The linter is now PROVISIONED: infra's `stack-tool-yamllint` (PMAT-234) pins
yamllint 1.38.0 into /opt/fleet-bin, which is root-owned, first on the runner
PATH and survives a rust-cache prune. One declared version, installed once,
not resolved per job — the same argument that moved cargo-audit, cargo-deny
and cargo-mutants out of per-run installs.

The step prepends /opt/fleet-bin/bin, REFUSES when the linter is absent
(naming the resource that installs it, so the remedy is one line and not a
bisect), and prints `yamllint --version` before the verdict — a green from an
unknown linter version is a claim nobody can reproduce.

`|| true` is not coming back and there is no "skip if missing" branch: an
absent verifier is a NO-GO, never a pass. The comment keeps the whole history
(pip install -> uvx -> fleet-bin) including the measurement that made arming
this step safe in the first place: yamllint exits 0 on this tree, 0 errors and
279 line-length warnings, and warnings do not affect its exit status.

MEASURED, not asserted. The step's own shell was extracted from this file and
executed against two fixtures: PATH with no yamllint -> exit 1 with the
::error:: line; PATH with a yamllint that prints a version -> exit 0.
actionlint -shellcheck= -pyflakes= exits 0 on the tree.

Pmat-Ticket: PMAT-234
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…fallback (Refs PMAT-234)

D1 lane 1 (agy 31a2cad5-d161-427a-b632-597b671e8214): command -v could match any other yamllint on PATH if fleet-bin's were absent, defeating the fail-closed claim. The provisioned tool is now named by its path.

Pmat-Ticket: PMAT-234

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@noahgift
noahgift marked this pull request as ready for review September 5, 2026 18:39
@noahgift
noahgift enabled auto-merge (squash) September 5, 2026 18:39
@noahgift
noahgift merged commit 4453399 into main Sep 5, 2026
2 of 4 checks passed
@noahgift
noahgift deleted the bse-m1-wave1 branch September 5, 2026 18:59
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