Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
nextest-version = "0.9.140"

# Core tests are platform-independent library decisions and contract binaries.
# Tests under tests/ default to the host profile unless deliberately named contract_*.
[profile.ci-core]
fail-fast = false
default-filter = 'binary(/^contract_/) | (binary(gitveil) & not test(/^(configure::tests|git::repository::tests|identity::tests|runtime::sidecar::tests)/))'

# Host tests are the exact complement of core tests and exercise external effects.
[profile.ci-host]
fail-fast = false
default-filter = 'not (binary(/^contract_/) | (binary(gitveil) & not test(/^(configure::tests|git::repository::tests|identity::tests|runtime::sidecar::tests)/)))'

# Nextest runs every test in its own process, so serial_test's in-process
# lock does not serialize across tests here. This group preserves the same
# serialization the `#[serial]` markers provide under `cargo test`.
Expand Down
120 changes: 109 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,86 @@ concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

defaults:
run:
shell: bash

env:
CARGO_INCREMENTAL: "0"
CARGO_TERM_COLOR: always
PYTHONDONTWRITEBYTECODE: "1"

jobs:
quality:
core:
name: Core
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- name: Check out the repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
fetch-depth: 0
persist-credentials: false

- name: Activate the pinned Rust toolchain
run: |
rustc --version
cargo --version

- name: Install pinned quality tools
uses: taiki-e/install-action@6cd13508893c0e7eab5f273c2575d3859bd7229a # v2
with:
tool: |
cargo-nextest@0.9.140
cargo-deny@0.20.2
checksum: true
fallback: none

- name: Check formatting
run: cargo fmt --all -- --check

- name: Check functional-core boundaries
run: ./scripts/check-functional-core.py

- name: Check documentation links
run: ./scripts/check-doc-links.py

- name: Run Clippy
run: cargo clippy --all-targets --all-features --locked -- -D warnings

- name: Verify the test partition
run: |
partition="$(mktemp -d)"
trap 'rm -rf "$partition"' EXIT
cargo nextest list --profile ci-core --all-features --locked | sort -u > "$partition/core"
cargo nextest list --profile ci-host --all-features --locked | sort -u > "$partition/host"
cargo nextest list --all-features --locked | sort -u > "$partition/all"
comm -12 "$partition/core" "$partition/host" > "$partition/overlap"
if [[ -s "$partition/overlap" ]]; then
echo "Core and host test profiles overlap" >&2
exit 1
fi
cat "$partition/core" "$partition/host" | sort -u > "$partition/combined"
diff -u "$partition/all" "$partition/combined"

- name: Run core tests
run: cargo nextest run --profile ci-core --all-features --locked --no-tests fail

- name: Check dependency policy
run: cargo deny check

- name: Check patch whitespace
env:
BASE_SHA: ${{ github.event.pull_request.base.sha || github.event.before }}
HEAD_SHA: ${{ github.sha }}
run: |
if [[ -n "$BASE_SHA" && "$BASE_SHA" != "0000000000000000000000000000000000000000" ]]; then
git diff --check "$BASE_SHA" "$HEAD_SHA"
else
git diff-tree --check --root --no-commit-id -r "$HEAD_SHA"
fi

host:
name: ${{ matrix.name }}
strategy:
fail-fast: false
Expand All @@ -27,27 +105,47 @@ jobs:
os: macos-latest
runs-on: ${{ matrix.os }}
timeout-minutes: 90
env:
CARGO_INCREMENTAL: "0"
CARGO_TERM_COLOR: always
PYTHONDONTWRITEBYTECODE: "1"
steps:
- name: Check out the repository
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false

- name: Activate the pinned Rust toolchain
run: |
rustc --version
cargo --version

- name: Install pinned quality tools
- name: Install pinned test runner
uses: taiki-e/install-action@6cd13508893c0e7eab5f273c2575d3859bd7229a # v2
with:
tool: |
cargo-nextest@0.9.140
cargo-deny@0.20.2
tool: cargo-nextest@0.9.140
checksum: true
fallback: none

- name: Run the native host quality gate
run: ./scripts/check-host.sh
- name: Fetch checksum-pinned test sidecars
run: |
sops_bin="$(./scripts/fetch-test-sops.py)"
age_keygen_bin="$(./scripts/fetch-test-age-keygen.py)"
age_keygen_archive="$(./scripts/fetch-test-age-keygen.py --print-archive)"
{
echo "SOPS_BIN=$sops_bin"
echo "AGE_KEYGEN_BIN=$age_keygen_bin"
echo "AGE_KEYGEN_ARCHIVE=$age_keygen_archive"
} >> "$GITHUB_ENV"

- name: Run native host tests
run: cargo nextest run --profile ci-host --all-features --locked --no-tests fail

- name: Build the native release binary
run: cargo build --release --locked

