CodeQL ran on every pull_request push, and PR CI's security / codeql-gate
job polled up to 150 min for that run before ci-ok could pass. With ~30 PRs
pushed at once, the gate jobs held runners while the CodeQL runs they waited
for queued behind them, and PR CI starved for hours.
- codeql.yml: triggers are push to main, a weekly schedule, workflow_call
and workflow_dispatch; pull_request is gone. The open-alert check moves
here, unchanged and still fail-closed, as the `alerts` job (needs
analyze), so every main push and every weekly run fails while any
code-scanning alert is open. Push runs keep cancel-in-progress; called and
scheduled runs get a group of their own. The analysis key is pinned to the
push configuration so dry-run/release uploads do not open configurations
of their own on main.
- _security.yml: codeql-gate removed; callers grant contents: read only.
- dry-run.yml, release.yml: a `codeql` job calls codeql.yml on the
dispatched commit; release verify needs it and requires success.
- select-lanes.sh, require-all-green.sh: the codeql-gate lane is gone from
the selector universe and from ci-ok's job map; no other lane changes.
- tests: the fail-closed contract follows the check to codeql.yml and now
also pins the triggers, the derived callers' grants and release verify;
lane fixtures drop codeql-gate.
- SECURITY.md, MAINTAINERS.md: say where CodeQL runs now.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The lint job's single-process cppcheck pass now takes 22-29 minutes:
the src tree grew about 60 % in files in ten days (210 files / 7.4 MB on
2026-10-04, 335 / 11.2 MB since 2026-10-12) while the cppcheck flags
stayed the same. Three runs on 2026-10-13 (38330125249, 38345850562,
38370789949) were cancelled at the 30-minute cap with cppcheck still
running and every earlier lint step clean. A cancelled lint is a hard
stop for the release graph, and the dry run's tests need lint to pass.
45 minutes puts the bound back where only a genuine hang reaches it.
Parallel cppcheck (-j) would be faster, but it changes cppcheck's
whole-program checks; it is left for its own change with a
findings-equivalence proof.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Ten files changed on both sides; every conflict was a union of independent
additions:
- workflows (_lint.yml, _test.yml): keep the branch's lane `if:` and main's
pinned ubuntu-24.04 runner with its reason.
- internal/cbm/cbm.h: main's callable-identity and module-doc fields plus the
branch's configured-test fields.
- Makefile.cbm: both new test files; prod_lsp_all.o keeps main's
.build-config dependency and the branch's coverage flags.
- scripts/test.sh: main's setup-scripts contract becomes Step 0y3, ahead of
the branch's 0z series and the contracts-only exit.
- src/foundation/subprocess.c: main's cbm_posix_close_fds_from (it closes the
error descriptor too).
- src/mcp/mcp.c: main's repository default base for detect_changes, with the
early returns released through the branch's detect_release.
- src/pipeline/lsp_resolve.h, tests/test_main.c: both includes, suites and
start-up steps (main's git environment reset first).
- scripts/memory-core-baseline.txt: recounted on the merged tree and lowered
(mcp.c 773, config_toml_edit.c 73).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
pr.yml's `changes` job now lists the changed files (renames with their old
path too), runs scripts/ci/select-lanes.sh in a credential-less step and
outputs tier, full, lanes and the pr-smoke matrix. security, lint and test
pass the lanes to their reusable workflows, which take a `lanes` input
defaulting to "all": dry-run.yml and release.yml pass none and run exactly
as before (setup-matrix output for every broad/shard combination is
byte-identical; the TSan matrix moved into setup-matrix with the same legs).
Under a selection, setup-matrix keeps only the tagged legs of the selected
lanes (an untagged leg is an error, never a silent drop) and each lane job
has an `if:` on its lane. pr-smoke and memwaste run on their lanes instead of
the old product regex. Docs-only PRs (T0a) get a `contracts` job running the
new `scripts/test.sh --contracts-only` (Step 0 contracts, no build). ci-ok
needs contracts, lists the run's jobs (actions: read) and passes LANES/JOBS
to require-all-green.sh; shard-completeness passes --lanes.
tests/test_lane_wiring_contract.sh (Step 0z4) pins it: dry run and release
pass no lanes and the inputs default to all, pr.yml wires the selection
through, and every lane is served by a tagged leg or an `if:` naming it.
The smoke-fixture contract now checks the selector's decisions for the
smoke-harness paths instead of the removed regex; select-lanes.sh joins the
venue-parity interface probes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Distilled from #2109 by Rares Popa. Two slices only; the rest of that PR is
either frontend work the UI rebuild makes redundant, or a gating change that
needs re-siting first.
1. Three jobs move from ubuntu-latest to ubuntu-24.04
_lint.yml `lint` and `lint-mem`, and _test.yml `test-diag`, each install a
toolchain from an apt.llvm.org repo whose name contains the release:
deb http://apt.llvm.org/noble/ llvm-toolchain-noble-20 main
deb http://apt.llvm.org/noble/ llvm-toolchain-noble-21 main
noble IS 24.04. So these jobs already depend on a specific release while
asking for whatever ubuntu-latest happens to mean that morning. The day
GitHub advances ubuntu-latest, all three break at once, on a schedule
nobody here chose, and the failure surfaces as an apt error far from its
cause. Pinning the runner to the release the repo line already names costs
nothing and removes the trapdoor.
2. actions/attest-sbom -> actions/attest
attest-sbom is deprecated. Its remaining behaviour is to print
"::warning::actions/attest-sbom has been deprecated" and forward to
actions/attest with NODE_OPTIONS set, passing sbom-path through unchanged.
Calling attest directly yields the same attestation minus the wrapper.
Verified rather than assumed, because this sits on the release path:
59d89421af93a897026c735860bf21b6eb4f7b26 is exactly tag v4.1.0 of
actions/attest, and sbom-path is a real input on that action at that SHA.
The step keeps no continue-on-error, so a failed SBOM attestation still
fails the release. That is the intended behaviour and the comment now says
so: an unattested SBOM is worse than an absent one, because the artifact
ships looking complete.
Deliberately NOT taken from #2109, each for its own reason:
- the actionlint job, as sited. _lint.yml is consumed by pr.yml, dry-run.yml
and release.yml, pr.yml gates its whole test stage on `if lint succeeded`,
and release.yml gates build on needs.lint.result == 'success'. A job there
is merge-blocking and release-blocking with no branch-protection edit to
make it visible, and this one curls a tarball from github.com releases with
no cache and no fallback. actionlint is worth having; not on that path, and
not before it has been run against current main.
- the test-package-wrappers node matrix. It reruns the Go and PyPI suites a
second time per OS to vary a Node version only the npm leg reads.
- everything frontend: the test-frontend job, .node-version, and the
dependabot graph-ui block. The UI is being rebuilt from the ground up, so
gating on the current one buys coverage of a surface that is going away.
Co-authored-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
apt.llvm.org rotates its per-major noble snapshot channels; the
llvm-toolchain-noble-22 channel transiently emptied, breaking every lane that
installs clang from it. #2124 already moved Dockerfile.msan (clang-22 ->
clang-21) for the same reason. The two GitHub-hosted lanes that install from
apt.llvm.org were still on 22 and would break identically on the next
rotation: _test.yml test-diag (ASan+UBSan) and _lint.yml lint-mem
(clang-analyzer, clang-tidy). Repin both to clang-21 / clang-tidy-21 /
llvm-toolchain-noble-21 (the channel #2124 verified apt.llvm.org serves on
amd64+arm64). The macOS LSan lane's Homebrew llvm@22 pin is intentional and
unrelated to apt churn (left unchanged); the clang-format-20 CI-lint lane is
untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Three fixes from the v0.10.7 release incident (2026-08-18):
1. build (and smoke/soak) required 'not failed' instead of explicit success.
failure() does not cover a needed job that TIMED OUT (conclusion
'cancelled'), so lint hitting its 15-min timeout cascaded test into
'skipped' and the pipeline published with the whole test matrix and
asan-soak silently skipped. build now requires lint success plus either
test success or the sanctioned skip_tests input; smoke/soak require build
success explicitly.
2. The tag is inputs.version verbatim: dispatching a bare '0.10.7' published
a release the installers can never resolve (they fetch
releases/download/v<version>/...), and under immutable releases the
mis-named tag cannot be retagged or its name reused. A preflight job now
refuses any non-v-prefixed version before anything runs.
3. Lint's 15-min timeout was one slow-runner day away from cancelling a
normally-5-min job; raised to 30 so only a genuine hang can hit it.
Release-path only (workflow_dispatch); adds one ~5s preflight job; no PR-CI
gating, cost, or trigger changes.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The MSan and memory-analyzer jobs drove docker and make directly from
workflow steps, and the diag step used folded `run: >`. All four are
venue-parity violations: a venue may provision, plumb artifacts, or call
a canonical leg script, and nothing else. Anything that actually
exercises the product belongs inside a scripts/ entry so that every
venue runs the same code instead of each workflow growing its own
slightly different invocation.
So the docker work moves to scripts/ci/msan-lane.sh (build | run | all)
and the analyzer gate to scripts/ci/lint-mem.sh, both of which the local
paths already reach through run.sh and the Makefile. The folded step
becomes `run: |`.
Found by the contract itself, running as step 0j of the local Linux leg.
It had never run against these jobs, because they were added and then
exercised only through GitHub CI -- which is exactly the gap the local
ladder exists to close, and the reason the contract runs as step zero of
every leg rather than as a job of its own.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The memory-diagnostics report's priority-4 lane (path-sensitive clang-analyzer,
memory checks only) run over all 111 production files. 21 findings triaged;
the real ones, all cold-path (none can explain #581's per-query residual):
LEAKS
- mcp get_architecture: scope_path leaked on the missing-store early return
(REQUIRE_STORE frees only `project`); allocate after the gate.
- pass_definitions: cancellation mid-extraction leaked the pass-owned result
cache including already-extracted entries; mirror the end-of-pass cleanup.
- store package-boundary scan: the row-scan abort path freed the node arrays
but not the boundary accumulators or their duplicated package strings.
- cbm quarantine set: a duplicate path line leaked the replaced value (and a
fresh key copy -- the table borrows key pointers); a partial strdup failure
leaked the surviving half. Reuse the stored key for duplicates.
- pass_githistory: unchecked malloc/strdup -- an OOM dereferenced NULL and a
failed strdup leaked the index cell. Allocate before claiming the slot.
NULL/UB
- cli config subcommand: NULL argv with nonzero argc slipped the guard (the
inner `argv &&` shielded only the help comparison) into argv[0].
- store bfs_multi: a negative max_results broke out before any row was
written, then freed fields of an unwritten negative-index slot. Clamp.
- pass_calls emit_http_async_edge: the service-pattern call sites pass a NULL
target behind a hand-duplicated URL predicate; a drift between the copies
turned target->id into a null deref. The callee is now total.
- sqlite_writer: both leaf-array OOM paths left leaf_count stale with a NULL
array, walking pb_finalize_* into leaves[0]; consistent empty state routes
them to the existing root=0 failure return.
HARDENED (invariants true but invisible to path-sensitive analysis)
- Leiden CSR + aggregate arrays, SCC adjacency: calloc + endpoint guards, so
a future degree/collection miscount degrades benignly instead of UB.
- SCC cycle fill: the ncyc==0 no-slot invariant made local.
RECORDED FALSE POSITIVES (no code change)
- yaml sequence starts (loop bound == alloc bound), cypher agg arrays (same
count both sides), mcp read_message ch (assigned by fgetc each iteration),
pkgmap clean buffer, mcp csize (Tarjan: ncomp>=1 when nverts>=1), vendored
verstable x2.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
GATE + LANES (user decision: runner cost accepted)
- make lint-mem (local triage) and lint-mem-ci (gating: vendored-filtered,
any remaining finding fails). The gate is green because every false
positive above was restructured for provability -- calloc'd fill-cursor
arrays, explicit Tarjan invariant, zeroed buffer tails, min-1-element
allocations -- never suppressed.
- make diag: pinned newest-LLVM ASan/UBSan lane with straighter stacks.
- CI: lint-mem job (_lint.yml) and test-diag job (_test.yml), both on the
pinned LLVM 22 apt toolchain. Cost disclosure: roughly +25-40 min and
+25-60 min (ccache-warm) per push respectively.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Add scripts/check-no-test-skips.sh (run from lint) which fails the lint phase on any plain SKIP() or direct tf_skip_count manipulation; only SKIP_PLATFORM() (for genuinely platform-specific tests) is tolerated. Add FAIL() and SKIP_PLATFORM() helpers to the test framework and convert the remaining SKIP()/perf-gated skips across the suite into pass-or-fail assertions, so a suite that cannot meet its preconditions reports a red failure instead of a silent skip.