feat(context): enforce sensitivity policy — drop/redact restricted items from prompts#98
Merged
feat(context): enforce sensitivity policy — drop/redact restricted items from prompts#98
Conversation
Add sensitivity filtering to the context compilation pipeline. Items
whose sensitivity level meets or exceeds ContextPolicy.sensitivity_floor
are now dropped (default) or redacted before reaching the prompt.
- Add ContextItem.sensitivity field (default: Sensitivity.public)
- Add ContextPolicy.sensitivity_action field ('drop' or 'redact')
- Add context/sensitivity.py with apply_sensitivity_filter() and
MaskRedactionHook (replaces text with '[REDACTED: {sensitivity}]')
- Wire sensitivity filter into manager._build() between dependency
closure and firewall (step 2b)
- Record sensitivity drops in BuildStats.dropped_reasons['sensitivity']
- Export MaskRedactionHook from context/ and top-level __init__.py
- Add 19 tests in tests/test_sensitivity.py covering drop mode, redact
mode, hook behavior, edge cases, serde roundtrip, and integration
Closes #16
…vity enforcement - AGENTS.md: update context/ pipeline description to include sensitivity filter - CLAUDE.md: note sensitivity.py in module responsibility map - .github/copilot-instructions.md: add sensitivity_filter step to pipeline, add MaskRedactionHook to key types - docs/architecture.md: add sensitivity_filter as pipeline step 3 (now 8 steps) - docs/concepts.md: add Sensitivity Enforcement section
There was a problem hiding this comment.
Pull request overview
This PR closes #16 by enforcing the existing sensitivity policy in the Context Engine so sensitive ContextItems are dropped (default) or redacted before they can reach the rendered prompt, reducing data-leakage risk.
Changes:
- Added
ContextItem.sensitivity(serde included) andContextPolicy.sensitivity_actionto support drop vs redact behavior. - Introduced
contextweaver.context.sensitivitywithapply_sensitivity_filter()and a built-inMaskRedactionHook. - Wired sensitivity filtering into
ContextManager._build()and added a dedicated test suite + documentation updates.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/contextweaver/types.py |
Adds sensitivity to ContextItem plus to_dict()/from_dict() support. |
src/contextweaver/config.py |
Adds sensitivity_action and updates policy docs. |
src/contextweaver/context/sensitivity.py |
Implements sensitivity ordering, hook resolution, and drop/redact filtering. |
src/contextweaver/context/manager.py |
Inserts sensitivity filter into the pipeline and updates BuildStats. |
src/contextweaver/context/__init__.py |
Re-exports sensitivity helpers. |
src/contextweaver/__init__.py |
Re-exports MaskRedactionHook at package level. |
tests/test_sensitivity.py |
Adds unit + integration coverage for drop/redact/serde behavior. |
docs/concepts.md |
Documents sensitivity enforcement behavior and stats. |
docs/architecture.md |
Updates pipeline stages to include sensitivity filtering. |
CHANGELOG.md |
Notes new enforcement and public API changes. |
AGENTS.md / CLAUDE.md / .github/copilot-instructions.md |
Updates internal docs/instructions to reflect the new step/type. |
You can also share your feedback on Copilot code review. Take the survey.
…tats select_and_pack() computes total_candidates from the post-filter list, so adding sensitivity_drops to dropped_count without also adjusting total_candidates broke the invariant: dropped_count + included_count <= total_candidates Now both total_candidates and dropped_count are incremented by sensitivity_drops, keeping BuildStats internally consistent.
Use get(..., 0) + N instead of direct assignment, matching the pattern in selection.py. Defensive against future multi-pass scenarios.
Raise ValueError for unrecognised sensitivity_action values (e.g. typos) instead of silently falling through to drop mode. Consistent with the existing _resolve_hooks() validation pattern in the same module.
…filter The guard checked floor_level > restricted, but restricted is the maximum Sensitivity level, making the branch unreachable dead code.
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
Closes #16.
The
Sensitivityenum andContextPolicy.sensitivity_floorexisted but had zero enforcement in the context pipeline. Items taggedrestrictedorconfidentialflowed into prompts unchecked — a data leakage risk.This PR wires sensitivity enforcement into the context compilation pipeline so that items at or above the configured floor are dropped (default) or redacted before reaching the prompt.
What changed
Implementation (
featcommit)src/contextweaver/types.pysensitivity: Sensitivity = Sensitivity.publictoContextItem; updatedto_dict()/from_dict()src/contextweaver/config.pysensitivity_action: str = "drop"toContextPolicy; updated docstringsrc/contextweaver/context/sensitivity.py_SENSITIVITY_ORDER,MaskRedactionHook,apply_sensitivity_filter(), built-in hook registrysrc/contextweaver/context/manager.pyapply_sensitivity_filter()into_build()(step 2b, between dependency closure and firewall); records drops inBuildStatssrc/contextweaver/context/__init__.pyapply_sensitivity_filter,MaskRedactionHooksrc/contextweaver/__init__.pyMaskRedactionHooktests/test_sensitivity.pyCHANGELOG.md[Unreleased]Documentation (
docscommit)docs/architecture.mddocs/concepts.mdAGENTS.mdCLAUDE.md.github/copilot-instructions.mdHow it works
sensitivity >= floorare removed from candidates before scoring or rendering.BuildStats.dropped_reasons["sensitivity"]records the count.sensitivity_action="redact"):MaskRedactionHookreplaces item text with[REDACTED: {sensitivity}]while preserving all metadata. The item stays in the pipeline.ContextPolicy(sensitivity_floor=Sensitivity.confidential, sensitivity_action="drop"|"redact", redaction_hooks=["mask"]).sensitivity=Sensitivity.publiconContextItemis fully backward-compatible.Testing performed
Total: 433 passed, 0 failed.
Test coverage (19 new tests)
to_dict()/from_dict()roundtrip with sensitivity fieldContextManager.build_sync()excludes/redacts sensitive contentRisks / edge cases
ContextItem.sensitivitydefaults toSensitivity.public— existing code unaffected.Sensitivity.publicfor serialized data without the field.