feat: pii_core + redact-proxy MVP (non-streaming) #1
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/core-and-redact-proxy"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Build steps 1-2 of BRIEF.md.
What
packages/core(pii_core): checksum validators + Presidio recognizers for NL/DE/common PII.apps/redact-proxy: OpenAI-compatible/v1/chat/completionsthat redacts, forwards, restores. Dry-run, counts-only audit log, YAML config, Docker.Verified
uv run pytest -m "not integration": 70 passed.-m integration(realnl_core_news_mdNER + context scoring): passed.pre-commit run --all-files(ruff, format, import-linter, gitleaks, ...): passed.Not verified
docker compose up --build: Docker daemon was not running. Dockerfile and compose are untested.Known gaps
Streaming, tool-call arguments, per-request language detection, accuracy table, bare NL postcode (score 0.3 < threshold). See CHANGELOG.
🤖 Generated with Claude Code
https://claude.ai/code/session_01JZCbAYmY516SAe7umoLKWt
🤖 Auto-review status ·
jonah/safe-ai-toolsPR #1📋 Review posted — fixes required, awaiting human action.
Last updated: 2026-10-05T15:38:01.211Z
[STATUS: FIXES_REQUIRED]
Findings
Required fixes
apps/redact-proxy/src/redact_proxy/config.py+analyzer.py— configured entities are never validated (fail-open).Config.entitiesaccepts arbitrary strings. Verified against installed presidio: a typo'd entity mixed into a valid list (["PERSON", "NL_VBS"]) is silently ignored — under-redaction, the exact fail-open direction (AGENTS.mdXIX — "deny rather than grant"); a list containing only unknown names raises a rawValueError("No matching recognizers were found to serve the request.")from the recognizer registry inside the request thread (unhandled 500). Fix inbuild_analyze(apps/redact-proxy/src/redact_proxy/analyzer.py:29): after building the engine, assert everyconfig.entitiesentry is in the engine's supported entities for the language and raise a clear startup error otherwise. Cheap, and it converts a silent privacy regression into a fail-fast.README.md:39— repo toolchain is red.ruff format --check .reports README.md would be reformatted (missing blank line afterfrom openai import OpenAIin the ```python block). CI'schecksjob runs pre-commit ruff-format; the dev-group ruff (0.16.10, resolves viauv.lock) flags it either way. One blank line.Minor / non-blocking
apps/redact-proxy/src/redact_proxy/analyzer.py:22— a user-supplied partialspacy_modelsdict that omitsconfig.languageyields a bareKeyErrorat startup instead of a pydantic validation error. Add a model-level validator so config errors are all one kind.[LABEL_n]that later collides with a mapped placeholder,restoresubstitutes the real PII value for the user's literal in the reply — round-trip distortion, not a leak. Worth a line in the README limitations rather than a code change.contentstrings and text-type content parts are redacted;tool_calls[].function.arguments, messagename, and response-side non-message.contentfields pass through untouched. Consistent with the MVP docstring/README "non-streaming MVP", but thetool_callsargs path is a genuine PII gap the README doesn't state — add it to the limitations.analyzer.py47% coverage; CI runs-m "not integration", so the NER wiring is only exercised via the skip-guarded integration test — fine for now (hermetic-by-default is the right call), worth a milestone ticket.apps/redact-proxy/tests/conftest.py:18usesrec.analyze(text, rec.supported_entities)without conflict resolution — fine for hermetic tests, but note it diverges from production dedup behavior; the app-level tests cover the composed path.app.py:139(healthz body),app.py:147-148(models 502 path),upstream.py:23,config.py:59— trivial, no gate.File/function discipline, naming, error contracts, layering, secret hygiene, and test determinism are all well above the bar; the two required fixes are both small and local.
🤖 Auto-review status ·
jonah/safe-ai-toolsPR #1✅ PR is clean — no fixes required.
Last updated: 2026-10-05T18:43:59.729Z
Addressed in
860c700(v0.1.1):entitiesnow raise a clear ValueError at startup inbuild_analyze, before any model load (test:test_config.py).spacy_modelsmissing the configured language is now a pydantic validation error.PR #2 rebased onto this.
[STATUS: CLEAN]
Both previously-required fixes are implemented and verified; no new issues found. One non-blocking observation below.
Verification of prior required fixes
apps/redact-proxy/src/redact_proxy/analyzer.py:24-28now builds the registry first and raisesValueError(f"Unknown entities for '{language}': ...")for anyconfig.entitiesentry the registry doesn't serve, beforeNlpEngineProviderloads a model. Confirmed against installed presidio:RecognizerRegistry.get_supported_entities(languages=[...])exists with that signature, filters recognizers by language, and the default entity set validates clean fornl,de, anden. Since__main__.py:21callsbuild_analyze(config)beforeuvicorn.run, this is a true startup error, not a per-request 500.README.md:38blank line added. Verifieduv run ruff format --check .passes — ruff 0.16.10 does format python blocks inside markdown (confirmed with a temp file), so the gate was real and is now green.spacy_modelsnow a pydanticmodel_validatorerror (config.py:51-55), placeholder-collision limitation documented (README.md:69), and thetool_callslimitation was already present (README.md:67). Newtests/test_config.pycovers both paths (100%);analyzer.pycoverage 47% → 72%.Checks run (all green)
pytest -m "not integration": 72 passed;pytest -m integration: 1 passed (real spaCy model through the reorderedbuild_analyze)ruff check ./ruff format --check .: clean ·lint-imports: 2 contracts keptSKIP=import-linter pre-commit run --all-files(CIchecksjob equivalent): all hooks passuv lock --check: lockfile fresh ·VERSION+CHANGELOG.mdboth bumped, satisfying the CIpolicyjob · working tree cleanMinor / non-blocking
apps/redact-proxy/src/redact_proxy/analyzer.py:21— docstring claimsOSError: the configured spaCy model is not installed, but presidio'sSpacyNlpEngine/SlimSpacyNlpEngineauto-download missing models viaspacy.cli.downloadinstead (observed firsthand: a 541 MB fetch whenbuild_analyzeran without a model). Two consequences: the documentedOSErroris effectively dead, and the README's "the only outbound call is the one to the upstream you configure" is untrue if the operator skips the model-install step. Docker pre-installs models, so this is a bare-metal/docs nit only — a one-line docstring correction, or pinauto_download=Falseif strict offline startup matters.