- name: Validate the native release package
run: |
./scripts/build-release-archive.py \
--skip-build \
--binary target/release/gitveil \
--sops-bin "$SOPS_BIN" \
--age-keygen-bin "$AGE_KEYGEN_BIN" \
--age-keygen-archive "$AGE_KEYGEN_ARCHIVE" \
--output-dir target/package-test
21 changes: 10 additions & 11 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Inspect the existing implementation and tests before proposing changes. Do not i
- Gitveil is one Rust 2024 package with a library and a thin binary.
- Supported production hosts are macOS and Linux.
- The Rust version is pinned in `rust-toolchain.toml`.
- Compatible SOPS and age-keygen builds and artifact checksums are declared in `scripts/sops_artifacts.py` and `scripts/age_artifacts.py`.
- Compatible SOPS and age-keygen builds and artifact checksums are declared in `scripts/_lib/sops_artifacts.py` and `scripts/_lib/age_keygen_artifacts.py`.
- Files directly under `scripts/` are executable entry points; import-only Python modules belong under `scripts/_lib/` and must not be executable.
- `Cargo.toml` intentionally has `publish = false`; do not publish the crate or create releases unless explicitly requested.
- Keep all tracked text, code comments, tests, commit messages, and pull request content in English.
- Do not add private keys, plaintext secrets, local absolute paths, generated build output, unpublished planning material, or tool transcripts.
Expand All @@ -29,7 +30,7 @@ Inspect the existing implementation and tests before proposing changes. Do not i
Keep pure policy and transformation code separate from external effects.

- Pure domain modules and feature-local `plan` modules must not access the filesystem, processes, Git, SOPS, environment, network, clock, threads, or randomness.
- `scripts/check-architecture.py` is the executable declaration of modules that belong to the functional core. Add new pure modules to that declaration rather than bypassing the check.
- `scripts/check-functional-core.py` is the executable declaration of modules that belong to the functional core. Add new pure modules to that declaration rather than bypassing the check.
- `src/source/` owns typed and lossless source parsing and rendering.
- `src/semantic/` owns pure semantic diff and merge behavior.
- `src/git/`, `src/sops/`, `src/age.rs`, and `src/runtime/` contain external adapters.
Expand Down Expand Up @@ -80,15 +81,13 @@ Match the test level to the contract:
- Isolate `HOME`, global and system Git configuration, identity environment variables, and fixture state.
- Every child process needs a deadline and must be killed and reaped after a timeout.

Use targeted tests during development. The public gates are:
Use targeted tests during development. Complete validation runs only in GitHub Actions:

```bash
./scripts/check-host.sh
./scripts/check-linux.sh
./scripts/check-all.sh
```
- `Core` runs platform-independent library unit tests, pure `contract_*` binaries, formatting, architecture and documentation checks, Clippy, and dependency policy once on Ubuntu.
- `Linux` and `macOS` each run the complementary native host suite, release build, and package validation on a GitHub-hosted runner.
- `.config/nextest.toml` is the executable test classification. New `tests/*.rs` binaries default to the host suite unless deliberately named `contract_*`; colocated unit tests must remain pure unless their adapter module is explicitly assigned to the host profile.

`check-host.sh` is the native macOS/Linux gate. `check-linux.sh` runs the Linux suite in Docker. `check-all.sh` runs both and therefore must be invoked from macOS. GitHub Actions runs `check-host.sh` natively on Linux and macOS for pull requests and pushes to `main`.
Do not recreate aggregate local quality-gate scripts or Docker replicas of GitHub-hosted runners. Local commands are for targeted Red/Green feedback; the pull request checks are the complete merge gates.

Do not change test expectations merely to make a gate pass. Fix the implementation, fixture isolation, or contract.

Expand All @@ -97,7 +96,7 @@ Do not change test expectations merely to make a gate pass. Fix the implementati
- Update `README.md` when commands, configuration, installation, security boundaries, or release layout change.
- Update `docs/getting-started.md` when the first-use path changes.
- Update `CONTRIBUTING.md` when contributor tooling or quality gates change.
- Keep relative links in public Markdown valid; `scripts/check-docs.py` enforces them.
- Keep relative links in public Markdown valid; `scripts/check-doc-links.py` enforces them.
- Keep dependency changes intentional and locked. Explain why a new runtime dependency belongs at its architectural boundary.
- Preserve third-party license files and notices in release archives.

Expand All @@ -108,7 +107,7 @@ Use English branch names, commits, pull request titles, and descriptions. Keep e
- a pull request,
- linear history through squash merging,
- resolved review conversations,
- successful `Linux` and `macOS` checks,
- successful `Core`, `Linux`, and `macOS` checks,
- a branch tested against the latest `main`.

Before handing off a change, inspect the final diff, run `git diff --check`, confirm that no generated or secret-bearing files are tracked, and report the exact checks run and any checks that could not be run.
43 changes: 15 additions & 28 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,14 @@ Gitveil handles secret-bearing files and Git history, so changes must preserve i

## Development environment

Required tools:
Required tools for source development:

