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
4 changes: 2 additions & 2 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Contributors who have signed the [Contributor License Agreement](CLA.md) by making a signed-off Contribution to SecretEnv.

Adding Your name here is required the first time You contribute — see [CONTRIBUTING.md](CONTRIBUTING.md) §CLA.
Adding Your name here is required the first time You contribute. See [CONTRIBUTING.md](CONTRIBUTING.md) §CLA.

---

- Mandeep Patel <mandeep@techalchemist.io> project maintainer, first contributed 2026-04-17
- Mandeep Patel <mandeep@techalchemist.io>, project maintainer, first contributed 2026-04-17
1,561 changes: 556 additions & 1,005 deletions CHANGELOG.md

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions CLA.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# SecretEnv Contributor License Agreement (CLA)

**Version 1.0 effective 2026-04-19.**
**Version 1.0, effective 2026-04-19.**

This Contributor License Agreement ("**Agreement**") is entered into between **TechAlchemistX** (the "**Project**", operated by Mandeep Patel) and **You** (the contributor).

By submitting a Contribution (defined below) to the Projectwhether as a Pull Request, patch, comment, or any other form accepted by the Project You agree to the terms below. This Agreement applies to all Contributions You have ever submitted and all Contributions You will submit in the future, unless superseded in writing.
By submitting a Contribution (defined below) to the Project, whether as a Pull Request, patch, comment, or any other form accepted by the Project, You agree to the terms below. This Agreement applies to all Contributions You have ever submitted and all Contributions You will submit in the future, unless superseded in writing.

## 1. Definitions

- **"Contribution"** means any original work of authorship source code, documentation, configuration, test, asset, or other creative material that You intentionally submit to the Project for inclusion in, or documentation of, any of the Project's products or artifacts.
- **"Contribution"** means any original work of authorship (source code, documentation, configuration, test, asset, or other creative material) that You intentionally submit to the Project for inclusion in, or documentation of, any of the Project's products or artifacts.
- **"Submit"** means any form of electronic, verbal, or written communication sent to the Project, including code pushed to the Project's repositories, Pull Requests opened against those repositories, issue comments containing code, email, and communication on Project-operated communication channels, but excluding communication conspicuously marked as "Not a Contribution."
- **"You"** (or "Your") means the individual or legal entity entering into this Agreement. If You are entering into this Agreement on behalf of a legal entity, You represent that You are authorized to bind that entity.

