obzenflow 0.1.2

High-performance event streaming and processing framework
Documentation
name: CI

on:
  push:
    branches: [main]
  pull_request:

jobs:
  dco:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Verify DCO sign-off
        env:
          BASE_SHA: ${{ github.event.pull_request.base.sha }}
          HEAD_SHA: ${{ github.event.pull_request.head.sha }}
        run: |
          python3 - <<'PY'
          from __future__ import annotations

          import os
          import subprocess
          import sys

          base_sha = os.environ.get("BASE_SHA")
          head_sha = os.environ.get("HEAD_SHA")

          if not base_sha or not head_sha:
            print("DCO check skipped: missing BASE_SHA/HEAD_SHA")
            sys.exit(0)

          def ensure_commit(sha: str) -> None:
            try:
              subprocess.check_call(["git", "cat-file", "-e", f"{sha}^{{commit}}"])
            except subprocess.CalledProcessError:
              subprocess.check_call(["git", "fetch", "--no-tags", "--depth", "1", "origin", sha])

          ensure_commit(base_sha)
          ensure_commit(head_sha)

          merge_base = (
            subprocess.check_output(["git", "merge-base", base_sha, head_sha], text=True)
            .strip()
          )

          commits = (
            subprocess.check_output(["git", "rev-list", f"{merge_base}..{head_sha}"], text=True)
            .splitlines()
          )

          missing: list[str] = []
          for sha in commits:
            message = subprocess.check_output(["git", "show", "-s", "--format=%B", sha], text=True)
            if "Signed-off-by:" not in message:
              missing.append(sha)

          if missing:
            print("DCO sign-off missing in one or more commits:")
            for sha in missing:
              subject = subprocess.check_output(
                ["git", "show", "-s", "--format=%s", sha],
                text=True,
              ).strip()
              print(f" - {sha[:7]} {subject}")
            print()
            print("Fix:")
            print(" - For a new commit:      git commit -s")
            print(" - For the last commit:   git commit --amend -s")
            print(" - For all branch commits: git rebase -i --signoff main")
            sys.exit(1)

          print(f"DCO sign-off OK in {len(commits)} commit(s).")
          PY

  fmt:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: 1.93.0
          components: rustfmt
      - name: Verify lockfile
        run: cargo fetch --locked
      - uses: Swatinem/rust-cache@v2

      - name: Format
        run: cargo fmt --all --check

  clippy:
    needs: fmt
    runs-on: ubuntu-latest
    env:
      CARGO_INCREMENTAL: "0"
      CARGO_PROFILE_DEV_DEBUG: "0"
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: default
            cargo_args: ""
          - name: all-features
            cargo_args: "--all-features"
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: 1.93.0
          components: clippy
      - name: Verify lockfile
        run: cargo fetch --locked
      - uses: Swatinem/rust-cache@v2

      - name: Clippy (${{ matrix.name }})
        run: cargo clippy --workspace --all-targets --locked ${{ matrix.cargo_args }} -- -D warnings

  test:
    needs: fmt
    runs-on: ubuntu-latest
    env:
      CARGO_INCREMENTAL: "0"
      CARGO_PROFILE_DEV_DEBUG: "0"
      CARGO_PROFILE_TEST_DEBUG: "0"
    strategy:
      fail-fast: false
      matrix:
        include:
          - name: default
            cargo_args: ""
            cargo_jobs: ""
          - name: all-features
            cargo_args: "--all-features"
            cargo_jobs: "--jobs 1"
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: 1.93.0
      - name: Verify lockfile
        run: cargo fetch --locked
      - uses: Swatinem/rust-cache@v2

      - name: Tests (${{ matrix.name }})
        run: cargo test --workspace --locked ${{ matrix.cargo_args }} ${{ matrix.cargo_jobs }}

  policy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          toolchain: 1.93.0
      - name: Verify lockfile
        run: cargo fetch --locked
      - uses: Swatinem/rust-cache@v2

      - name: Verify SPDX headers
        run: |
          python3 - <<'PY'
          from __future__ import annotations
          
          import subprocess
          import sys
          from pathlib import Path
          
          expected = [
              "// SPDX-License-Identifier: MIT OR Apache-2.0",
              "// SPDX-FileCopyrightText: 2025-2026 ObzenFlow Contributors",
              "// https://obzenflow.dev",
          ]
          
          files = subprocess.check_output(["git", "ls-files", "*.rs"], text=True).splitlines()
          bad: list[tuple[str, list[str]]] = []
          for f in files:
              lines = Path(f).read_text(encoding="utf-8").splitlines()
              got = lines[:3]
              if got != expected:
                  bad.append((f, got))
          
          if bad:
              print("SPDX header mismatch in Rust files:")
              for f, got in bad:
                  print(f" - {f}")
                  for i, line in enumerate(got or ["<missing>"] * 3, start=1):
                      print(f"   {i}: {line}")
              sys.exit(1)
          
          print(f"SPDX headers OK in {len(files)} Rust files")
          PY

      - name: Verify license files
        run: |
          python3 - <<'PY'
          from __future__ import annotations

          import glob
          import re
          import sys
          from pathlib import Path

          REQUIRED = ["LICENSE-MIT", "LICENSE-APACHE", "NOTICE"]
          root = Path(".")
          errors: list[str] = []

          # --- 1. Discover crate directories dynamically ---
          crate_dirs = sorted(
              Path(p).parent for p in glob.glob("crates/*/Cargo.toml")
          )
          if not crate_dirs:
              print("No crates found under crates/*/Cargo.toml")
              sys.exit(1)

          # --- 2. Presence check ---
          for name in REQUIRED:
              if not (root / name).is_file():
                  errors.append(f"Missing root {name}")

          for crate_dir in crate_dirs:
              for name in REQUIRED:
                  if not (crate_dir / name).is_file():
                      errors.append(f"Missing {crate_dir / name}")

          # --- 3. Byte-identity check ---
          for crate_dir in crate_dirs:
              for name in REQUIRED:
                  root_file = root / name
                  crate_file = crate_dir / name
                  if root_file.is_file() and crate_file.is_file():
                      if root_file.read_bytes() != crate_file.read_bytes():
                          errors.append(
                              f"{crate_file} differs from root {name}"
                          )

          # --- 4. Copyright year consistency ---
          # Single source of truth: the SPDX header expected value (line 2 in
          # the SPDX check above).
          spdx_copyright = (
              "// SPDX-FileCopyrightText: 2025-2026 ObzenFlow Contributors"
          )
          m = re.search(r"(\d{4}(?:-\d{4})?)", spdx_copyright)
          if not m:
              errors.append("Cannot extract year range from SPDX header")
          else:
              spdx_years = m.group(1)
              mit = root / "LICENSE-MIT"
              if mit.is_file():
                  mit_lines = mit.read_text(encoding="utf-8").splitlines()
                  if len(mit_lines) < 3:
                      errors.append("LICENSE-MIT has fewer than 3 lines")
                  else:
                      cm = re.search(r"(\d{4}(?:-\d{4})?)", mit_lines[2])
                      if not cm:
                          errors.append(
                              "Cannot extract year from LICENSE-MIT line 3"
                          )
                      elif cm.group(1) != spdx_years:
                          errors.append(
                              f"Year mismatch: SPDX header has {spdx_years}, "
                              f"LICENSE-MIT line 3 has {cm.group(1)}"
                          )

          if errors:
              print("License file check failed:")
              for e in errors:
                  print(f"  - {e}")
              sys.exit(1)

          print(
              f"License files OK: {len(REQUIRED)} files verified across "
              f"root + {len(crate_dirs)} crate(s)"
          )
          PY

      - name: Verify supervised_base sealing
        run: |
          ! grep -n '^pub mod base;' crates/obzenflow_runtime/src/supervised_base/mod.rs
          ! grep -n 'pub use base::Supervisor' crates/obzenflow_runtime/src/supervised_base/mod.rs

      - name: Install cargo-deny
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-deny

      - name: Install cargo-machete
        uses: taiki-e/install-action@v2
        with:
          tool: cargo-machete

      - name: cargo-deny
        run: cargo deny --all-features check

      - name: cargo-machete
        run: cargo machete --skip-target-dir