From 93f88bcc555de399f9e567fbebd9624363a4401d Mon Sep 17 00:00:00 2001 From: Igor Somov Date: Tue, 9 Jun 2026 06:24:37 -0300 Subject: [PATCH] Split baseline CI and release verification --- .github/workflows/ci.yml | 163 +++++----------------------------- .github/workflows/release.yml | 145 ++++++++++++++++++++++++++++++ docs/testing.md | 11 +++ src/main.zig | 39 ++++++-- 4 files changed, 212 insertions(+), 146 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3e998b..12ec5d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,150 +9,31 @@ on: permissions: contents: read -jobs: - zig: - uses: nullclaw/nullbuilder/.github/workflows/zig-ci.yml@v1 - permissions: - contents: read - with: - binary_name: nullpantry - artifact_prefix: nullpantry - zig_version: "0.16.0" - test_command: zig build test --summary all - targets_json: >- - [ - {"os":"ubuntu-latest","target":"linux-x86_64","zig_target":"x86_64-linux-musl"}, - {"os":"ubuntu-latest","target":"linux-aarch64","zig_target":"aarch64-linux-musl"}, - {"os":"macos-latest","target":"macos-aarch64","zig_target":"aarch64-macos"}, - {"os":"windows-latest","target":"windows-x86_64","zig_target":"x86_64-windows"} - ] - - full-import: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - run: zig build check-full-import -Dengine-profile=full --summary all - - run: zig build test-profile -Dengine-profile=full --summary all - - postgres-contract: - runs-on: ubuntu-latest - services: - postgres: - image: pgvector/pgvector:pg16 - env: - POSTGRES_USER: nullpantry - POSTGRES_PASSWORD: nullpantry - POSTGRES_DB: nullpantry_test - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U nullpantry -d nullpantry_test" - --health-interval 5s - --health-timeout 5s - --health-retries 20 - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - run: zig build postgres-contract --summary all - env: - NULLPANTRY_TEST_POSTGRES_URL: postgres://nullpantry:nullpantry@127.0.0.1:5432/nullpantry_test - - run: zig build pgvector-contract --summary all - env: - NULLPANTRY_TEST_PGVECTOR_URL: postgres://nullpantry:nullpantry@127.0.0.1:5432/nullpantry_test - - redis-contract: - runs-on: ubuntu-latest - services: - redis: - image: redis:7-alpine - ports: - - 6379:6379 - options: >- - --health-cmd "redis-cli ping" - --health-interval 5s - --health-timeout 5s - --health-retries 20 - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - run: zig build redis-contract --summary all - env: - NULLPANTRY_TEST_REDIS_URL: redis://127.0.0.1:6379/0 +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true - qdrant-contract: - runs-on: ubuntu-latest - services: - qdrant: - image: qdrant/qdrant:v1.14.1 - ports: - - 6333:6333 - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - run: | - for i in {1..30}; do - curl -fsS http://127.0.0.1:6333/healthz && exit 0 - sleep 1 - done - exit 1 - - run: zig build qdrant-contract --summary all - env: - NULLPANTRY_TEST_QDRANT_URL: http://127.0.0.1:6333 - - lancedb-contract: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - run: python -m pip install lancedb - - run: zig build lancedb-contract --summary all - env: - NULLPANTRY_TEST_LANCEDB_URI: ${{ runner.temp }}/nullpantry-lancedb - NULLPANTRY_TEST_LANCEDB_COMMAND: python - - clickhouse-contract: - runs-on: ubuntu-latest - services: - clickhouse: - image: clickhouse/clickhouse-server:24.8 - ports: - - 8123:8123 - env: - CLICKHOUSE_SKIP_USER_SETUP: 1 - steps: - - uses: actions/checkout@v4 - - uses: mlugg/setup-zig@v2 - with: - version: "0.16.0" - - run: | - for i in {1..60}; do - curl -fsS http://127.0.0.1:8123/ping && exit 0 - sleep 1 - done - exit 1 - - run: zig build clickhouse-contract --summary all - env: - NULLPANTRY_TEST_CLICKHOUSE_URL: http://127.0.0.1:8123 - - lucid-contract: - runs-on: ubuntu-latest +jobs: + baseline: + name: baseline / ${{ matrix.target }} + runs-on: ${{ matrix.os }} + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - target: linux + os: ubuntu-latest + - target: macos + os: macos-latest + - target: windows + os: windows-latest steps: - uses: actions/checkout@v4 - uses: mlugg/setup-zig@v2 with: version: "0.16.0" - - run: zig build lucid-contract --summary all + - name: Run baseline tests + run: zig build test --summary all + - name: Compile default binary + run: zig build --summary all diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bdb5fd1..57d59a8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,152 @@ permissions: contents: read jobs: + full-import: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: zig build check-full-import -Dengine-profile=full --summary all + - run: zig build test-profile -Dengine-profile=full --summary all + + postgres-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + postgres: + image: pgvector/pgvector:pg16 + env: + POSTGRES_USER: nullpantry + POSTGRES_PASSWORD: nullpantry + POSTGRES_DB: nullpantry_test + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U nullpantry -d nullpantry_test" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: zig build postgres-contract --summary all + env: + NULLPANTRY_TEST_POSTGRES_URL: postgres://nullpantry:nullpantry@127.0.0.1:5432/nullpantry_test + - run: zig build pgvector-contract --summary all + env: + NULLPANTRY_TEST_PGVECTOR_URL: postgres://nullpantry:nullpantry@127.0.0.1:5432/nullpantry_test + + redis-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + redis: + image: redis:7-alpine + ports: + - 6379:6379 + options: >- + --health-cmd "redis-cli ping" + --health-interval 5s + --health-timeout 5s + --health-retries 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: zig build redis-contract --summary all + env: + NULLPANTRY_TEST_REDIS_URL: redis://127.0.0.1:6379/0 + + qdrant-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + qdrant: + image: qdrant/qdrant:v1.14.1 + ports: + - 6333:6333 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: | + for i in {1..30}; do + curl -fsS http://127.0.0.1:6333/healthz && exit 0 + sleep 1 + done + exit 1 + - run: zig build qdrant-contract --summary all + env: + NULLPANTRY_TEST_QDRANT_URL: http://127.0.0.1:6333 + + lancedb-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - run: python -m pip install lancedb + - run: zig build lancedb-contract --summary all + env: + NULLPANTRY_TEST_LANCEDB_URI: ${{ runner.temp }}/nullpantry-lancedb + NULLPANTRY_TEST_LANCEDB_COMMAND: python + + clickhouse-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + services: + clickhouse: + image: clickhouse/clickhouse-server:24.8 + ports: + - 8123:8123 + env: + CLICKHOUSE_SKIP_USER_SETUP: 1 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: | + for i in {1..60}; do + curl -fsS http://127.0.0.1:8123/ping && exit 0 + sleep 1 + done + exit 1 + - run: zig build clickhouse-contract --summary all + env: + NULLPANTRY_TEST_CLICKHOUSE_URL: http://127.0.0.1:8123 + + lucid-contract: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: mlugg/setup-zig@v2 + with: + version: "0.16.0" + - run: zig build lucid-contract --summary all + release: + needs: + - full-import + - postgres-contract + - redis-contract + - qdrant-contract + - lancedb-contract + - clickhouse-contract + - lucid-contract uses: nullclaw/nullbuilder/.github/workflows/zig-release.yml@v1 permissions: contents: write diff --git a/docs/testing.md b/docs/testing.md index cfe4dc7..a622526 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -60,6 +60,17 @@ zig build test-full-engine `zig build test -Dserver-tests=true` is still available when a script wants the old default behavior without changing the step name. `-Dfull-import-tests=true` adds aggregate import coverage to `zig build test`; it is ignored for `-Dengine-profile=minimal`. +## GitHub Gates + +Pull requests into `main` run the required `CI` baseline matrix on Linux, macOS, and Windows. Each matrix job runs: + +```sh +zig build test --summary all +zig build --summary all +``` + +These checks are intentionally small enough to stay required for every merge. Full engine import checks, external runtime contracts, and release artifact builds run from the `Release` workflow on `v*` tags or manual dispatch before release artifacts are published. + ## External Contracts Concrete backend contracts stay opt-in because they require external services: diff --git a/src/main.zig b/src/main.zig index 70d8843..356f7e7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -696,7 +696,38 @@ fn permissionsGroupOrWorldWritable(permissions: std.Io.File.Permissions) bool { } fn supportsPosixOwnerCheck() bool { - return builtin.link_libc and switch (builtin.os.tag) { + return switch (builtin.os.tag) { + .linux => true, + .windows, .wasi, .emscripten, .freestanding, .other => false, + else => builtin.link_libc, + }; +} + +fn openHandleOwnedByEffectiveUser(handle: std.posix.fd_t) !bool { + if (comptime builtin.os.tag == .linux) { + const linux = std.os.linux; + var statx_buf: linux.Statx = undefined; + switch (linux.errno(linux.statx( + @intCast(handle), + "", + linux.AT.EMPTY_PATH | linux.AT.NO_AUTOMOUNT, + .{ .UID = true }, + &statx_buf, + ))) { + .SUCCESS => {}, + else => return error.UntrustedRuntimeConfig, + } + if (!statx_buf.mask.UID) return error.UntrustedRuntimeConfig; + return statx_buf.uid == linux.geteuid(); + } else if (comptime builtin.link_libc) { + var stat_buf: std.c.Stat = undefined; + if (fstat(@intCast(handle), &stat_buf) != 0) return error.UntrustedRuntimeConfig; + return stat_buf.uid == geteuid(); + } else return false; +} + +fn supportsSecureCreateMode() bool { + return switch (builtin.os.tag) { .windows, .wasi, .emscripten, .freestanding, .other => false, else => true, }; @@ -705,9 +736,7 @@ fn supportsPosixOwnerCheck() bool { fn validateOpenHandleTrust(handle: std.posix.fd_t, permissions: std.Io.File.Permissions) !void { if (permissionsGroupOrWorldWritable(permissions)) return error.UntrustedRuntimeConfig; if (comptime supportsPosixOwnerCheck()) { - var stat_buf: std.c.Stat = undefined; - if (fstat(@intCast(handle), &stat_buf) != 0) return error.UntrustedRuntimeConfig; - if (stat_buf.uid != geteuid()) return error.UntrustedRuntimeConfig; + if (!try openHandleOwnedByEffectiveUser(handle)) return error.UntrustedRuntimeConfig; } } @@ -745,7 +774,7 @@ fn loadImplicitHomeConfigFile(out: *ConfigFileEnv, config_path: []const u8) !voi fn ensureRuntimeHomeRoot(home_path: []const u8) !void { const trimmed = std.mem.trim(u8, home_path, " \t\r\n"); if (trimmed.len == 0) return; - const permissions: std.Io.Dir.Permissions = if (comptime @hasDecl(std.Io.Dir.Permissions, "fromMode")) + const permissions: std.Io.Dir.Permissions = if (comptime @hasDecl(std.Io.Dir.Permissions, "fromMode") and supportsSecureCreateMode()) .fromMode(0o700) else .default_dir;