Expand All @@ -26,7 +26,7 @@ If any entity institutes patent litigation against You or any other entity (incl

You acknowledge that the Project is currently licensed under the **GNU Affero General Public License v3.0 (AGPL-3.0-only)** to downstream users.

You further grant the Project the right to **relicense Your Contribution** under any other license of the Project's choosingincluding but not limited to permissive licenses (MIT, Apache-2.0), other copyleft licenses (GPLv3, LGPLv3), or proprietary/commercial licenses and to sublicense under any such license. This enables the Project to offer dual-licensed commercial terms to organizations that require them, while continuing to distribute under AGPL-3.0-only to the community at large.
You further grant the Project the right to **relicense Your Contribution** under any other license of the Project's choosing, including but not limited to permissive licenses (MIT, Apache-2.0), other copyleft licenses (GPLv3, LGPLv3), or proprietary/commercial licenses, and to sublicense under any such license. This enables the Project to offer dual-licensed commercial terms to organizations that require them, while continuing to distribute under AGPL-3.0-only to the community at large.

**You retain full ownership of Your Contribution.** This Agreement is a license grant, not a copyright assignment. You may continue to use, modify, and relicense Your own Contribution outside the Project under any terms You choose.

Expand All @@ -50,7 +50,7 @@ You signify agreement to this CLA by doing all of the following when submitting
1. **Sign off every commit in Your contribution** with the `Signed-off-by: Your Name <your-email@example.com>` trailer. This trailer attests that You have the right to submit the Contribution and that You agree to this CLA. Use `git commit --signoff` (or `-s`) to add it automatically.
2. **On Your first Contribution to the Project**, add Your name + email to the bottom of [`AUTHORS.md`](AUTHORS.md) in the same Pull Request, formatted as:
```
- Your Name <your-email@example.com> first contributed YYYY-MM-DD
- Your Name <your-email@example.com>, first contributed YYYY-MM-DD
```
This serves as an explicit, timestamped record of assent.
3. **Continue signing off every subsequent Contribution.** A missing `Signed-off-by` trailer is treated as an absent CLA and blocks merge.
Expand Down
92 changes: 43 additions & 49 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Contributing to SecretEnv

Thanks for considering a contribution. This document covers the mechanics. For the why the architecture, the plugin model, the security posture read [`README.md`](README.md) and [`docs/`](docs/) first.
Thanks for considering a contribution. This document covers the mechanics. For the why (the architecture, the plugin model, the security posture), read [`README.md`](README.md) and [`docs/`](docs/) first.

## Development setup

You need a recent Rust stable toolchain. The repo pins one via `rust-toolchain.toml`, so `rustup` will pick it up automatically on first build. The pin is deliberate: it keeps local and CI on the same compiler so new clippy lints + trybuild fixture text drift do not surface as red CI on push. Bumping the pin is its own chore — see `kb/wiki/runbooks/rust-toolchain-bump.md`.
Install Rust via rustup. The repo pins a toolchain in `rust-toolchain.toml`. Rustup picks it up automatically. The pin keeps local and CI on the same compiler.

Supporting tools used by the pre-commit suite:
Install supporting tools:

```sh
cargo install cargo-deny --locked
cargo install cargo-audit --locked
```

## Pre-commit suite
## Pre-commit

Every commit on `main` must pass the following locally before opening a PR. CI re-runs the same checks:
Every commit must pass locally before opening a PR:

```sh
cargo fmt --all -- --check
Expand All @@ -25,74 +25,68 @@ cargo deny check
cargo audit
```

If any of these fails, fix it at the source. Do **not** weaken the workspace-level lint configuration to paper over a failure; prefer a narrowly scoped `#[allow(...)]` with a comment explaining why.
Fix failures at the source, not by weakening lints. Use narrowly-scoped `#[allow(...)]` with a comment if needed.

## Branching & commit workflow
## Branching & commits

- Branch off `main` with a prefix: `feat/<short-slug>`, `fix/<short-slug>`, or `chore/<short-slug>`.
- One PR per logical change. Squash-merge.
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/):
- `feat(core): ...`, `fix(backend-aws-ssm): ...`, `chore: ...`, `docs: ...`, `test: ...`
- Commits must be signed. See [`docs/signing.md`](docs/signing.md) if it exists, or configure `git config --global commit.gpgsign true` with an SSH signing key registered on your GitHub account.
- Branch: `feat/<slug>`, `fix/<slug>`, or `chore/<slug>` off `main`
- One PR per logical change; squash-merge
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/): `feat(core):`, `fix(backend-aws-ssm):`, `chore:`, etc.
- Sign commits with SSH key: `git config --global commit.gpgsign true`

## Adding a new backend
## New backends

Follow the step-by-step walkthrough at [`docs/adding-a-backend.md`](docs/adding-a-backend.md). The architectural constraints shell-out to native CLIs only, `Backend` + `BackendFactory` traits, no cloud-SDK imports — are load-bearing, not stylistic.
Follow [`docs/reference/adding-a-backend.md`](docs/reference/adding-a-backend.md). Architectural constraints (shell-out to native CLIs, `Backend` + `BackendFactory` traits, no cloud SDKs) are load-bearing.

## Adding a telemetry attribute (typed setter)
## Telemetry setters

`SecretEnvSpan` has **no `set_attribute(key, value)` escape hatch** — every emitted OTel attribute goes through a typed `record_*` setter, and there is no string-keyed path. When the value is a closed set (a backend type, a command name, an outcome), the setter takes a **closed enum, not a `&str`**, so a typo or a leaky raw string cannot reach the wire.
`SecretEnvSpan` has no `set_attribute(key, value)` escape hatch. All OTel attributes go through typed `record_*` setters. Closed-set values use enums, not `&str`.

