diff --git a/docs/plans/M5b/14-runbook.md b/docs/plans/M5b/14-runbook.md new file mode 100644 index 0000000..c6cbbcb --- /dev/null +++ b/docs/plans/M5b/14-runbook.md @@ -0,0 +1,78 @@ +# M5b task 14: the runbook entry `sanitizer-rules` + +**Branch:** `m5b` (run `git switch m5b`; `git status --short` must be empty, otherwise stop) +**Commit subject:** `docs: runbook: sanitizer-rules, how to change a sanitizer rule` + +## Goal + +The M5b spec, section 6.5, gives the procedure for changing a sanitizer rule. Tasks 06, 08 and 12 +added the runbook entries that code points to; this task adds the last one, `sanitizer-rules`, +which no code points to, and makes the entries `memory-write-refused` and `memory-write-flagged` +send the reader to it instead of to the spec. Only `docs/runbook.md` changes. No code changes. + +## Files + +- Given: `docs/plans/M5b/files/runbook-sanitizer-rules.md`, appended verbatim. +- Modify: `docs/runbook.md` (edits R1 and R2, then the append). + +## The edits, exactly + +**R1.** `docs/runbook.md`, in the entry `memory-write-refused`, under **Fix.**: the lines + +```text +Ordinary text refused by mistake: add it to the code-work corpus and narrow the rule, as the M5b +spec's section 6.5 says; the change reaches the agent when it is rebuilt. +``` + +become + +```text +Ordinary text refused by mistake: add it to the code-work corpus and narrow the rule, as +[`sanitizer-rules`](#sanitizer-rules) says; the change reaches the agent when it is rebuilt. +``` + +**R2.** `docs/runbook.md`, in the entry `memory-write-flagged`, under **Fix.**: the lines + +```text +stays in `notes.md`, which is append-only. If the rule catches ordinary text, narrow it as the M5b +spec's section 6.5 says. +``` + +become + +```text +stays in `notes.md`, which is append-only. If the rule catches ordinary text, narrow it as +[`sanitizer-rules`](#sanitizer-rules) says. +``` + +## Steps + +- [ ] **1. Edits R1 and R2.** Each "before" block is in `docs/runbook.md` exactly once. Change + nothing else in the file. Then `grep -c "section 6.5" docs/runbook.md` prints `0`. +- [ ] **2. The entry.** From the repository root: + + ```sh + { echo; cat docs/plans/M5b/files/runbook-sanitizer-rules.md; } >> docs/runbook.md + ``` + + Then `grep -c '^## sanitizer-rules$' docs/runbook.md` prints `1`, and + `grep -c '(#sanitizer-rules)' docs/runbook.md` prints `2`. +- [ ] **3. Run the gate.** `make gate`, with a shell timeout of 600000 ms. Expected last line: + `gate: ok`, with 1118 tests, the same count as before this task: no test changes. +- [ ] **4. Log and commit.** `git status --short` lists `docs/runbook.md` and your log as + modified, and nothing else. Stage with `git add docs/runbook.md docs/implementer-log.md`, then + commit as `AGENTS.md` says. + +## Done when + +- `make gate` prints `gate: ok` with 1118 tests. +- `git diff HEAD~1 --stat` lists only `docs/runbook.md` and `docs/implementer-log.md`. + +## Stop and report if + +- A "before" block of R1 or R2 is not in `docs/runbook.md` exactly as shown, or is there more than + once. +- `docs/runbook.md` already has a line `## sanitizer-rules`. +- A grep in step 1 or 2 prints another number. +- The gate fails, or passes with a count other than 1118. This task changes no code, so do not + try to fix it: stop, and give the gate's last lines. diff --git a/docs/plans/M5b/files/runbook-sanitizer-rules.md b/docs/plans/M5b/files/runbook-sanitizer-rules.md new file mode 100644 index 0000000..f0ac540 --- /dev/null +++ b/docs/plans/M5b/files/runbook-sanitizer-rules.md @@ -0,0 +1,41 @@ +## sanitizer-rules + +**What you see.** Not a failure: the procedure for changing a sanitizer rule, which the entries +`memory-write-refused` and `memory-write-flagged` send you to when a rule catches ordinary text, +or which you follow when an attack gets through. The rules are in `crates/bxcore/src/sanitize/`: +`refuse.rs` (6.1 of the M5b spec), `tags.rs` (the chat-template tags that are neutralised, 6.2) +and `flag.rs` with `normalise.rs` (6.3). + +**Why.** The rules are compiled in rather than read from a file, so that every change passes the +two corpora and review, and a typo cannot switch a rule off (M5b spec, section 6.5). A rule that +refuses code is as much a bug as one that lets an attack through: the agent works on code +projects, and a flagged note makes every session that recalls it untrusted. + +**Confirm.** The evidence, before anything changes: + +- a rule's name in `bxctl memory log` (`flagged `), in `memoryd`'s log, or in a refusal the + agent passed on; +- `bxctl sanitize check` with the text, which names each rule that fires and writes nothing; +- a device check, a review or new research that shows an attack the rules miss. + +**Fix.** In this order, one commit: + +1. **The case first.** Add the text to a corpus, with a comment saying where it was seen: the + attack corpus if it must be caught, the code-work corpus if it must pass. The corpora are the + given tests `crates/bxcore/tests/sanitize_refuse_corpus.rs` (refuse), + `sanitize_corpus.rs` (neutralise) and `sanitize_flag_corpus.rs` (flag). Build a tag or a key + from pieces, as the files already do: never write one whole. Run `cargo test -p bxcore` and see + the new case fail. +2. **The rule.** Add or narrow the entry in the rule's table, with a comment naming its source. + Keep it as narrow as the evidence: a flag rule ported from Hermes keeps its attribution. +3. **The gate.** `make gate` runs both corpora for every action, so a rule that catches code + fails before it lands. +4. **The record.** A policy change (a new kind of refusal, or a rule moved from flag to refuse) + gets a row in `docs/decisions.md`; an ordinary rule change is its commit. + +A change reaches an agent when it is rebuilt and restarted. Stored memory is never rewritten: an +identity entry that breaks a new refuse rule leaves the memory block at once (see +`memory-entry-excluded`), and a note flagged under an old rule stays as it was written. + +**Check.** `bxctl sanitize check` with the text gives the new answer (nothing, and exit 0, for a +text that must pass). After the rebuild, the agent's next write of it is answered as expected.