acme-proxy 0.2.0

An ACME (RFC 8555) server that issues from a local CA, relays to an upstream CA, or delegates to a script
Documentation
# Contributing to acme-proxy

Thank you for your interest in contributing to `acme-proxy`! Whether you're
fixing a bug, adding a new feature, or improving documentation, your help is
welcome.

## Development environment

To start developing, ensure you have the following installed:
- [Rust]https://rustup.rs/ (latest stable version)
- `sqlite3` (for database inspection, though `sqlx` handles migrations)
- [mdBook]https://rust-lang.github.io/mdBook/ (if you want to build this
  documentation locally)

### Initial setup

Clone the repository and build the project:
```bash
git clone https://github.com/acme-proxy/acme-proxy.git
cd acme-proxy
cargo build
```

## Testing

The suite is what holds RFC 8555 compliance in place, and CI enforces a
coverage floor, so a change that adds a branch generally has to add a test for
it.

Before submitting a pull request, run the full suite with **nextest**:
```bash
cargo nextest run
```

> Use `cargo nextest run`, not `cargo test`. This is a requirement, not a
> preference: several tests execute a script file they have just written, and
> under `cargo test` — which runs tests as threads of a single process — another
> thread's `Command::spawn` can fork while the file's write descriptor is still
> open, failing with `ETXTBSY` roughly one run in three. nextest's
> process-per-test isolation removes the race entirely. See [Testing &
> Coverage](testing.md).

### What CI will check

Your pull request has to pass all of these:

```bash
cargo fmt --all --check
cargo clippy --all-targets -- -D warnings
cargo llvm-cov nextest --fail-under-lines 97
cargo test --doc          # llvm-cov skips doc-tests
cargo deny check          # supply-chain audit, against deny.toml
```

Note the coverage floor is enforced, so new code generally needs new tests.
`cargo test --doc` is the only thing that compiles the startup example in
`src/lib.rs`.

### Writing tests
- **Unit Tests:** Keep them close to the code (in the same file, in a `mod
  tests`).
- **Integration Tests:** Located in the `tests/` directory. These tests spin up
  a full in-memory axum router and SQLite database to test the entire ACME flow.

See the [Testing & Coverage](testing.md) page for more details.

## Code style

- Format your code using `cargo fmt`.
- Ensure all lints pass by running `cargo clippy --all-targets -- -D warnings`.
- Document public APIs using rustdoc comments (`///`).
- Comments, doc comments and error-message strings are written in **English**,
  as are identifiers and log messages.
- Every `tracing` call carries `event = "<subsystem>_<outcome>"` as its
  **first** field, as a string literal rather than a computed value, so the name
  stays greppable. Several are asserted by the end-to-end suite — grep before
  renaming one.
- The crate is edition 2024; see `rust-version` in `Cargo.toml` for the minimum
  toolchain.

## Changing the database schema

**`migrations/` is append-only as of 0.1.0.** Add a migration; never edit a
committed one:

```bash
sqlx migrate add add_widget_table
```

`sqlx` tracks each migration by a checksum, so editing a file that has already
run turns every existing deployment into a startup failure. This reverses the
rule that held before the first release, when the server had never been deployed
and a schema change meant editing the migration and running `rm -f sqlite.db*`.

Two consequences:

- **A new column is a new file**, even when it plainly belongs to an existing
  table. `ALTER TABLE ADD COLUMN` is cheap; putting it in the original `CREATE
  TABLE` is what breaks.
- **A new `CHECK`, `UNIQUE` or foreign key needs a table rebuild**, because
  SQLite cannot add one to an existing table. Write the rebuild in the new
  migration, and remember that an `INSERT … SELECT` silently drops any column
  you forget to name.

## Changing a configuration key

The schema is the only frozen surface. Before 1.0.0, renaming or removing a
configuration key is a normal change rather than one to design around — that is
what keeps the code free of a compatibility layer for every shape a section has
ever had. What such a change owes:

- **An entry in the changelog** under the release's `### Breaking` heading,
  naming the old spelling and the new one. See
  [Compatibility]https://github.com/acme-proxy/acme-proxy/blob/main/CHANGELOG.md#compatibility.
- **A startup error naming the replacement**, where practical, so an unmigrated
  configuration stops the server instead of coming up looking configured and
  doing nothing. `src/filter/build.rs`'s `refuse_removed_keys` and the
  `signer.backend = "acme_proxy"` arm in `src/signer/mod.rs` are the worked
  examples. A key must still *parse* to be refused by name, which is why the
  removed `[filter]` fields survive in `src/config/types/filter.rs` and in
  `LIST_KEYS`; an unregistered one fails as an opaque serde error instead.
- **No alias, no dual syntax, no legacy lowering.** Delete the old shape. The
  refusals themselves are one-line diagnostics and go away at 1.0.0.

## Submitting a pull request

1. Fork the repository and create your branch from `main`.
2. Write clear, descriptive commit messages.
3. If you've added code that should be tested, add tests.
4. If you've changed APIs, update the documentation in this `mdBook`.
5. Open a PR, describing the problem you're solving and how you fixed it.

## Architecture guidelines

If you are proposing a large feature (like a new Signer or Filter), please
review the [Architecture & Design](architecture.md) documentation first. It's
often best to open an Issue to discuss the design before writing extensive code.