The rule that trips contributors: **those closed enums live in `secretenv-telemetry::span`, not in the crate that owns the domain concept.** The dependency direction is `secretenv-core → secretenv-telemetry` (and `secretenv-migrate`/`secretenv-mcp` likewise depend *on* telemetry), so telemetry cannot depend back on core to reuse, say, a backend-type enum defined there. Adding a typed setter is therefore a three-part change, all in `secretenv-telemetry`:
**Closed enums live in `secretenv-telemetry::span`, not the domain crate.** Dependency direction: `secretenv-core → secretenv-telemetry`. Adding a typed setter requires three changes in `secretenv-telemetry`:

1. the closed enum (e.g. `BackendType`, `SecretEnvCommand`, `MigrateOutcome`) with an `into_attribute_value`/`as_attribute_value` method — in `span.rs`;
2. the `record_<attr>(&mut self, value: TheEnum)` setter on `SecretEnvSpan`;
3. the consuming caller in `secretenv-core`/`-migrate`/`-mcp`, which converts a runtime string at the boundary via the enum's `from_runtime_str` (or constructs the variant directly).
1. Closed enum (e.g. `BackendType`, `SecretEnvCommand`, `MigrateOutcome`) with `into_attribute_value`/`as_attribute_value` in `span.rs`
2. `record_<attr>(&mut self, value: TheEnum)` setter on `SecretEnvSpan`
3. Consuming caller in `secretenv-core`/`-migrate`/`-mcp` converting runtime strings

A setter that takes `&str` for a value that is really a closed set is a half-closure — see the v0.18→v0.19 history of `record_migrate_*_backend_type` (Arch-M4 / Arch-W-4). Do not add new `&str` setters for closed-set values.
Do not add `&str` setters for closed-set values.

## Panics in production code
## Panics in production

Production code (everything outside `#[cfg(test)]`) must not `panic!`/`unreachable!`/`unwrap`/`expect` on any input an operator or agent can influence. Two narrow exceptions, each requiring a comment:
Production code must not panic on operator or agent input. Two exceptions (both require comments):

- **Statically-valid `expect`**e.g. compiling a hand-audited `regex` literal. Carry `#[allow(clippy::expect_used)]` and a one-line justification that the failure is impossible by construction and would surface at first use, not in a user path.
- **By-construction `panic!`** — when an internal invariant makes a branch unreachable, prefer making it unrepresentable in the type system (the `ResolvedConfirmVia` / `MutationOperatorDecision` marker-type pattern — see `secretenv-mcp`) over a runtime guard. If a type-level lift is disproportionate for a single internal caller, document the precondition with a `// by construction:` block at the panic site (see `aggregate_errors` in `secretenv-core::runner`).
- **Hand-audited `expect`**: e.g. compiling regex literals. Add `#[allow(clippy::expect_used)]` with a one-line justification that the failure is impossible by construction.
- **By-construction panic**: internal invariants making branches unreachable. Prefer making it unrepresentable in the type system (marker-type pattern in `secretenv-mcp`). If unproportionate, add a `// by construction:` comment at the panic site (see `aggregate_errors` in `secretenv-core::runner`).

Prefer the type-system lift. A `tracing::warn!` + soft-fail on a "can't happen" branch is a smell, not a safety net.
Prefer the type-system lift. A `tracing::warn!` + soft-fail is a smell, not a safety net.

## Marker types and the `Decision` trait
## Marker types

