Commit Graph
384 Commits
Author SHA1 Message Date
Martin Vogel c04e322d61 Merge branch 'main' into dependabot/github_actions/actions/setup-python-7.1.0 2026-11-01 23:20:24 +01:00
Martin Vogel 9f278b7ed3 Merge pull request #2880 from DeusData/dependabot/github_actions/ossf/scorecard-action-2.5.0
build(deps): bump ossf/scorecard-action from 2.4.4 to 2.5.0
2026-11-01 22:58:22 +01:00
Martin Vogel edc2301b02 Merge pull request #2503 from DeusData/dependabot/github_actions/msys2/setup-msys2-2.33.0
build(deps): bump msys2/setup-msys2 from 2.32.0 to 2.33.0
2026-11-01 21:29:38 +01:00
dependabot[bot] 083bc94d5e build(deps): bump ossf/scorecard-action from 2.4.4 to 2.5.0
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.4 to 2.5.0.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](https://github.com/ossf/scorecard-action/compare/2d1146689b8cda280b9bc96326124645441f03bc...fa7117db83c52d787ae5a89f350999701505d78e)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 18:34:52 +00:00
dependabot[bot] 11f20c578b build(deps): bump actions/setup-python from 7.0.0 to 7.1.0
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 7.0.0 to 7.1.0.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/5fda3b95a4ea91299a34e894583c3862153e4b97...6d4fd22b20465ccc0f7354476588e8df7c9ca0e0)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: 7.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 18:34:44 +00:00
Martin Vogel 358501f6c7 Merge pull request #2727 from DeusData/chore/codeql-action-v4.39.0
ci: bump the CodeQL actions to v4.39.1 together and group their Dependabot updates
2026-11-01 19:30:11 +01:00
dependabot[bot] 1f56c7d6bb build(deps): bump msys2/setup-msys2 from 2.32.0 to 2.33.0
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.32.0 to 2.33.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](https://github.com/msys2/setup-msys2/compare/66cd2cce69caa17b53920067426061ca1de3a884...ec48f7c5447b3140e2b088413ae3a55687bccb6e)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-version: 2.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 16:06:58 +00:00
Martin Vogel caad969154 ci: pin every CodeQL action to v4.39.1
Main already moved upload-sarif to v4.39.1 (#2779). Move init and analyze to
the same commit, 9b3254085494b55a0f7c4c2b3d2d52cc712ce370 (the commit the
v4.39.1 tag points to), so all three CodeQL actions run one version again.
The pin comment follows the form Dependabot wrote for upload-sarif. The
codeql-action Dependabot group added earlier on this branch keeps the three
in lockstep from here on.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 16:12:47 +01:00
Martin Vogel 45ceb80ccd Merge origin/main into chore/codeql-action-v4.39.0
Conflict in .github/workflows/scorecard.yml resolved to the main version:
main already pins upload-sarif to 9b3254085494b55a0f7c4c2b3d2d52cc712ce370
(v4.39.1, #2779), which supersedes this branch's v4.39.0 pin.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 16:12:13 +01:00
dependabot[bot] 54e079587d build(deps): bump actions/attest from 4.1.0 to 4.2.2
Bumps [actions/attest](https://github.com/actions/attest) from 4.1.0 to 4.2.2.
- [Release notes](https://github.com/actions/attest/releases)
- [Changelog](https://github.com/actions/attest/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest/compare/59d89421af93a897026c735860bf21b6eb4f7b26...1e69f48acb82d1966a394da916b4c1698aa569d6)

---
updated-dependencies:
- dependency-name: actions/attest
  dependency-version: 4.2.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-11-01 15:09:38 +00:00
Martin Vogel 6c0ce230ee Merge pull request #2779 from DeusData/dependabot/github_actions/github/codeql-action/upload-sarif-4.39.1
build(deps): Bump github/codeql-action/upload-sarif from 4.38.1 to 4.39.1
2026-11-01 16:08:06 +01:00
Martin Vogel e3a9fa34ed ci: let apt fail over between mirrors instead of hanging
The hosted Ubuntu x64 runners read the archive through
mirror+file:/etc/apt/apt-mirrors.txt. With Acquire::Retries above 0
(including the apt default of 3), a failover from a dead first entry
of that list (connection refused, DNS failure or a stalled host) to
the next entry goes idle: apt-get fetches a few InRelease files from
the second mirror and then waits until the step timeout. With
Acquire::Retries=0 the same failover completes. Seen on noble
(apt 2.8.3) and jammy (apt 2.4.14).

Every apt step this branch hardened now picks the retry count at
runtime, applied to both update and install: 0 when the runner has the
mirror list (moving to the next mirror is the retry), 3 otherwise
(arm64 runners and any host with plain sources keep their retries).
--error-on=any, the http and https timeouts and the step timeouts are
unchanged.

Proof: the step text extracted from the edited _build.yml (update plus
install of zlib1g-dev), run under bash -e in ubuntu:24.04 and
ubuntu:22.04 containers with runner-shaped mirror lists:
- first mirror refused: exit 0 in 8 s / 9 s, every index and deb
  fetched from the second mirror
- first mirror stalled (192.0.2.1): exit 0 in 68 s / 69 s, one 30 s
  timeout each for update and install, then the second mirror
- healthy first mirror: exit 0 in 7 s / 9 s
- no mirror list, plain sources: the prefix keeps Retries=3, exit 0
  in 8 s / 7 s
- every mirror dead: update exits 100 in under 1 s, install skipped
- revert check, the previous step text with a dead first mirror
  (refused or stalled): idle with no Packages index at the 180 s cap
  on both releases

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 12:32:45 +01:00
Martin Vogel a545a0aa8c ci: fail apt-get update on any fetch error
apt-get update exits 0 when index fetches fail: it only prints
"W: Some index files failed to download. They have been ignored, or
old ones used instead." and the job breaks later at install time with
a misleading "Unable to locate package", or installs from stale lists.
Add --error-on=any to every apt-get update this branch hardens, placed
after the -o options as in test-infrastructure/Dockerfile.msan, so the
update step itself fails when an index cannot be fetched.

Proof in ubuntu:24.04 (apt 2.8.3) and ubuntu:22.04 (apt 2.4.14)
containers with a runner-shaped mirror+file:/etc/apt/apt-mirrors.txt
source and the same -o options CI passes:
- every mirror unreachable: exit 100 with the flag (E: lines), exit 0
  without it (W: lines, zero Packages lists), on both apt versions
- first mirror refused or stalled (30 s connect timeout) and the real
  archive second: the Ign: failover lines do not trip the flag, exit 0
  with every index fetched (measured with Acquire::Retries=0)
- the image sources with the flag: exit 0 and no Ign/W/E lines, so the
  option is understood by apt 2.4 and 2.8
- green hosted-runner logs (ubuntu-24.04 and ubuntu-22.04 legs) show no
  Ign/Err/W/E lines in the update step

With Acquire::Retries=3 a mirror failover leaves apt idle after the
InRelease failover, with or without the flag and also for a plain
apt-get update; that apt behaviour predates this change and is
unchanged by it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 10:08:28 +01:00
Martin Vogel 339cd51660 ci: fail fast when an apt mirror stalls
On 2026-11-01 two Linux jobs hung in `sudo apt-get update` right after
"Get:5 https://archive.ubuntu.com/ubuntu noble-security InRelease" until
their job limit; the build never started in either:

- PR 2641, test-tsan x86 (run 41251045506, job 124688326533): the
  "Install deps (Ubuntu)" step ran 2 h until the 120-minute job limit.
- PR 2395, test-unix gcc x86 (run 41256855177, job 124705168264): the
  same step ran 4 h until the 240-minute job limit.

Both logs show the azure mirror ignored after about 15 s, the fallback to
archive.ubuntu.com, and then no output until the cancel. The default apt
http timeout (120 s) did not end the stall.

Rule applied to every workflow step whose run block calls apt:

- A step-level timeout-minutes. 10 for steps that only provision packages
  (green runs on 2026-11-01 and the last green dry run: 5 to 76 s); 20 for
  the three pr.yml steps that also run scripts/build.sh (green: 246 to
  331 s). A stalled mirror now fails the step in minutes instead of
  holding the runner until the job limit.
- `-o Acquire::Retries=3 -o Acquire::http::Timeout=30
  -o Acquire::https::Timeout=30` on every apt-get update and install,
  with the -o options before the subcommand as in
  test-infrastructure/Dockerfile.msan.

Nothing else changes: no action, runner, package or step-order changes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-11-01 09:23:09 +01:00
Martin Vogel 9bb891b669 ci: move macOS runners to macos-15 and pin the deployment target to 14.0
GitHub retires the macos-14 runner image on 2026-11-02, and its brownouts
already cancel our macos-14 jobs. Move every macos-14 runner reference to
macos-15 (arm64): the release darwin-arm64 build, the unix-macos14 and
tsan-mac test legs, the smoke-mac PR leg (its two matrix.os guards move in
lockstep), the release smoke, both soak legs, the memwaste event lane, the
bug-repro board, the cache warmer and the all-platform smoke. Lane names
stay unchanged: select-lanes.sh, ci-ok and the lane contracts key on them,
never on the runner label.

Two broad legs already ran on macos-15 (_test.yml BROAD_UNIX, _smoke.yml
BROAD_UNIX) and smoke.yml listed both images, so the moved legs would have
duplicated them. Those duplicates are dropped; PR CI keeps exactly the
same legs.

Without an explicit minimum, clang stamps the binary with the SDK's
default (LC_BUILD_VERSION minos), so a release built on macos-15 would
refuse to start on macOS 14. Makefile.cbm now folds
-mmacosx-version-min=14.0 into CC/CXX next to ARCHFLAGS for Apple targets
(detected from the compiler's target, not the host), so it reaches every
compile and link, vendored objects included, on every build path.

Proof on macOS 26.6 / SDK 26.5: the production binary built from
origin/main reports minos 26.0; with this change it reports minos 14.0,
with zero warnings under -Werror, no weak imports and an identical
undefined-symbol set. The test runner (direct make, no env.sh) is minos
14.0 too; platform and daemon_ipc suites pass.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-30 23:44:25 +01:00
Martin Vogel 282f0e6412 fix(ci): post the PR acknowledgement without its hidden maintainer note
The template opens with an HTML-comment note for maintainers, and the
workflow posted the file verbatim, so every acknowledgement comment
carried text a contributor cannot see but a tool reads. Our own PR
screening holds every comment with such hidden text, which parked nine
contributor PRs on a note we wrote ourselves.

Strip HTML comments (an unterminated one runs to the end, as GitHub
renders it) into a temporary body file and post that. The posted text is
byte-identical to what rendered before. A template whose only content is
the note now counts as blank, which is the documented off switch.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-25 18:40:50 +01:00
dependabot[bot] a9434d88e9 build(deps): Bump github/codeql-action/upload-sarif
Bumps [github/codeql-action/upload-sarif](https://github.com/github/codeql-action) from 4.38.1 to 4.39.1.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/1c5b675653bb5c22dbe9b12b556ec555138e09fd...9b3254085494b55a0f7c4c2b3d2d52cc712ce370)

---
updated-dependencies:
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.39.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-24 05:44:06 +00:00
Martin Vogel 0b81154813 ci: bump the CodeQL actions to v4.39.0 together and group their Dependabot updates
Dependabot opened one PR per CodeQL action (#2504 init, #2505 analyze, #2506 upload-sarif), each moving to 4.38.2. Merging them one at a time would leave init and analyze on different versions in between. This moves all three to v4.39.0 (6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee, the commit the v4.39.0 tag points to) in one change, and adds a Dependabot group so future CodeQL Action updates arrive as a single PR.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-21 00:34:05 +02:00
Martin Vogel 453b21e680 ci(codeql): PR scan per language inside PR CI, full scan on main
Bring CodeQL back to pull requests as jobs of PR CI itself (needed by
ci-ok, no polling), narrowed to first-party code, while push to main,
the weekly run, the dry run and the release keep the full scan.

- codeql.yml: one leg per language (c-cpp, javascript-typescript,
  python, actions), scope in .github/codeql/codeql-config.yml. The full
  scan traces the whole build, vendored C included. The PR scan
  (light: true) analyses each selected language's whole first-party
  scope; c-cpp compiles the third-party objects before tracing
  (scripts/build.sh --vendored-only) and then traces the first-party
  build plus the vendored files the PR changes (--reuse-vendored
  --retrace-from, make -W). Diff-informed analysis (codeql-action
  v4.38.1, forced on for PR calls) only restricts reported alerts.
  The alert check moves into each leg and reads the analysed ref.
  Push and PR runs cancel superseded runs per language; dispatched
  runs never cancel. Compiler cache off so every compile is traced.
- pr.yml: a codeql job runs the legs of the selected codeql-* lanes;
  ci-ok needs it.
- select-lanes.sh: codeql-cpp/-js/-py/-actions lanes, selected by a
  change in their scope (any C/C++ incl. vendored; JS/TS, Python and
  workflows inside the config's paths minus paths-ignore) and by every
  full run (undeterminable diff, workflows, build, CodeQL machinery).
  --codeql-scope LANGUAGE prints a list's in-scope paths.
- codeql-retrace-vendored.sh: the vendored C files a PR changes (empty,
  never narrowing, when the diff cannot be determined).
- Makefile.cbm: cbm-third-party builds the vendored prod objects only.
- tests: the gate contract pins the new layout; the selector contract
  pins that a C change always selects the C scan, an undeterminable
  diff selects every language, and a vendored-only change retraces
  exactly the changed vendored files. Selector fixtures, ci-ok's job
  map, the replay's codeql-action failures (lane codeql-cpp) and the
  interface probes follow.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-20 23:09:09 +02:00
Martin Vogel 5afdfcaac9 ci(codeql): run CodeQL on main, dry run and release, not per PR
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>
2026-10-20 22:37:22 +02:00
Martin Vogel 57b557ced8 fix(test-impact): close the role table's fail-opens, follow literals through macros
The first commit of this branch is the Smart CI change as of the rebase
onto 9f85f104. A review of it found places where the engine could run
fewer tests than a change needs; each now runs everything or selects the
right tests, with a test that fails when its fix is reverted.

Engine:
- A `#define NAME "literal"` line counted as naming a role but selected
  nobody, so the tests that start the blocking-git probe through their
  marker macros would not have run. The scan now follows NAME, as a whole
  word, to the code that uses it, through chains of up to 4 macros; a
  define it cannot follow runs everything.
- main dispatches through the role table, so no edge leads from main to a
  handler. A table entry the engine cannot read, or whose handler is not
  exactly one function, was skipped; it now runs everything.
- In a runner file, only a test case names a role's literal. The runner's
  own helper that reads the marker no longer counts as a spawner, so a
  role only the runner names runs everything again.
- A handler reached first by a parse gap or by the spawn lift was never
  lifted. Role lifts and parse gaps now run to a fixpoint, before the
  runner check and again after the spawn lift.
- A reached sink that main calls (the blocking-git probe) counted as a way
  into main and hid the real one: #2644 now names the start-up path main
  -> cbm_index_supervisor_capture_build_fingerprint.
- No pointer past the end of a role line; the RUNNER_REACHED note clamps
  its prefix.

CI:
- pkg/npm/README.md, which test_cli reads, runs the test legs instead of
  the docs tier.
- The build rule lists the harness scripts the test legs run:
  run-test-wave.py, path-safety.sh, clean.sh.
- The revert guard rejects the ancestor's coverage when a diff fails.
- The vetoed selection is uploaded (test-selection-vetoed).

Docs: SMART_CI.md states the new fail-safe cases, the version that lacks
the new config keys, the selection base, and that the narrowed leg builds
with gcc while the coverage comes from clang.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-17 20:44:27 +02:00
Martin Vogel 7f6f567840 wip: Smart CI narrows PR CI for real (pre-rebase checkpoint)
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-17 14:48:52 +02:00
Martin Vogel 6382fad7b3 fix(ci): give the wrapper smoke a Go toolchain and a deletable work tree
The v0.12.1 release smoke failed on every leg in Phase 13w, the
package-wrapper install gate added in #2664:

- macOS: the runner images have no Go on PATH, and
  SMOKE_REQUIRE_WRAPPERS=1 rightly fails without one. pr-smoke and the
  three release smoke jobs now set up Go from pkg/go/go.mod, with the
  same pinned step the wrapper test job uses.
- Windows: `mkdir -m 700` changes the mode in a second step, which MSYS2
  cannot do under the smoke profile's owner-only DACL. mktemp -d sets
  the mode at creation.
- Linux: the runner's older Go downloaded the 1.26.1 toolchain into the
  smoke profile's HOME. Go writes its module cache read-only, so the
  harness could not delete its profile afterwards. All Go state now stays
  in the wrapper's own work tree, and its cleanup restores write
  permission before removing it.

npm and PyPI passed on every leg that reached them; the gate itself is
unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-16 22:12:26 +02:00
Martin Vogel 10c7fc3206 fix(pkg): remove the archive size caps from the npm, PyPI and Go wrappers
v0.12.0 could not be installed through npm, pnpm, pip or the Go wrapper on
any platform (#2657): each wrapper refused an archive member above 256 MiB,
and every v0.12.0 binary is 331-335 MB. The v0.11.0 binaries were already
about 300 MB, so the Go wrapper, which has had the cap since v0.10.0, was
broken before; npm and PyPI gained it in v0.12.0. The archive is verified
against the release's checksums.txt before it is unpacked, so a byte cap
only ever refused what we ship.

- npm, PyPI, Go: delete every byte-size cap (compressed archive, per member,
  aggregate, checksum manifest, download size). The checksum, the exact
  member set, path and link refusal, the member count, HTTPS and the
  --version check stay.
- All three honour CBM_DOWNLOAD_URL like install.sh, for testing: https, or
  plain http to an exact loopback authority with no proxy and no redirect.
- Smoke: scripts/smoke-wrappers.sh installs the three wrappers from the
  checkout against the release fixture and runs the binary each one
  installed. smoke-test.sh Phase 13w runs it; SMOKE_REQUIRE_WRAPPERS=1 makes
  it required on the PR smoke lanes and in the release smoke, which gates
  publishing.

Other install paths found broken while checking every channel:
- Homebrew: the formula was still at 0.10.3 and installed the dynamic Linux
  build, which needs glibc 2.38 (Ubuntu 22.04, Debian 12 and RHEL 9 lack it).
  It now installs 0.12.0 with the static -portable Linux assets and tracks
  the newest release in the version contract like the other surfaces.
- Go: `go install .../pkg/go/cmd/codebase-memory-mcp@vX.Y.Z` never worked
  (no pkg/go/v* tags, so Go fetched the whole repository, over its 500 MB
  module limit), and @latest built main's wrapper, whose version constant
  lags each release. The release now also tags pkg/go/vX.Y.Z, and the
  wrapper installs its own module version (the constant only for local and
  untagged builds).
- npm: a stable release also moves the `next` dist-tag to itself; `next`
  still pointed to 0.9.1-rc.1.
- Docs: winget and Chocolatey were listed as install channels but are not
  published; they are removed until they are. Windows arm64 ships and is
  now listed.

Fixes #2657.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-16 18:09:06 +02:00
Martin Vogel 8a6a1047d6 ci(security): pin the ScanCode install; pluggy 1.7.0 breaks it
The license gate installed `scancode-toolkit` unpinned. pluggy 1.7.0
(uploaded to PyPI 2026-10-15 09:50 UTC) no longer finds the hookspecs that
plugincode 32.0.0 declares on a class (`@pre_scan_spec class
PreScanPlugin`), so scancode crashes at import with "ValueError: did not
find any 'pre_scan' hooks" and `security / license-gate` fails on every
run, whatever the tree. The last green gate started at 09:50:14 UTC;
every run since is red. `ci-ok` and the release `verify` job both need the
security workflow, so this blocks every PR and the release.

Install exactly scancode-toolkit 32.5.0 (the version every green run
used) with pluggy constrained to 1.6.0 through PIP_CONSTRAINT, which the
pip that pipx runs reads. The local hint in license-gate.sh names the
same command.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-15 18:16:16 +02:00
Martin Vogel ece0c2d2e1 ci(lint): raise the lint job timeout from 30 to 45 minutes
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>
2026-10-13 19:22:10 +02:00
Martin Vogel 7644e8bae1 ci: upload failing suite logs when a test job fails
The job log of a red test job carries a summary of each failing suite,
not its log, and the only artifact was shard-manifest.txt. A failure
whose summary was not enough could only be understood by reproducing it.

The four jobs in .github/workflows/_test.yml that run the parallel
harness (test-unix, test-windows, test-diag, test-lsan-macos) get one
more step, "Failing suite logs". It runs only when the job failed and
uploads build/c/test-logs/failed/: the logs of the suites that did not
end green, plus results.txt, as the harness leaves them there. One red
suite is typically a few kilobytes; a whole run's logs are about 25 MB.
Retention is 7 days. The artifact names follow the shard-manifest ones
(suite-logs-<os>-<compiler or msystem>-<job index>, suite-logs-diag,
suite-logs-lsan-macos), and the step uses the upload-artifact reference
the file already pins.

A job that failed outside the suite waves has no such directory; the
step then warns and uploads nothing. The step cannot change a job's
result: it is skipped on a green job, and a failed job stays failed
whatever the upload does. Triggers, permissions, job dependencies,
timeouts, the matrix and the shard-completeness download pattern are
unchanged.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-12 20:18:19 +02:00
Martin Vogel 4d9592b5fb Merge pull request #2068 from DeusData/feat/codeatlas-web
CBM Lens Beta: graph exploration and architecture workspace
2026-10-12 05:38:30 +02:00
Martin Vogel dbc6ac486b fix(msan): reports print with fast unwinding; five suites run again
Five suites (grammar_regression grammar_labels pipeline lang_contract
grammar_probe_e) were excluded from the MSan lane for "stack-overflow
under instrumentation". That was never deep recursion. MSan aborted while
REPORTING real uninitialized reads:

- every abort was on the main thread. MSan's "T<n>" is the OS thread id
  and equalled the pid in all five x86-64 aborts (CI job 91868259558);
  the old block read it as a worker-thread ordinal, so the worker-stack
  knob, MSAN_ORIGINS and CBM_WORKERS=1 could never change anything;
- with the default slow unwinder (the image's MSan-instrumented
  libunwind) a report exhausted the main thread's 256 MiB stack, and the
  overflow handler then failed with "nested bug in the same thread";
- with fast_unwind_on_fatal=1 the same test prints the
  use-of-uninitialized-value report instead (measured on one tree and
  test: slow unwind aborts at the stack limit, fast unwind reports
  wit/scanner.c:13).

The two reads are fixed in the previous commits of this series, so:

- MSAN_OPTIONS defaults to fast_unwind_on_fatal=1 (the build already
  keeps frame pointers);
- MSAN_EXCLUDE is down to cli, whose install-path failure is a separate,
  still-undiagnosed cause;
- the known-red block, the header (it still claimed CI runs without
  exclusions) and the MSAN_ORIGINS and stack comments say what is true;
- the partial-lane warning and the exclude-name typo check lived in a
  branch behind `elif true` that never ran. They now run in the
  per-suite path, the check as a case match instead of a grep -q pipe
  that pipefail can misreport;
- the comments in scripts/ci/msan-lane.sh and above the test-msan job
  in _test.yml no longer say the five suites overflow.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 18:26:26 +02:00
Martin Vogel 1a073040ef Audit all Lens source URLs with scoped fixture exceptions
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 18:23:58 +02:00
Martin Vogel cf41bdc6d6 Secure Lens history reads and verify shipped browser dependencies
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 18:12:30 +02:00
Martin Vogel 0392068d49 Merge main into CBM Lens Beta
Reconcile the complete Atlas and Lens stream with current main while retaining both histories. Preserve architecture functionality, current process and watcher safeguards, test infrastructure, and dependency security updates. Add coverage for Git-environment isolation combined with child-local overrides.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-11 16:57:48 +02:00
Martin Vogel 674a731fcc ci(test-impact): give the engine full history; publish maps with an incomplete suite
The test-impact engine certifies the history it diffs and refuses a shallow
clone ("Graft or shallow state prevents history certification"). The shadow
job, the gate-mode select-tests job and the main-branch producer all checked
out with fetch-depth 2. So every shadow answer since #2458 was run_all:
GIT_UNAVAILABLE, then POLICY_UNAVAILABLE, because the merge base's
.codebase-memory.json is read through the same git facts. That was 24 of 24
measured PR runs. All three jobs now check out full history (about 400 MiB,
these jobs only).

The producer never published a bundle either. coverage-map.py exits 1 when a
suite fails under coverage: the map is still written and that suite's tests
are incomplete. test-impact-publish.sh ran it under set -e, so the publish
aborted. The failing suite was test_impact_git. Its hostile-diff probe
re-enters the runner as a child, which inherited CBM_TEST_COVERAGE_DIR,
started a coverage run of its own, and refused the per-test LLVM_PROFILE_FILE
it inherits.

Two changes:
- The probe now clears CBM_TEST_COVERAGE_DIR, as it already clears
  CBM_TEST_ONLY_FILE. The child's profile then goes to the spawning test, like
  any other child process.
- publish treats exit 1 as a written, conservative map (incomplete tests run
  whole), with a warning that names the suite. It fails only on exit 2.

Proof:
- Depth-2 clone at f9ce3865: test-impact select answers run_all
  [GIT_UNAVAILABLE, POLICY_UNAVAILABLE] with "Graft or shallow state ...", as
  in CI. Same commit with full history: graph certified, run_all
  [RUNNER_REACHED] (the change reaches the runner's main).
- Coverage runner (Homebrew LLVM, macOS), test_impact_git: before, 43 passed
  and 2 failed ("a coverage run starts with LLVM_PROFILE_FILE=/dev/null");
  after, 45 passed. coverage-map.py --suite test_impact_git: 45 tests,
  0 incomplete, exit 0.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-09 01:59:34 +02:00
Martin Vogel 3379c864eb Merge main into ci/main-push-concurrency for the release batch
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-07 18:20:39 +02:00
Martin Vogel 867e5f883d Merge origin/main into the smart-CI branch
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>
2026-10-05 18:53:37 +02:00
Martin Vogel 776bb119ac ci(test-impact): team artifact producer, PR shadow and the PR-CI narrowing switch
Producer. test-impact-artifact.yml publishes, on every push to main, the
commit's team artifact bundle: its frozen graph, built incrementally from the
previous bundle, and its per-test coverage map, re-run only for the suites
the push selects (scripts/ci/test-impact-publish.sh). A weekly run rebuilds
both from scratch and fails when the incremental graph's topology differs
from the full one. Bundles are Actions artifacts kept 30 days, never
committed.

Shadow. The report-only test-impact-shadow job also builds the checkout and
runs `test-impact select` against the base side of the PR merge commit
(scripts/ci/test-impact-select.sh). A bundle is used only when it comes from
a successful main-branch producer run for exactly that commit
(scripts/ci/test-impact-bundle.sh). The job stays continue-on-error and
outside ci-ok.

Narrowing switch, PR CI only. While the repository variable
TEST_IMPACT_MODE is `gate`, a select-tests job turns the engine's answer into
the tests the test-unix and test-windows legs run. Unset, nothing changes.
It fails open by construction:
- scripts/test-impact/selection.py is the only place an answer becomes a
  runner filter. Only a well-formed selected lane whose filter matches its
  own suite list narrows; run-all answers, a lane that selects nothing,
  unknown schemas or tokens, and any crash run everything.
- `test` passes a selection only from a successful, narrowed select-tests
  run; ci-ok does not need select-tests. A broken selection costs minutes,
  never coverage.
- scripts/run-tests-parallel.sh launches only the selected suites and keeps
  every guard on the selected list. A selected suite the build does not
  register fails the run, unless the engine saw its registration as
  conditional; then it is dropped with a notice, exactly as a full run of
  that build never runs it. Each runner judges its own tokens, so a selected
  test that does not exist fails its suite.
- The shard manifest records the selection's digest, and
  verify-shard-union.sh requires every shard of a leg to agree on it.
- Dry runs and releases never pass a selection (lane-wiring contract).

Also fixed on the way:
- publish crashed on any run-all push: a run-all lane carries
  "suites": null.
- the git-facts hostile-diff probe re-ran the runner with the parent's
  CBM_TEST_ONLY_FILE still set; under a narrowed run naming its suite it
  re-ran the spawning test, which spawned again. The child now clears it
  (a new test fails without that).
- the select summary crashed on the same null suites.

Contracts: tests/test_test_impact_selection.sh (Step 0z8),
tests/test_harness_selection.sh (Step 2c, the real runner through the real
harness), and new cases in the lane-gate and lane-wiring contracts.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-05 18:52:31 +02:00
Martin Vogel 2633334c59 ci: analyse and score only the newest main commit
CodeQL SAST keyed its concurrency group by commit SHA on push events,
so every merge to main got its own group and nothing was ever
cancelled; OpenSSF Scorecard had no group at all. On 2026-10-04 a day
of 36 merges queued one CodeQL analysis (40-100 minutes each) and one
Scorecard run per merge, ahead of every pull request run: pull request
CI waited hours for a runner, and a main analysis waited 4 hours.

CodeQL now groups by event and by pull request number or ref, so pull
requests keep one analysis each as before, and a newer push to main
cancels the analysis of an older main commit. Scorecard groups by ref
with the same cancellation. Only the newest main commit matters for
both: code scanning tracks the default branch's latest analysis.

DCO keeps its per-commit group on purpose: its push run on main is the
check that catches a merge rewriting sign-offs, so a superseded push
must still be checked, and a DCO job takes seconds.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-10-04 22:06:47 +02:00
Martin Vogel 268a9d8886 Merge pull request #2470 from DeusData/chore/codeql-action-4.38.1
build(deps): bump github/codeql-action to 4.38.1 in one step
2026-10-04 21:01:18 +02:00
Martin Vogel 16e2732819 ci: non-product PRs (T0b) run the contract steps too
Under R1, a T0b push (test-infrastructure/, lint config, graph-ui, memwaste
tooling) selected lint and no test leg, so the static contract steps that
police exactly those files -- the VM scripts, the compose and run.sh wiring,
the venue-parity rules -- ran nowhere. Replayed over September, 13 of the 18
T0b pushes ran no contract step at all.

The R1 table now gives T0b lint + contracts, so those PRs get the
contracts job (scripts/test.sh --contracts-only) as T0a PRs do. The
fixture table's six T0b cases expect it, and the history replay asserts
that every push runs the contract steps (the contracts lane or any test
leg). Comments that described the job as docs-only are updated.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:48:22 +02:00
Martin Vogel d3efe3baec ci: a truncated PR file list selects every lane
The pulls/files API lists at most 3000 files and says nothing when it stops,
and the lane decision is only as complete as that list: a cut list can drop
exactly the file that needed a lane.

select-lanes.sh takes --expect-files N (the PR's changed_files; batch rows
take "expect_files") and decides full with a file_list_truncated reason when
the list has fewer distinct paths than N (a complete list never does: renames
only add their old path), when N is at or over the 3000-file cap, or when the
list itself reaches the cap. The tier still classifies what was listed. An
empty or non-numeric count is a usage error, so a workflow that loses the
count fails loudly instead of skipping the check.

pr.yml passes github.event.pull_request.changed_files. The selector contract
covers short, complete, rename-inflated, at-cap and just-under-cap lists and
the github-output and batch channels; the wiring contract requires the count
to be passed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:46:46 +02:00
Martin Vogel ae5bb0bdea ci: PR shard profile (x86 and Windows whole, arm64 x2)
_test.yml takes a shard_profile input: "standard" (the default; dry run and
release pass nothing and keep ubuntu x3 / windows x2, byte-identical
matrices) or "pr", which pr.yml now uses: ubuntu x86 and Windows run as one
job each and only arm64, the slowest leg, splits in two. A PR runs few legs
and every shard re-pays its setup, so fewer, longer jobs cost less. An
unknown profile is an error. The wiring contract pins both the default and
PR's use of the profile.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:30:57 +02:00
Martin Vogel a8d5a8154f ci: report-only test-impact shadow job on PRs
A new pr.yml job, test-impact-shadow, predicts from the code graph which C
test suites a change can affect and uploads the prediction, so graph-based
selection can be judged against what actually failed before anything gates
on it. It is continue-on-error and not in ci-ok's needs: it can never block
or pass a merge.

scripts/ci/test-impact-shadow.sh --fetch-release downloads the latest
published release archive for the host plus checksums.txt with the job's
read-only token and verifies the SHA-256 (seconds instead of a build, and a
PR that breaks the indexer cannot corrupt its own prediction); pr.yml builds
the checkout only if that fails. --binary/--out then indexes the checkout
into a throwaway HOME/cache with a private CBM_RUNTIME_DIR (short, under
/tmp, so the daemon socket fits sun_path), runs detect_changes against the
merge commit's base at depth 15 and hands it to predict.py. The graph is
only consulted when C sources changed.

scripts/test-impact/predict.py ports the replay's mapping: impacted test
functions -> registered suites (perf excluded; nested RUN_SUITE children
resolve to the registered suite that runs them), changed test files ->
whole suite, fixtures -> suites that name them, grammars -> grammar suites,
and run-all on the selector's full, a test header, test infrastructure, or
a saturated/truncated impact set. It writes prediction.json and a short
step-summary table.

tests/test_test_impact_predict.sh (Step 0z5) pins the mapping on a
synthetic tree; the wiring contract requires the job to stay
continue-on-error; the script joins the venue-parity interface probes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:29:52 +02:00
Martin Vogel adec281d1a ci: run only the selected lanes on PRs; dry run and release unchanged
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>
2026-09-30 23:21:43 +02:00
Martin VogelandRares Popa a45a99c9c7 ci: pin the noble-dependent runners, and attest the SBOM without the wrapper
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>
2026-09-27 23:47:35 +02:00
Martin Vogel a030a831df build(deps): bump github/codeql-action to 4.38.1 in one step
Dependabot opened one PR per sub-action (#2363 init, #2365 analyze, #2364
upload-sarif). CodeQL refuses a split pin: with init at 4.38.1 and analyze
at 4.38.0 the analyze post-step fails with "Loaded a configuration file for
version '4.38.1', but running version '4.38.0'", so neither PR can pass
the codeql gate alone and merging either one would break the gate on main.

This bumps all three to 1c5b675653bb5c22dbe9b12b556ec555138e09fd, which is
the commit the upstream v4.38.1 annotated tag points to.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-26 21:38:23 +02:00
Martin Vogel fcdc6bedb7 Merge branch 'main' into fix/smoke-tool-inventory 2026-09-24 07:39:20 +02:00
Martin Vogel 65d60e72f5 ci(pr): the changes classifier no longer fails open on a large file list
Found while proving the previous commit, in the same step, and the worse
of the two defects because it fails OPEN. The classification was

    if printf '%s\n' "$FILES" | grep -qE '^(src/|internal/|...)'; then

under the runner's `bash -eo pipefail`. grep -q exits on its first match;
once the list is larger than the pipe buffer printf is still writing,
dies of SIGPIPE, and pipefail turns the MATCH into a failed pipeline. The
else branch then writes product=false, and pr-smoke and memwaste are
skipped -- on exactly the PRs that change the most files. The previous
commit's message flagged this as a follow-up; it is fixed here instead.

The list now reaches grep through a here-string, so there is no pipe to
break. The regex is unchanged.

Proof, by executing the workflow's own run block under
`bash -eo pipefail` with a stub gh, ten runs each:

  849 KB list, first line src/a.c   before: product=false 10/10
                                    after:  product=true  10/10
  849 KB list, docs only            product=false before and after
  small docs-only list              product=false
  422 fallback on the real #2246    product=true
  fallback, nonexistent merge ref   rc 128, no output written

It takes roughly 1,500 changed files to cross the buffer, which is why
ordinary PRs never showed it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-21 20:44:19 +02:00
Martin Vogel a262a35724 ci(pr): classify changed files from the merge ref when the files API 422s
The `changes` job decides whether a PR touches product code, and ci-ok
requires it. It asks GitHub for the PR's file list. An earlier fix moved
it off the .diff endpoint, which rejects PRs over 20k lines, onto the
paginated pulls/files endpoint -- but that endpoint renders the diff
server-side as well, and for a PR that regenerates a vendored parser it
answers

    HTTP 422: Sorry, this diff is taking too long to generate.

Observed on #2246 (a 42 MB sql/parser.c): `changes` red, therefore ci-ok
red, for a reason that has nothing to do with the contribution. The
compare endpoint fails the same way, so there is no API route to the
file list for such a PR. Every grammar refresh would hit this.

When the API call fails, the job now takes the list from git instead.
The PR merge ref's first parent is the base, so a name-only diff across
that one merge commit is exactly the PR's changed files. The fetch is
depth 2 with --filter=blob:none into a bare repository under RUNNER_TEMP:
commits and trees only, no blobs, nothing checked out and nothing from
the PR executed. No new action, no new permission, and the API path and
the classification regex are untouched, so ordinary PRs behave as before.
A ::notice:: line records when the fallback was used. If the fetch fails
too, the step exits non-zero and writes no output -- the gate fails
closed, as it did before.

Proof, by executing the workflow's own run block under
`bash -eo pipefail` with a stub gh:

  API path, docs-only list        rc 0    product=false
  fallback, real #2246            rc 0    product=true, the same five
                                          files `git diff --numstat`
                                          reports for the PR; 0.85 s,
                                          188 KB fetched
  fallback, nonexistent merge ref rc 128  no output written

Contract tests that read pr.yml -- security_gate_fail_closed,
smoke_fixture_contract, windows_bundle_contract, venue_parity_contract --
pass.

Not addressed here, noted for a follow-up: the classification pipes the
list into `grep -q` under pipefail, which can report a match as a
failure once the list outgrows the pipe buffer.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-21 20:44:19 +02:00
Tamer cda5e3c9e5 docs(ci): avoid hard-coded MCP tool count
Signed-off-by: Tamer <tamer.elbakkali@gmail.com>
2026-09-20 21:37:43 +02:00
Martin Vogel c58eb61f04 Merge pull request #2243 from DeusData/build/codeql-action-4.38.0
build(deps): bump codeql-action init and analyze together to v4.38.0
2026-09-20 10:10:16 +02:00