Contextual name redaction for AI context: replace, don't remove - #93
Merged
Merged
Conversation
Context MCP tools now default to a Redacted layer: the original title or label with only sensitive spans replaced by typed stable tokens (PERSON_, EMAIL_, PHONE_, PERSONNUMMER_, ID_). Raw evidence stays unchanged in storage; the dashboard allowlist is unchanged. - server/contextual_redaction.py: local, deterministic name detection (bundled US Census 1990 / SCB 2022 name lists, EN+SV context cues, pairs, coordinations, Surname-First order, learned identities, stoplists for apps/UI/months/organizations, ambiguous-name handling, never/always-redact lists). ~20 us per title, cached per string. - server/ai_context.py + ai_context_routes.py: AI context detail setting (Redacted default | Full), org lock (Gateway force_redacted_ai_context or managed config), one middleware choke point for requests marked X-OpenWorkGraph-Context: ai, fail-closed, detail level header. - MCP: every request marked as AI context; every tool result carries detail_level. - Dashboard: AI context detail control with never/always lists; export gains 'Redact names in export' (default off, ticket-bound). - Fix: identities learned from 'Name <email>' while serving a response were forgotten by the next response; now kept in process memory (never written on read paths) and reused as the same token. - Eval: 246 labeled titles; name recall 0.982, over-redaction 0.010. - tests/test_release_version_v087.py aligned with shipped 0.91.0.
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
AI apps connected through MCP now get Redacted context by default: the original title or label, with only the sensitive span replaced by a typed stable token.
Three layers
PERSON_ EMAIL_ PHONE_ PERSONNUMMER_ ID_What's in it
server/contextual_redaction.py: runs locally with no network. It uses:from/for/to/with/by/about/re/cc/chat with/meeting with/call/Dr./Mr./Ms./Hej/från/för/till/med/av/hos/om/möte med/samtal med, plus relation words);First Surname,First and/och/&// First,Surname, FirstandSurname First, a segment that is only a first name, possessives;Never replaced: apps and sites, UI vocabulary, months and days, organizations that are also surnames (
Ericsson), words before place/organization nouns (Hope Street), and the user's never redact list. Always redact is supported. Ambiguous names (May, Bill, Grace, Paris, Claude…) need a listed surname or a strong cue. OWNER handling is unchanged.One choke point (
server/ai_context.py,server/ai_context_routes.py): MCP requests carryX-OpenWorkGraph-Context: ai, and the whole JSON response is transformed once according to the effective detail level./v1/tasks,/v1/summary,/v1/procedural-memory/*,/v1/task-context).Setting "AI context detail" under Connect → Connections: Redacted (default) or Full, plus never/always lists. Stored in
config.jsonunderai_context.force_redacted_ai_context(restrictive merge, cached by the sync worker) or the managedconfig.jsonkeyorganization_ai_context_detail: "redacted". Choosing Full while locked returns 409.detail_levelin every MCP response, taken from the level the server actually applied (X-OpenWorkGraph-Detail-Level).Export: new Redact names in export (default off), using the same redactor. The flag is bound into the short-lived export ticket, so a redacted ticket can't be replayed unredacted.
The learned-identity bug
Reproduced: an identity seen as
Name <email>while serving a response was forgotten by the next response.persist_registry=False(by design, read paths never write the registry), so the learned alias was dropped./v1/eventsalready worked, verified end to end.Name lists: sources and licenses
server/name_lexicon/holds names only, about 62 KB: 4,387 first names and 4,196 surnames. It's rebuilt byscripts/build_name_lexicon.py; nothing is downloaded at runtime.The ambiguous-name list is hand-curated in code, not data.
Evaluation
tests/fixtures/redaction_eval.jsonlhas 246 hand-labelled titles and labels, 193 English and 53 Swedish, with 113 names and 138 negatives.scripts/redaction_eval.pyaligns input and output by tokens:Remaining errors are documented in
docs/OWNER_REDACTION.md:Bill Gates,Florence Nightingale Museum.Morgan Stanley,Jack Daniels,Martin Luther King Jr. Day), which the never-redact list covers;The first metric version aligned characters and wrongly credited "Anna Svensson" → "PERSON_…" with shared letters. It now aligns on word tokens.
Tests
tests/test_ai_context_mcp_v092.py: a real API process throughserver.enterprise_runnerand the real compact stdio MCP server.Open email from Anna Svensson, window titleRe: Contract for Anna Svensson - Gmail.Open email from PERSON_andRe: Contract for PERSON_do, and every result hasdetail_level: "redacted".tests/test_contextual_redaction_v092.py(23 tests):tests/test_ai_context_http_v092.py:/v1/workflow-tracewith 600 events measured 129 ms baseline and 132 ms with AI redaction (bound: less than 3× + 0.5 s);tests/js/connections.test.mjs: the dashboard control and the export checkbox.node --test tests/js/*.test.mjs: 51/51.scripts/check_injected_dashboard_js.py: OK.test_presentation_redactionpins the export line, so the code was shaped to keep it.tests/test_release_version_v087.pyexpected0.90.0after the v0.91.0 release (it already failed onmain) and now matches the shipped version.test_v47pinnedsecure_get("/v1/ai-access")exactly; it now checks the call prefix, because the call passesclient.config.json, and the lock disables Full.Docs
README,
docs/MCP_ARCHITECTURE.md,docs/PRIVACY_AND_DATA.md,docs/OWNER_REDACTION.md(with the metrics),AI_GUIDE.md(detail_level),docs/CHANGELOG_V092.md(unreleased; no version bump), andserver/name_lexicon/README.md.