feat(privacy-llm): foundation — PII redactor + approved-LLM contracts#48
Merged
Conversation
potiuk
added a commit
that referenced
this pull request
May 4, 2026
… commit CI failure on PR #48 was doctoc — the new "Privacy-LLM" section in AGENTS.md and the new projects/_template/privacy-llm.md template both needed their TOCs regenerated. Local `prek run --all-files` confirms every hook passes. In the same PR, the AGENTS.md "Local setup" section is restructured so prek-install is now the first imperative — moved to the top of the section, marked "MUST be run before any other work in this repository — including the first commit on a fresh clone", and augmented with a one-line pre-flight check (`test -x .git/hooks/pre-commit || prek install`) that agents and human contributors run before preparing any commit. The old prek paragraph at the bottom of "Local setup" is removed (its content folded into the new top-of-section block). The reasoning the new text spells out: a missing local hook turns into a CI failure on push (the prek workflow re-runs the same hooks against the diff) — exactly the failure pattern this PR is recovering from. Making the install + verify steps explicit and mandatory closes that gap. Generated-by: Claude Code (Claude Opus 4.7)
PR-1 of three. Lays the framework + docs for handling private mail
content; per-skill wiring is deferred to PR-2 (security@-touching
skills get the PII-redactor pre/post hooks) and PR-3 (private@-
touching skills get the approved-LLM gate).
What lands:
- tools/privacy-llm/{tool,pii,models}.md — the contracts: which
fields count as PII, the hash-prefixed identifier format
(R-a3f9d2, E-b8c247, IP-1a5cef, …), the local mapping store at
~/.config/apache-steward/pii-mapping.json (per the home-dir
credentials rule), the approved-model registry (Claude Code,
*.apache.org, local inference all default-approved; everything
else opt-in via <project-config>/privacy-llm.md).
- tools/privacy-llm/redactor/ — stdlib-only Python helper exposing
three console scripts: pii-redact (replace PII with identifiers),
pii-reveal (reverse before outbound), pii-list (debug). 48 unit
tests covering hashing determinism, idempotency, collision
extension, atomic writes, mode 0600, malformed-file handling.
- docs/setup/privacy-llm.md — copy-pasteable setup recipes for six
variants: Claude-only (default), local Ollama, local vLLM,
Apache-hosted, AWS Bedrock, direct Anthropic API. Marked
provisional pending ASF Legal Affairs ratification of an
authoritative approved-LLM list.
- projects/_template/privacy-llm.md — adopter starting point
pre-filled with the Claude-Code-only default.
- AGENTS.md — new "Privacy-LLM" section (sibling to "Confidentiality
of the tracker repository") codifying the three rules: reporter
PII never enters any LLM in the clear; <private-list> content
never reaches a non-approved LLM; adding a new LLM hop is a
deliberate act, not an emergent one.
- .pre-commit-config.yaml — new redactor-{ruff-check,ruff-format,
mypy,pytest} hooks mirroring the oauth-draft pattern.
The two privacy mechanisms are deliberately separate: PII redaction
(always-on for security@) is mechanically distinct from the
approved-model gate (pre-flight on private@-touching skills).
Conflating them is a real foot-gun the framework guards against.
Generated-by: Claude Code (Claude Opus 4.7)
… commit CI failure on PR #48 was doctoc — the new "Privacy-LLM" section in AGENTS.md and the new projects/_template/privacy-llm.md template both needed their TOCs regenerated. Local `prek run --all-files` confirms every hook passes. In the same PR, the AGENTS.md "Local setup" section is restructured so prek-install is now the first imperative — moved to the top of the section, marked "MUST be run before any other work in this repository — including the first commit on a fresh clone", and augmented with a one-line pre-flight check (`test -x .git/hooks/pre-commit || prek install`) that agents and human contributors run before preparing any commit. The old prek paragraph at the bottom of "Local setup" is removed (its content folded into the new top-of-section block). The reasoning the new text spells out: a missing local hook turns into a CI failure on push (the prek workflow re-runs the same hooks against the diff) — exactly the failure pattern this PR is recovering from. Making the install + verify steps explicit and mandatory closes that gap. Generated-by: Claude Code (Claude Opus 4.7)
6c451e7 to
a1755af
Compare
potiuk
added a commit
to andreahlert/magpie
that referenced
this pull request
May 4, 2026
Two follow-ups on top of the rebase: 1. Pin `lycheeVersion: v0.24.2` in `link-check.yml`. The lychee-action's default binary version is v0.23.0 today, which uses the old boolean form of `include_fragments` and rejects the `"anchor-only"` enum string this repo's `.lychee.toml` carries. The v0.24 line introduced the `--include-fragments=<none|anchor-only|text-only|full>` enum form the config relies on, so pin to v0.24.2 (the version the config is validated against locally) and document the why so a future bumper does not reach for the latest upstream blindly. 2. Fix a dangling internal link in `tools/privacy-llm/models.md`. That file links to `redactor/src/redactor/registry.py`, which does not exist yet (the same paragraph already says *"once PR-3 lands the gate-call wiring"*). The link slipped past CI because the link-check was still informational at the time PR apache#48 merged. Convert the markdown link into inline code so the forward-reference reads the same but stops claiming the file exists. This is exactly the regression PR apache#47's hard gate is meant to catch. Local validation against lychee v0.24.2 (Homebrew): 0 errors offline; 0 errors with external URLs (1088 links checked, 1043 OK, 45 excluded, 10 redirects). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
potiuk
added a commit
to andreahlert/magpie
that referenced
this pull request
May 4, 2026
Two follow-ups on top of the rebase: 1. Pin `lycheeVersion: v0.23.0` in `link-check.yml` and switch `.lychee.toml` to the boolean `include_fragments = true` that v0.23.x expects. The original PR landed `.lychee.toml` with the v0.24+ enum form `include_fragments = "anchor-only"`, which v0.23.0 (the `lychee-action@v2.8.0` default) rejects with *"expected a boolean"*. The natural fix is to bump the binary to v0.24+, but v0.24 changed the release-archive layout — the executable is now nested under `lychee-<arch>-<os>/` rather than at the top level — and the v2.8.0 action's install step still hard-codes the top-level layout, so `install -t … lychee` exits with *"cannot stat"*. Bumping the action SHA to one that handles the new layout is blocked on the ASF infrastructure-actions allowlist (only the `8646ba30...` v2.8.0 SHA is approved today). Until the allowlist gains a newer entry, the binary has to stay on v0.23.x and the config has to match. Pin the binary explicitly so the action's default version drifting later does not break us silently. The toml comment leaves the migration note in place so the next bumper flips both back together. 2. Fix a dangling internal link in `tools/privacy-llm/models.md`. That file links to `redactor/src/redactor/registry.py`, which does not exist yet (the same paragraph already says *"once PR-3 lands the gate-call wiring"*). The link slipped past CI because the link-check was still informational at the time PR apache#48 merged. Convert the markdown link into inline code so the forward-reference reads the same but stops claiming the file exists. This is exactly the regression PR apache#47's hard gate is meant to catch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
potiuk
added a commit
that referenced
this pull request
May 4, 2026
* ci: fix all broken links and flip link-check to hard gate Resolves the baseline of broken links that kept the lychee workflow in informational-only mode. Changes: - Fix 43 broken internal links across 15 files: - Remap README.md fragments moved to docs/security/process.md - Fix paths to images, pyproject.toml, dependabot.yml - Update stale skill names (setup-steward-upgrade → setup-steward/upgrade.md) - Remove links to non-existent config/ directory - Fix docs/docs/setup/ path duplication - Remove link to missing search-queries.md (not yet written) - Fix external apache.org voting fragment - Update CONTRIBUTING.md and AGENTS.md to reference .apache-steward-overrides/user.md instead of config/user.md. - Flip .github/workflows/link-check.yml from informational-only (continue-on-error: true, fail: false) to hard gate (continue-on-error: false, fail: true). Validation: lychee reports 0 errors, markdownlint 0 errors, check-placeholders OK. Signed-off-by: André Ahlert <andre@aex.partners> * ci(lychee): pin lycheeVersion to v0.23.0; fix dangling registry.py link Two follow-ups on top of the rebase: 1. Pin `lycheeVersion: v0.23.0` in `link-check.yml` and switch `.lychee.toml` to the boolean `include_fragments = true` that v0.23.x expects. The original PR landed `.lychee.toml` with the v0.24+ enum form `include_fragments = "anchor-only"`, which v0.23.0 (the `lychee-action@v2.8.0` default) rejects with *"expected a boolean"*. The natural fix is to bump the binary to v0.24+, but v0.24 changed the release-archive layout — the executable is now nested under `lychee-<arch>-<os>/` rather than at the top level — and the v2.8.0 action's install step still hard-codes the top-level layout, so `install -t … lychee` exits with *"cannot stat"*. Bumping the action SHA to one that handles the new layout is blocked on the ASF infrastructure-actions allowlist (only the `8646ba30...` v2.8.0 SHA is approved today). Until the allowlist gains a newer entry, the binary has to stay on v0.23.x and the config has to match. Pin the binary explicitly so the action's default version drifting later does not break us silently. The toml comment leaves the migration note in place so the next bumper flips both back together. 2. Fix a dangling internal link in `tools/privacy-llm/models.md`. That file links to `redactor/src/redactor/registry.py`, which does not exist yet (the same paragraph already says *"once PR-3 lands the gate-call wiring"*). The link slipped past CI because the link-check was still informational at the time PR #48 merged. Convert the markdown link into inline code so the forward-reference reads the same but stops claiming the file exists. This is exactly the regression PR #47's hard gate is meant to catch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Signed-off-by: André Ahlert <andre@aex.partners> Co-authored-by: Jarek Potiuk <jarek@potiuk.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 4, 2026
potiuk
added a commit
that referenced
this pull request
May 4, 2026
…#51) * feat(privacy-llm): PR-3 — approved-LLM gate-check + skill-side wiring PR-3 of the privacy-llm series. Lands the second mechanism from the original design (PR-1: foundation; PR-2: skill-side redaction wiring; PR-3: approved-LLM gate-check). Foundations and contracts were already on main via #48 and #50; this PR wires the gate-check helper that the contracts in `models.md` and `wiring.md` reference. What lands: - `tools/privacy-llm/checker/` (new, ~6 files) — stdlib-only Python sub-tool exposing one console script: `privacy-llm-check`. Parses `<project-config>/privacy-llm.md`, extracts the *Currently configured LLM stack* and *Approved third-party endpoints (opt-in)* sections, applies the approval rules from `tools/privacy-llm/models.md` (Claude Code default-approved; `*.apache.org` default-approved; localhost / 127.0.0.1 / ::1 default-approved; everything else requires opt-in entry with Data-residency + Approved-by lines that aren't placeholder text). Exit 0 on approval, 1 with stderr explanation on rejection, 2 if the config can't be located or parsed. 33 unit tests covering each rule, malformed configs, opt-in validation, CLI exit codes, and the shipped template. - `.pre-commit-config.yaml` — new `checker-{ruff-check, ruff-format,mypy,pytest}` hooks mirroring the redactor pattern. - `tools/privacy-llm/models.md` — replaces "PR-3 future" / "deferred from PR-1" placeholder language with the concrete `privacy-llm-check` invocation. Notes that the gate-call is required even for `<security-list>`-only skills (defence-in-depth: validates the adopter's config is in a sane state before any private content flows). - `tools/privacy-llm/wiring.md` — Step 0 pre-flight section now shows the actual `uv run --project … privacy-llm-check --reads-private-list` invocation. - `tools/privacy-llm/tool.md` — capability table gains a row for the checker sub-tool. - Skill files — every Gmail-touching SKILL.md (`security-issue-import`, `-sync`, `-invalidate`, `-cve-allocate`, `-import-from-md`) gains an explicit gate-check invocation in its Step 0 pre-flight bullet, with `--reads-private-list` set on `-sync` (which may escalate to PMC-private foundation lists). Tests: 33/33 checker tests pass. `prek run --all-files` clean (every existing hook + the four new checker hooks). The shipped `projects/_template/privacy-llm.md` is a fixture in the test suite — it must always parse + approve, ensuring the framework's default starting state is never broken. Generated-by: Claude Code (Claude Opus 4.7) * fix(privacy-llm): typos + CodeQL false positive in checker CI on PR #51 flagged two issues: 1. `typos` hook — `unparseable` should be `unparsable` (US spelling). Two occurrences: - tools/privacy-llm/checker/src/checker/check.py:76 (verdict reason for an unparsable URL host) - tools/privacy-llm/checker/src/checker/config.py:239 (host_of docstring) 2. CodeQL `py/incomplete-url-substring-sanitization` — `assert "apache.org" in v.reason` in test_apache_org_is_approved. False positive: v.reason is a human-readable message, not a URL being validated. Tightened the assertion to match the production code's exact phrasing (`"*.apache.org-hosted"`); now it's unambiguously not a URL check, and as a side-benefit it locks the user-facing reason text against accidental drift. Local prek run — all hooks pass including typos. Generated-by: Claude Code (Claude Opus 4.7)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR-1 of three. Lays the framework + docs for handling private mail content with privacy-aware LLM routing. Per-skill wiring is deferred to PR-2 (security@-touching skills get the PII-redactor hooks) and PR-3 (private@-touching skills get the approved-LLM gate).
Two distinct mechanisms, deliberately separated:
R-a3f9d2, …) before any LLM-bound step. Mapping kept local at~/.config/apache-steward/pii-mapping.json. Always-on for<security-list>content.<private-list>content can only reach LLMs in the approved registry. Default-approved: Claude Code itself,*.apache.org-hosted endpoints, local-only inference (Ollama / vLLM on127.0.0.1). Everything else (AWS Bedrock, direct Anthropic, …) is opt-in via<project-config>/privacy-llm.md.What lands:
tools/privacy-llm/{tool,pii,models}.md— the contracts.tools/privacy-llm/redactor/— stdlib-only Python helper, three CLIs (pii-redact,pii-reveal,pii-list), 48 passing unit tests.docs/setup/privacy-llm.md— six setup-variant recipes, marked provisional pending ASF Legal.projects/_template/privacy-llm.md— adopter starting point.AGENTS.md— new "Privacy-LLM" section codifying the three rules..pre-commit-config.yaml— newredactor-{ruff-check,ruff-format,mypy,pytest}hooks mirroring the oauth-draft pattern.Status: the default-approved registry reflects the framework maintainer's working position; ASF Legal Affairs has not yet ratified an authoritative list. When such a list lands, the registry will be updated to point at it as source-of-truth.
Test plan
pytest— 48 passedruff check src tests— cleanruff format --check src tests— cleanmypy— no issues found in 10 source filesVisual review of
AGENTS.mddiff — new "Privacy-LLM" section reads as a sibling to "Confidentiality of the tracker repository".Walk through one of the variant recipes in
docs/setup/privacy-llm.mdend-to-end.Sanity-check the redactor end-to-end: