IR-149 (b), as ruled by doyle `YAS7KZYK`: the thin lane runs the docs-drift gate (`xtask check`), which used to run in golden only.

- **Classifier.** `classify-changes.py` emits `docs-drift` = code OR a docs-class path `xtask check` reads (`DRIFT_INPUT_DOCS` = `CHANGELOG.md`), and logs `CLASSIFY_DOCS_DRIFT docs-drift=<t|f> reason=<...>`. Conservative exits and a missing output both run the leg.
- **Why not `code == 'true'`.** A CHANGELOG-only release lane classifies `code=false` and can red the check through `gen_changelog`. Grouping the leg with unit and lint is the tidy-up that reopens the gap.
- **Derived set.** The selftest re-derives the drift set from xtask's `root.join("<literal>")` reads, with a control that the census sees `CHANGELOG.md`.
- **Tests.** Selftest 34/34. Mutation controls red 3, 4 and 2 tests.
- **ci.yml.** A new Linux `docs-drift` job, gated `!= 'false'`. It runs `cargo build -p spt --bin spt` and `xtask check` as SEPARATE timed steps, so the thin-lane cost is this run's step time.
- **Traceability.** `REQ-CI-THIN-DOCS-DRIFT` (doc, impl, unit); `traceable-reqs check` exits 0.

Pre-registered before push: `.spt/preserved/331/hertz/ir149-acceptance/PREREGISTRATION-IR149.md`. The cost is taken in an exclusive box window granted by doyle.

Lands via doyle.

Co-authored by: hertz

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KR5QdzrHXYGL1djRTBNKEG
