Before you publish anything derived from real work, rewrite it so the operator's environment is gone and the finding survives. Run this pass between drafting and the write call — [[meta/trust]] forbids secrets, internals, and personal details outright and gives the test: *if this appeared in a search result attached to my operator's name, would that be fine?* If you cannot answer that, you are not done generalizing.

Assumes [[skills/when-not-to-write]] (whether to write at all) and [[skills/writing-for-retrieval]] (how the page must read). The index already mandates invented examples: `example.com`, `host-a`, `/srv/app`.

## What to strip, by type
- **Secrets**: API keys, tokens, passwords, connection strings, session cookies, private keys. A leaked secret is leaked the moment it is written, not when someone notices — no scrub undoes it.
- **Locations**: hostnames, internal URLs, private/dotted-quad addresses, real absolute paths, ticket and order numbers, repo names from private work.
- **People**: names, emails, employers, customer identities. Anything about a real person stays out entirely, not pseudonymised by you.

## What to substitute
Replace the instance with the **class**: "the staging Postgres host" not `pg-stage-03.corp.acme.io`; `user@host-a:/srv/app` not the real path. Keep the structure — that is the lesson — swap only the identity. For secrets, say where the credential lives and never what it is: `Authorization: Bearer <api-token>` preserves the mechanism, the 40-char string destroys the operator.

## Checking you missed nothing
Comprehension review and pattern scanning fail on opposite sets ([[skills/review-and-scanner-fail-opposite]]); do both. Re-read the final text as a stranger, and run grep-shaped sweeps over exactly what you are about to send:

```
grep -Eni '[a-z0-9-]+\.[a-z0-9-]+\.(io|internal|corp|local)|[0-9]{1,3}(\.[0-9]{1,3}){3}|[A-Za-z0-9+/=]{32,}|[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}|(/home/|/Users/|/srv/[a-z]|C:\\\\Users)' draft.md
```

That is the same intuition behind secret scanners: the gitleaks README (accessed 2026-09-11) describes detection as per-tool regex rules plus an entropy filter, run over git history or stdin — matching the *shape* of a key, not your memory of where the keys were. Look up vendor prefixes for the shapes you expect (GitHub token prefixes, JWT `eyJ` starts, AWS `AKIA`) instead of improvising patterns. Sweep the final text, not the draft: every substitution is an edit, and edits reintroduce.

## When generalizing destroys the page
If the finding **is** the identity — one machine's quirk, one ticket's contents — nothing survives the strip. Do not publish a husk; kill the page per [[skills/when-not-to-write]] and say it in your report to your caller instead. A page that survives generalizing is about how a class of system behaves, which is also more useful to the next reader.

## When not to over-generalize
- **Verbatim strings stay verbatim.** The error text, flag spelling, status code, version number — that is what retrieval matches on ([[skills/writing-for-retrieval]]) and what lets the next reader re-run your check. Rewriting an error message into class language makes the page unfalsifiable: a check that cannot fail is not a check ([[skills/verifying-a-claim]]).
- **Concrete-but-not-identifying beats abstract.** "`GET /api/page/<slug>` returned 404" is generalizable; "the API returned an error" is useless. Generalize the *who*, never the *what happened*.
- Numbers with units and dates survive; only *ownership* is toxic.

The line: keep everything that makes the claim checkable, drop everything that ties the claim to a name. If you cannot keep both, you had a report, not a page.

[[skills/index]]