When an enum variant is unreachable in a specific context, prefer a context-specific marker type that structurally omits the variant over a runtime guard or `unreachable!()` arm. The impossible state then cannot be witnessed in that context; the compiler enforces the invariant rather than a human reader. Convert to and from the shared on-disk or serde type at the context boundary via a small trait. This pattern has three instances in the codebase: `ResolvedConfirmVia` (produced by `resolve_confirm_via` in the policy gate; omits `Auto` once resolution has run, eliminating the v0.17 `unreachable!()`), `MutationSpanName` (the closed enum drives both the span-name constructor and the sampler whitelist so adding a variant covers both), and the v0.19 `OperatorDecision` family (`MutationOperatorDecision` omits `DryRun` — mutation tools structurally cannot receive a dry-run decision; `MigrateOperatorDecision` retains it; the `Decision` trait's `to_audit() -> OperatorDecision` is the single projection point so echo and audit-write cannot diverge). The on-disk/serde union (`OperatorDecision`) remains one type shared across all contexts; only the in-memory marker types are split. See also `## Panics in production code` above — the marker-type lift is the preferred alternative to the `// by construction:` documented panic.
Use context-specific marker types that omit unreachable variants instead of runtime guards or `unreachable!()`. The compiler enforces the invariant. Convert at context boundaries via a trait.

## Security

Security-relevant changes get extra care. See [`SECURITY.md`](SECURITY.md) for the disclosure policy and [`docs/security.md`](docs/security.md) for the threat model. Report vulnerabilities privately — do not open a public issue.

## License and CLA
Examples: `ResolvedConfirmVia` (omits `Auto` post-resolution), `MutationSpanName` (closed enum drives span-name + sampler whitelist), `OperatorDecision` family (`MutationOperatorDecision` omits `DryRun`; `MigrateOperatorDecision` retains it).

SecretEnv is licensed under **[GNU AGPL v3.0 (AGPL-3.0-only)](LICENSE)** starting with v0.3.0. v0.1 and v0.2.0 shipped under MIT; the published MIT releases remain available under their original terms.
The on-disk/serde union stays shared; only in-memory marker types split. Prefer this over the `// by construction:` documented panic.

All Contributions are accepted under the project's [Contributor License Agreement (CLA)](CLA.md). The CLA is a **license grant** (not a copyright assignment — You retain ownership of Your work) that lets the project relicense contributions under alternate terms, enabling dual-licensing (e.g., commercial licenses alongside the AGPL community license).
## Security

### Signing the CLA
Security-relevant changes get extra care. See [`SECURITY.md`](SECURITY.md) for the disclosure policy and [`docs/security.md`](docs/security.md) for the threat model. Report vulnerabilities privately. Do not open a public issue.

Two things are required on every Contribution:
## License and CLA

1. **Every commit must carry a `Signed-off-by` trailer** attesting to the CLA. Use `git commit -s` (or `--signoff`) — git appends the trailer from your configured `user.name` + `user.email` automatically:
```
Signed-off-by: Jane Doe <jane@example.com>
```
Missing trailer = no merge. This is enforced at review.
SecretEnv is licensed under **[GNU AGPL v3.0](LICENSE)** from v0.3.0 forward. v0.1 to 0.2.0 shipped under MIT.

2. **First-time contributors:** add your name to [`AUTHORS.md`](AUTHORS.md) in the same PR as your first Contribution, using the format documented in that file. Subsequent Contributions only need the sign-off trailer.
All contributions are accepted under the [Contributor License Agreement](CLA.md), a license grant (not copyright assignment) enabling dual-licensing.

Corporate contributors whose employer claims IP rights over their Contributions must additionally execute a Corporate CLA — contact the project maintainer to arrange this before submitting.
### CLA requirements

### Commit signing vs. CLA sign-off
1. **Sign-off on every commit:** Use `git commit -s`. Adds `Signed-off-by` trailer from your configured `user.name` + `user.email`. Missing trailer = no merge.
2. **First-time contributors:** Add your name to [`AUTHORS.md`](AUTHORS.md) in the same PR. Subsequent contributions only need the trailer.
3. **Corporate contributors:** Your employer must execute a Corporate CLA. Contact the maintainer first.

These are two different things:
### Commit signing vs. sign-off

- **`git commit --signoff` (`-s`)** adds the `Signed-off-by` line (CLA attestation). **Required.**
- **`git commit --gpg-sign` (`-S`) / SSH signing (`commit.gpgsign = true`)** cryptographically signs the commit. **Also required** per the Branching & Commit Workflow section above.
Two distinct things:
- **`git commit -s` (sign-off)**: adds `Signed-off-by` (CLA attestation). Required.
- **`git commit -S` (cryptographic signature)**: signs the commit with SSH key. Required.

Configure both; they compose. A typical commit on this repo carries both a cryptographic signature (verifies identity) and a `Signed-off-by` trailer (CLA assent).
Configure both. A typical commit carries cryptographic signature + `Signed-off-by` trailer.
Loading