- macOS or Linux
- Git 2.20+
- Rust 1.98.0, selected automatically by `rust-toolchain.toml`
- Python 3.9+
- `cargo-nextest` 0.9.140
- `cargo-deny`
- Docker for the Linux quality gate

Install the Cargo tools if they are not already available:

```bash
cargo install cargo-nextest --locked --version 0.9.140
cargo install cargo-deny --locked
```

The test and packaging scripts download checksum-pinned SOPS and age artifacts into `target/` on first use. They do not modify system installations.
GitHub Actions installs the pinned `cargo-nextest` and `cargo-deny` versions used by the complete merge gates. Artifact helpers download checksum-pinned SOPS and age builds for the current platform into `target/`; they do not modify system installations.

## Making changes

Expand All @@ -44,27 +34,24 @@ Preserve these invariants:

## Tests

Run the host gate for the current platform:
Use targeted commands for local Red/Green feedback. Fetch only the current platform's pinned sidecars when the selected test needs them:

```bash
./scripts/check-host.sh
./scripts/fetch-test-sops.py
AGE_KEYGEN_BIN="$(./scripts/fetch-test-age-keygen.py)" \
cargo test --test integration_identity
cargo test open::plan::tests::both_sides_changed_reports_a_conflict_and_keeps_local
```

Run the native Linux suite in Docker:

```bash
./scripts/check-linux.sh
```

On macOS, run both through the unified gate before requesting review:

```bash
./scripts/check-all.sh
```
Complete validation runs only in GitHub Actions. `.config/nextest.toml` defines two complementary suites:

The gates cover formatting, architecture boundaries, public documentation links, Clippy, unit and integration tests, dependency policy, release builds, and native archive validation. Integration tests use real Git and checksum-pinned SOPS and age-keygen executables. Initial cold runs download dependencies and build artifacts and may take substantially longer than warm runs.
| Check | Runner | Scope |
|---|---|---|
| `Core` | Ubuntu | Platform-independent unit and `contract_*` tests, formatting, architecture, documentation, Clippy, and dependency policy |
| `Linux` | Ubuntu | Native CLI, Git, SOPS, identity, filesystem, runtime, release build, and package validation |
| `macOS` | macOS | The same native host suite on macOS |

GitHub Actions runs `scripts/check-host.sh` natively on Linux and macOS for every pull request and push to `main`. CI uses no repository secrets and does not publish artifacts or releases.
Every pull request and push to `main` runs all three checks on GitHub-hosted runners. CI uses no repository secrets and does not publish artifacts or releases. Do not add aggregate local gate scripts or a Docker replica of the hosted Linux runner.

## Pull requests

Expand All @@ -74,6 +61,6 @@ A pull request should:
- Include tests for each changed postcondition and relevant failure path.
- Update user documentation when installation, commands, output, configuration, security boundaries, or release layout changes.
- Contain no private identity, plaintext secret, local absolute path, generated build output, or unrelated formatting churn.
- Pass the applicable host and Linux quality gates.
- Pass the required `Core`, `Linux`, and `macOS` GitHub Actions checks.

By intentionally submitting a contribution for inclusion in Gitveil, you agree to license that contribution under the MIT License without additional terms or conditions.
14 changes: 4 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,21 @@ Gitveil does not require a system SOPS or age executable. Release archives inclu
Install from a source checkout for the current user:

```bash
./scripts/install-local.py
./scripts/install-from-source.py
```

The default prefix is `${CARGO_HOME:-$HOME/.cargo}`. To select another prefix:

```bash
./scripts/install-local.py --prefix ~/.local
./scripts/install-from-source.py --prefix ~/.local
```

The installer performs a locked release build, fetches and verifies official SOPS 3.13.3 and age-keygen 1.3.1 artifacts, validates the complete release archive, and transactionally installs the binary, private sidecars, and license files. Do not use `cargo install --path .`: Cargo installs only the binary and omits the private sidecars.

The user-facing installation unit is a native release archive. Maintainers build one on each target platform with:

```bash
python3 scripts/package-release.py
./scripts/build-release-archive.py
```

The archive is written to `dist/gitveil-v<version>-<os>-<arch>.tar.gz` with this layout:
Expand Down Expand Up @@ -187,13 +187,7 @@ Gitveil reads index stages but never runs `git add`.

## Development

```bash
./scripts/check-host.sh
./scripts/check-linux.sh
./scripts/check-all.sh
```

The unified local quality gate covers the macOS host, Docker Linux, real SOPS compatibility, release archive structure, and native release builds. GitHub Actions runs the native host gate on Linux and macOS for every pull request and push to `main`.
Use targeted `cargo test` commands for local development. Complete validation runs in GitHub Actions for every pull request and push to `main`: `Core` runs platform-independent tests and static quality checks once, while `Linux` and `macOS` run the complementary native host tests, release builds, and package validation. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the test classification and contributor workflow.

## License

Expand Down
Loading