# AGENTS.md
This file provides guidance to AI coding agents (Claude Code, agy, codex, etc.) when working with code in this repository.
Ways of working are deliberately not repeated here. They live in the phpboyscout
skills, and naming a skill tends to age better than restating it, since the
restatement goes stale while the skill does not.
## What this crate is
`rtb-redact` takes a free-form string and replaces things that look like secrets
with `[redacted]`, for log lines, telemetry attributes and error messages on
their way to a surface you do not control. It is a crate in the
[phpboyscout Rust toolkit](https://rust.phpboyscout.uk), extracted from
[rust-tool-base](https://gitlab.com/phpboyscout/rust-tool-base); that
decomposition is still moving, so the monorepo/crate split does too.
Five public items, all in `src/lib.rs`: `string` (`:48`), `string_into` (`:76`),
`is_sensitive_header` (`:91`), `redact_header_value` (`:111`) and the
`SENSITIVE_HEADERS` set (`:26`). Two runtime dependencies, `regex` and `phf`;
MSRV `1.85` (`Cargo.toml:6`); an exact stable pinned in `rust-toolchain.toml`.
The crate root carries `#![forbid(unsafe_code)]` (`src/lib.rs:15`) while the
manifest sets `unsafe_code = "deny"` (`Cargo.toml:20`) so a test file can
`#![allow]` if it genuinely needs to. Framework-free is enforced, not asserted:
`deny.toml:36-38` bans `rust-tool-base` and `rtb-cli-bin` from the graph.
## What it actually matches, and what it does not
Read `docs/reference/rules.md` before touching a pattern. It lists all seven
rules, the order they run in, and what each leaves alone; its examples are
stated as literal output at 0.6.3 (`:13-14`), so a rule change and that page's
update belong in the same commit. The `// 1.` … `// 7.` comments in `apply_rules`
are the design document's numbering, not the execution order (`:74-76`). Four
limits make a comforting general claim about coverage false:
- **Nothing is parsed.** The sensitive-name rule keys on `=` (`src/lib.rs:274`),
so `password=hunter2` redacts and `"password": "hunter2"` does not. There is
no JSON or YAML handling anywhere in the crate.
- **The name list has no left-hand word boundary**, so it matches any name
*ending* in a recognised one. `client_secret=` and `csrf_token=` redact
because `secret` and `token` are listed; `key=`, `session=` and `pwd=` stay in
the clear. Adding `\b` to "tidy up" that pattern would narrow, not widen it.
- **`string` never consults `SENSITIVE_HEADERS`.** Redacting a header value is a
separate, explicit call the caller wires up
(`docs/explanation/what-rtb-redact-does-not-do.md:60-74`).
- **The catch-all is length, not meaning.** A whitespace-bounded run of 40 or
more characters is redacted (`src/lib.rs:309`) — most opaque tokens, and a
bare git SHA-1 along with them.
## The trap that costs the most
`string` runs a fast-path pre-check, `fast_has_sensitive_anchor`
(`src/lib.rs:126-139`), before any rule. If the input carries none of the anchor
characters and no long run it is returned borrowed and **not one pattern
executes**, so a rule added to `apply_rules` does nothing for inputs the
pre-check rejects. The consequence (`docs/reference/rules.md:31-47`): an AWS
access key ID is twenty uppercase alphanumerics, so `AKIAIOSFODNN7EXAMPLE` alone
trips no anchor and comes back untouched, though the provider-prefix rule would
have matched it. Put a full stop in the line and it redacts. Add a rule, add its
anchor, and test the bare unpunctuated case.
## How a change gets out
`rtb-chat` and `rtb-telemetry` depend on this crate, both non-optionally and both
on `rtb-redact = "0.6.0"`. Nothing else in `phpboyscout/rust/*` does, and neither
does `rust-tool-base` directly — there those two are themselves optional, gated
on its `chat` and `telemetry` features, so an application on the umbrella reaches
this crate only through chat or telemetry.
Resolution is by published crates.io version, never by path, and there are two
gates. That `"0.6.0"` is a caret admitting any `0.6.x`, but both consumers commit
a `Cargo.lock` resolving `0.6.1` against this crate's `0.6.3` — so a fix reaches
them when their lockfile moves, not when you tag. Nothing behavioural is held
back by that today: 0.6.2 and 0.6.3 are CI, branding and dependency commits.
## The quality gate
`just ci` runs `fmt-check`, `lint`, `docs`, `test` and `audit` (`justfile:35`),
and passes clean on `main`. Run it before raising a merge request. Tests are 19
unit cases in `tests/unit.rs` plus a cucumber suite (`tests/bdd.rs` over
`tests/features/`). The CI gates are MR-only (`.gitlab-ci.yml:4-8`) — `main` runs
colophon and the docs deploy, not the gate — so a green `main` is not
evidence a change was checked. Releases run through colophon, tagging
`rtb-redact-v<version>` (`.colophon.yaml`); cicd's `rust-semver` runs
`cargo-semver-checks` on the release MR, so an unintended public-API break
surfaces at release time, not review time. Not the releaser-pleaser flow the Go
side uses.
## Which skills apply here
> **The estate has no Rust-specific skills yet.** The table below is the
> language-agnostic set only. That is a known gap rather than an oversight, and
> this table is worth revisiting once it is filled.
| Touching a pattern, a threshold, or the anchor pre-check | `centralize-security-footguns` |
| Before stating what this crate covers, anywhere | `checkable-claims`, `verify-dont-trust` |
| Deciding when a rule change is finished | `definition-of-done` |
| Starting a feature, a component, or any non-trivial change | `spec-driven-development` |
| Working in a repo other than the one you were invoked in | `cross-repo-worktree` |
| Writing or restructuring this crate's documentation | `diataxis-docs` |
| Weighing a Renovate bump, including the pinned toolchain | `assess-before-bumping` |
| Writing a commit message or a merge request description | `conventional-commits`, `pre-1-0-release-safety` |
| Committing, branching, merging, or opening a merge request | `forge-publish-workflow` |
| Releasing several crates in this toolkit together | `release-train` |
> Skills are a Claude Code mechanism, shipped by the
> [phpboyscout marketplace](https://gitlab.com/phpboyscout/claude-code-plugins).
> An agent without them should treat a named skill as a topic to ask about
> rather than a file it can load.
## House rules
- Linear history. Rebase and fast-forward; never squash-merge from the UI.
- Conventional Commits, and the type decides whether a release is cut. `feat`,
`fix`, `perf` and `refactor` release. A change that repoints or removes a public
interface is `feat`, not `refactor`, or it ships as a patch.
- No AI attribution in anything published, and never at-mention anyone.
- Never cut a release yourself. That is the maintainer's call, every time.