diff --git a/.github/required-checks.json b/.github/required-checks.json
index d7843e7..76b9664 100644
--- a/.github/required-checks.json
+++ b/.github/required-checks.json
@@ -1,4 +1,4 @@
{
- "required": ["quality", "tests", "security", "Analyze (python)"],
- "decisional": ["quality", "tests", "security"]
+ "required": ["quality", "tests", "security", "docs-privacy", "Analyze (python)"],
+ "decisional": ["quality", "tests", "security", "docs-privacy"]
}
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 4600eef..1f9bf0a 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -103,8 +103,9 @@ single-source runners under `scripts/gates/` (see
- **`tests`** — `discover_floors.py --run` (the standalone `# FLOOR` floors) then
`pytest tests/`.
- **`security`** — `bandit` over the tree.
+- **`docs-privacy`** — private-marker scan over `docs/**`.
-A fourth check, **`Analyze (python)`** (CodeQL), is **non-decisional**: SARIF
+A fifth check, **`Analyze (python)`** (CodeQL), is **non-decisional**: SARIF
upload is blocked on the free private tier, so it shows nominally red and is
never a merge signal. Read the verdict on the **machine field**
(`statusCheckRollup[].name` / `.conclusion`) filtered to
diff --git a/CI_CD.md b/CI_CD.md
index 9fd8e92..48d2d3c 100644
--- a/CI_CD.md
+++ b/CI_CD.md
@@ -21,10 +21,12 @@ flowchart TD
PR --> Q["quality
ruff · compile · skills"]
PR --> T["tests
auto-discovered module floors"]
PR --> S["security
Bandit SAST"]
+ PR --> DP["docs-privacy
private-marker scan over docs/**"]
PR --> CQ["Analyze (python)
CodeQL security-extended"]
Q --> G{"All required checks green?
branch up to date?
no CodeQL alert ≥ High / Errors?"}
T --> G
S --> G
+ DP --> G
CQ --> G
G -- yes --> M(["Merge to main"])
G -- no --> X(["Merge blocked"])
@@ -49,6 +51,7 @@ but gets **none** of Layer B — until it is recreated, the workflows still *run
| `ci.yml` | PR to `main`, push to `main` | `quality` | `ruff check` → `ruff format --check` → `doc-drift` (tracked docs — `scripts/gates/doc_drift_targets.tsv`) → `compileall -q skills` |
| `tests.yml` | PR to `main`, push to `main` | `tests` | the auto-discovered module floors + `pytest tests/` (exit 0/1); the first non-zero exit fails the check |
| `security.yml` | PR to `main`, push to `main` | `security` | Bandit SAST: `bandit -c pyproject.toml -r .` |
+| `docs-privacy.yml` | PR to `main`, push to `main` | `docs-privacy` | private-marker scan over `docs/**`: `bash scripts/gates/docs-privacy.sh` |
| `codeql.yml` | PR + push + weekly (Mon 06:00 UTC) | `Analyze (python)` | CodeQL `security-extended` — the analysis **runs** (content gate is the Layer-B ruleset, below) |
| `release.yml` | push of a tag `v*` | `release` | package the `.skill` deliverable + attach it to a Release; a version guard aborts if the tag ≠ the artifact's `metadata.version` |
| `action-drift.yml` | weekly (Mon 06:00 UTC) + manual dispatch | `drift` (detector, not a gate) | re-checks the SHA-pinned action versions against upstream tags; opens an issue on drift — never edits a workflow, never blocks a merge |
@@ -119,7 +122,7 @@ Configured via `gh api` (METHOD §7/§10) and recreated in any fork that wants t
protection.
**Branch protection on `main`:** require a PR; require status checks
-`quality, tests, security, Analyze (python)`; require branches up to date; block force
+`quality, tests, security, docs-privacy, Analyze (python)`; require branches up to date; block force
pushes; restrict deletions; **do not allow bypassing (admins included)**.
**Code-scanning ruleset** (targeting `main`): require code-scanning results, tool **CodeQL**,
@@ -169,6 +172,7 @@ nothing **blocks** a merge — the blocking comes from Layer B.
| `quality` required check | B (branch) | broken lint / format / compile |
| `tests` required check | B (branch) | a failing skill floor |
| `security` required check | B (branch) | a Bandit finding |
+| `docs-privacy` required check | B (branch) | a private-marker leak into `docs/**` |
| `Analyze (python)` required check | B (branch) | a CodeQL analysis failure (the run, not an alert) |
| Code-scanning ruleset (CodeQL ≥ High / Errors) | B (ruleset) | a CodeQL **alert** (the finding) |
| Secret scanning + push protection | B (Adv. Security) | a secret at push time |
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index accdc5c..2439b54 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -18,7 +18,7 @@ This guide is for two audiences:
## Ground rules
`main` is the protected trunk: every change is meant to land through a pull request that
-passes the required checks (`quality`, `tests`, `security`, `Analyze (python)`).
+passes the required checks (`quality`, `tests`, `security`, `docs-privacy`, `Analyze (python)`).
Honest note on enforcement: on this repository's current tier, the **Layer-B** settings that
*enforce* the rule (branch protection + the code-scanning ruleset) are **defined in files but
@@ -72,7 +72,7 @@ Notes:
flowchart LR
F["Fork or branch"] --> L["Run gates locally
ruff · bandit · compile · floors"]
L --> PR["Open a PR to main"]
- PR --> C{"quality · tests · security · Analyze (python)
all green? branch up to date?"}
+ PR --> C{"quality · tests · security · docs-privacy · Analyze (python)
all green? branch up to date?"}
C -- yes --> M(["Squash-merge"])
C -- no --> Fix["Fix and push again"]
Fix --> C
@@ -86,6 +86,7 @@ flowchart LR
- **`quality`** — ruff lint + format, projected-doc drift (`doc-drift`), and the `compileall` sweep over `skills/`.
- **`security`** — Bandit security scan.
- **`tests`** — the auto-discovered module floors plus `pytest tests/`.
+ - **`docs-privacy`** — private-marker scan over `docs/**`.
- **`Analyze (python)`** — CodeQL semantic security analysis.
6. Your branch must be **up to date with `main`** before merging.
7. Once Layer B is active, a merge is also blocked if CodeQL reports a code-scanning alert