mixin-sensitivity 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. mixin_sensitivity-0.4.0/.github/workflows/ci.yml +45 -0
  2. mixin_sensitivity-0.4.0/.github/workflows/cleanup-guard.yml +30 -0
  3. mixin_sensitivity-0.4.0/.github/workflows/dto-strict.yml +35 -0
  4. mixin_sensitivity-0.4.0/.github/workflows/no-ai-attribution.yml +73 -0
  5. mixin_sensitivity-0.4.0/.github/workflows/publish.yml +33 -0
  6. mixin_sensitivity-0.4.0/.github/workflows/ruff.yml +29 -0
  7. mixin_sensitivity-0.4.0/.gitignore +131 -0
  8. mixin_sensitivity-0.4.0/CHANGELOG.md +167 -0
  9. mixin_sensitivity-0.4.0/CODE_OF_CONDUCT.md +31 -0
  10. mixin_sensitivity-0.4.0/CONTRIBUTING.md +65 -0
  11. mixin_sensitivity-0.4.0/LICENSE +201 -0
  12. mixin_sensitivity-0.4.0/PKG-INFO +472 -0
  13. mixin_sensitivity-0.4.0/README.md +448 -0
  14. mixin_sensitivity-0.4.0/SECURITY.md +20 -0
  15. mixin_sensitivity-0.4.0/docs/STATUS.md +55 -0
  16. mixin_sensitivity-0.4.0/docs/apps/decorators/compliance.md +98 -0
  17. mixin_sensitivity-0.4.0/docs/apps/decorators/policies.md +293 -0
  18. mixin_sensitivity-0.4.0/docs/apps/decorators/sensitive.md +238 -0
  19. mixin_sensitivity-0.4.0/docs/apps/diagrams.md +195 -0
  20. mixin_sensitivity-0.4.0/docs/architecture/architecture.md +246 -0
  21. mixin_sensitivity-0.4.0/docs/audits/.gitkeep +0 -0
  22. mixin_sensitivity-0.4.0/docs/audits/2026-06-09-decorator-layer-security-audit.md +194 -0
  23. mixin_sensitivity-0.4.0/docs/reviews/.gitkeep +0 -0
  24. mixin_sensitivity-0.4.0/docs/reviews/2026-06-09-decorator-layer-review.md +256 -0
  25. mixin_sensitivity-0.4.0/mixin_sensitivity/__init__.py +35 -0
  26. mixin_sensitivity-0.4.0/mixin_sensitivity/common/__init__.py +1 -0
  27. mixin_sensitivity-0.4.0/mixin_sensitivity/common/constants/__init__.py +1 -0
  28. mixin_sensitivity-0.4.0/mixin_sensitivity/common/constants/metadata.py +9 -0
  29. mixin_sensitivity-0.4.0/mixin_sensitivity/config/__init__.py +1 -0
  30. mixin_sensitivity-0.4.0/mixin_sensitivity/config/_version.py +3 -0
  31. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/__init__.py +1 -0
  32. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/__init__.py +1 -0
  33. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/compliance/__init__.py +8 -0
  34. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/compliance/compliance_objects.py +27 -0
  35. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/pci_aware/__init__.py +7 -0
  36. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/pci_aware/pci_aware_objects.py +25 -0
  37. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/phi_aware/__init__.py +7 -0
  38. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/phi_aware/phi_aware_objects.py +25 -0
  39. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/pii_aware/__init__.py +7 -0
  40. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/pii_aware/pii_aware_objects.py +25 -0
  41. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/secret_aware/__init__.py +7 -0
  42. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/classes/secret_aware/secret_aware_objects.py +25 -0
  43. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/constants/__init__.py +6 -0
  44. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/constants/sensitive.py +20 -0
  45. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/sensitive/__init__.py +9 -0
  46. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/sensitive/sensitive_client.py +47 -0
  47. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/sensitive/sensitive_objects.py +54 -0
  48. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/__init__.py +1 -0
  49. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_compliance/__init__.py +1 -0
  50. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_compliance/test_compliance_objects.py +80 -0
  51. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pci_aware/__init__.py +1 -0
  52. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pci_aware/conftest.py +40 -0
  53. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pci_aware/test_pci_aware_objects.py +142 -0
  54. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_phi_aware/__init__.py +1 -0
  55. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_phi_aware/conftest.py +40 -0
  56. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_phi_aware/test_phi_aware_objects.py +159 -0
  57. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pii_aware/__init__.py +1 -0
  58. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pii_aware/conftest.py +40 -0
  59. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_pii_aware/test_pii_aware_objects.py +152 -0
  60. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_secret_aware/__init__.py +1 -0
  61. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_secret_aware/conftest.py +40 -0
  62. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_secret_aware/test_secret_aware_objects.py +154 -0
  63. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_sensitive/__init__.py +1 -0
  64. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_sensitive/conftest.py +160 -0
  65. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_sensitive/test_sensitive_client.py +386 -0
  66. mixin_sensitivity-0.4.0/mixin_sensitivity/decorators/tests/test_sensitive/test_sensitive_objects.py +280 -0
  67. mixin_sensitivity-0.4.0/mixin_sensitivity/py.typed +0 -0
  68. mixin_sensitivity-0.4.0/mixin_sensitivity/services/__init__.py +1 -0
  69. mixin_sensitivity-0.4.0/mixin_sensitivity/services/classify/__init__.py +10 -0
  70. mixin_sensitivity-0.4.0/mixin_sensitivity/services/classify/classify_objects.py +57 -0
  71. mixin_sensitivity-0.4.0/mixin_sensitivity/services/tests/__init__.py +1 -0
  72. mixin_sensitivity-0.4.0/mixin_sensitivity/services/tests/test_classify/__init__.py +1 -0
  73. mixin_sensitivity-0.4.0/mixin_sensitivity/services/tests/test_classify/conftest.py +39 -0
  74. mixin_sensitivity-0.4.0/mixin_sensitivity/services/tests/test_classify/test_classify_objects.py +224 -0
  75. mixin_sensitivity-0.4.0/pyproject.toml +90 -0
  76. mixin_sensitivity-0.4.0/uv.lock +478 -0
@@ -0,0 +1,45 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ matrix:
20
+ python-version: ["3.11", "3.12"]
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: astral-sh/setup-uv@v4
24
+ with:
25
+ enable-cache: true
26
+ - name: Install Python
27
+ run: uv python install ${{ matrix.python-version }}
28
+ - name: Sync dependencies
29
+ run: uv sync --all-extras
30
+ - name: Test
31
+ run: uv run pytest -v --cov --cov-fail-under=95
32
+
33
+ mypy:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+ - uses: astral-sh/setup-uv@v4
38
+ with:
39
+ enable-cache: true
40
+ - name: Install Python 3.12
41
+ run: uv python install 3.12
42
+ - name: Sync dependencies
43
+ run: uv sync --all-extras
44
+ - name: Type-check
45
+ run: uv run mypy mixin_sensitivity/
@@ -0,0 +1,30 @@
1
+ name: Cleanup Guard
2
+
3
+ on:
4
+ push:
5
+ branches: [main, "refactor/*", "feat/*"]
6
+ pull_request:
7
+ branches: [main, "refactor/*", "feat/*"]
8
+
9
+ jobs:
10
+ cleanup-guard:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - name: Check audit and review artifacts
15
+ run: |
16
+ audit_count=$(find docs/audits -maxdepth 1 -name "*.md" -type f | wc -l)
17
+ review_count=$(find docs/reviews -maxdepth 1 -name "*.md" -type f | wc -l)
18
+
19
+ if [ "$audit_count" -eq 0 ]; then
20
+ echo "❌ FAIL: docs/audits/ contains no .md files (expected security audit)"
21
+ exit 1
22
+ fi
23
+
24
+ if [ "$review_count" -eq 0 ]; then
25
+ echo "❌ FAIL: docs/reviews/ contains no .md files (expected code review)"
26
+ exit 1
27
+ fi
28
+
29
+ echo "✓ Audit count: $audit_count"
30
+ echo "✓ Review count: $review_count"
@@ -0,0 +1,35 @@
1
+ name: dto-strict
2
+
3
+ # DTO-discipline lint (frozen+slots, facade-ban, R001-R008 rules).
4
+ # Lint step: whole package (native test-file exemption via R007/R008 rules).
5
+ # LOC-cap step: source dirs only (test density exempt from cap per review record).
6
+
7
+ on:
8
+ pull_request:
9
+ push:
10
+ branches: [main]
11
+
12
+ concurrency:
13
+ group: ${{ github.workflow }}-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ lint:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+ - uses: astral-sh/setup-uv@v4
25
+ with:
26
+ enable-cache: true
27
+ - name: Install Python 3.12
28
+ run: uv python install 3.12
29
+ - name: Run dto-strict
30
+ run: uvx --python 3.12 dto-strict@0.4.0 mixin_sensitivity/ --format github
31
+ - name: Check LOC cap
32
+ run: |
33
+ for dir in mixin_sensitivity/common mixin_sensitivity/config mixin_sensitivity/decorators/classes mixin_sensitivity/decorators/constants mixin_sensitivity/decorators/sensitive mixin_sensitivity/services/classify; do
34
+ uvx --python 3.12 dto-strict@0.4.0 loc-cap "$dir"
35
+ done
@@ -0,0 +1,73 @@
1
+ name: Attribution Gate
2
+
3
+ # Server-side enforcement: rejects pushes/PRs whose commit messages contain
4
+ # Claude/Anthropic attribution. Catches GitHub squash-merge propagation that
5
+ # bypasses local commit-msg, pre-push, and PreToolUse hooks.
6
+ #
7
+ # Per-repo copy of the canonical attribution gate.
8
+
9
+ on:
10
+ push:
11
+ pull_request:
12
+
13
+ permissions:
14
+ contents: read
15
+ pull-requests: read
16
+
17
+ jobs:
18
+ scan:
19
+ name: Scan commit messages for attribution
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout (full history)
23
+ uses: actions/checkout@v4
24
+ with:
25
+ fetch-depth: 0
26
+
27
+ - name: Determine commit range
28
+ id: range
29
+ env:
30
+ EVENT_NAME: ${{ github.event_name }}
31
+ PR_BASE: ${{ github.event.pull_request.base.sha }}
32
+ PR_HEAD: ${{ github.event.pull_request.head.sha }}
33
+ PUSH_BEFORE: ${{ github.event.before }}
34
+ PUSH_AFTER: ${{ github.event.after }}
35
+ run: |
36
+ set -euo pipefail
37
+ if [ "$EVENT_NAME" = "pull_request" ]; then
38
+ BASE="$PR_BASE"
39
+ HEAD="$PR_HEAD"
40
+ else
41
+ BASE="$PUSH_BEFORE"
42
+ HEAD="$PUSH_AFTER"
43
+ if [ "$BASE" = "0000000000000000000000000000000000000000" ] || [ -z "$BASE" ]; then
44
+ git fetch origin main:refs/remotes/origin/main 2>/dev/null || true
45
+ BASE=$(git merge-base origin/main "$HEAD" 2>/dev/null || git rev-list --max-parents=0 "$HEAD" | tail -1)
46
+ fi
47
+ fi
48
+ echo "base=$BASE" >> "$GITHUB_OUTPUT"
49
+ echo "head=$HEAD" >> "$GITHUB_OUTPUT"
50
+
51
+ - name: Scan for Claude/Anthropic attribution
52
+ env:
53
+ BASE: ${{ steps.range.outputs.base }}
54
+ HEAD: ${{ steps.range.outputs.head }}
55
+ PATTERN: 'co-authored-by.*(claude|anthropic)|generated with claude|@claude|noreply@anthropic|🤖'
56
+ run: |
57
+ set -uo pipefail
58
+ OFFENDING=""
59
+ for sha in $(git rev-list "$BASE..$HEAD"); do
60
+ MSG=$(git log -1 --pretty=format:"%B" "$sha")
61
+ if echo "$MSG" | grep -qiE "$PATTERN"; then
62
+ AUTHOR=$(git log -1 --pretty=format:"%an" "$sha")
63
+ SUBJECT=$(git log -1 --pretty=format:"%s" "$sha")
64
+ OFFENDING="${OFFENDING} ${sha:0:8} | author=${AUTHOR} | ${SUBJECT}"$'\n'
65
+ fi
66
+ done
67
+ if [ -n "$OFFENDING" ]; then
68
+ echo "::error::Claude/Anthropic attribution detected in commit messages"
69
+ echo "Offending commits in range $BASE..$HEAD:"
70
+ printf '%s' "$OFFENDING"
71
+ exit 1
72
+ fi
73
+ echo "OK: no attribution detected in $BASE..$HEAD"
@@ -0,0 +1,33 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ name: Build + publish
10
+ runs-on: ubuntu-latest
11
+ environment: pypi # second-layer GitHub deployment-environment gate
12
+ permissions:
13
+ id-token: write # OIDC trusted publisher
14
+ contents: read
15
+
16
+ steps:
17
+ - name: Checkout
18
+ uses: actions/checkout@v4
19
+
20
+ - name: Set up Python
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: '3.12'
24
+
25
+ - name: Install build deps
26
+ run: python -m pip install --upgrade build
27
+
28
+ - name: Build sdist + wheel
29
+ run: python -m build
30
+
31
+ - name: Publish via OIDC
32
+ uses: pypa/gh-action-pypi-publish@release/v1
33
+ # No password/token — OIDC trusted publisher does the auth
@@ -0,0 +1,29 @@
1
+ name: Ruff
2
+
3
+ # Ruff lint + format check. Pinned to 0.15.14 to match local version.
4
+ # Format drift was previously undetected; this gate prevents future drift.
5
+
6
+ on:
7
+ pull_request:
8
+ push:
9
+ branches: [main]
10
+
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ permissions:
16
+ contents: read
17
+
18
+ jobs:
19
+ ruff:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@v4
23
+ - uses: astral-sh/setup-uv@v4
24
+ with:
25
+ enable-cache: true
26
+ - name: Ruff lint
27
+ run: uvx ruff@0.15.14 check mixin_sensitivity/
28
+ - name: Ruff format check
29
+ run: uvx ruff@0.15.14 format --check mixin_sensitivity/
@@ -0,0 +1,131 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py,cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+
52
+ # Translations
53
+ *.mo
54
+ *.pot
55
+
56
+ # Django stuff:
57
+ *.log
58
+ local_settings.py
59
+ db.sqlite3
60
+ db.sqlite3-journal
61
+
62
+ # Flask stuff:
63
+ instance/
64
+ .webassets-cache
65
+
66
+ # Scrapy stuff:
67
+ .scrapy
68
+
69
+ # Sphinx documentation
70
+ docs/_build/
71
+
72
+ # PyBuilder
73
+ target/
74
+
75
+ # Jupyter Notebook
76
+ .ipynb_checkpoints
77
+
78
+ # IPython
79
+ profile_default/
80
+ ipython_config.py
81
+
82
+ # pyenv
83
+ .python-version
84
+
85
+ # pipenv
86
+ Pipfile.lock
87
+
88
+ # PEP 582
89
+ __pypackages__/
90
+
91
+ # Celery stuff
92
+ celerybeat-schedule
93
+ celerybeat.pid
94
+
95
+ # SageMath parsed files
96
+ *.sage.py
97
+
98
+ # Environments
99
+ .env
100
+ .venv
101
+ env/
102
+ venv/
103
+ ENV/
104
+ env.bak/
105
+ venv.bak/
106
+
107
+ # Spyder project settings
108
+ .spyderproject
109
+ .spyproject
110
+
111
+ # Rope project settings
112
+ .ropeproject
113
+
114
+ # mkdocs documentation
115
+ /site
116
+
117
+ # mypy
118
+ .mypy_cache/
119
+ .dmypy.json
120
+ dmypy.json
121
+
122
+ # Pyre type checker
123
+ .pyre/
124
+
125
+ # IDE
126
+ .vscode/
127
+ .idea/
128
+ *.swp
129
+ *.swo
130
+ *~
131
+ .DS_Store
@@ -0,0 +1,167 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ ### Changed
13
+
14
+ ### Deprecated
15
+
16
+ ### Removed
17
+
18
+ ### Fixed
19
+
20
+ ### Security
21
+
22
+ ## [0.4.0] - 2026-07-06
23
+
24
+ ### Changed
25
+
26
+ - Package renamed from `sensitivity-mixin` to `mixin-sensitivity` following the suite's category-first naming convention. Import root changes from `sensitivity_mixin` to `mixin_sensitivity`; all public symbols (`Sensitivity`, `classify`, `@sensitive`, `@classified`, `@phi_aware` backward compatibility) are unchanged.
27
+ - GitHub repository renamed to `jekhator/mixin-sensitivity`; the old URL redirects.
28
+ - The `sensitivity-mixin` distribution remains live on PyPI at 0.3.1; existing pins keep working. New releases publish as `mixin-sensitivity`.
29
+
30
+ ## [0.3.1] - 2026-06-10
31
+
32
+ ### Fixed
33
+
34
+ - Documentation code examples used uppercase sensitivity metadata values that raise ValueError at runtime; all examples corrected to the lowercase enum values and verified by execution.
35
+
36
+ ### Added
37
+
38
+ - README badges; contributing guide, security policy, and code of conduct; changelog project URL.
39
+
40
+ ## [0.3.0] - 2026-06-10
41
+
42
+ ### BREAKING CHANGES
43
+
44
+ Package renamed from `pii-aware-mixin` to `sensitivity-mixin`. Decorator and API scope broadened to support multiple sensitivity classifications.
45
+
46
+ ### Changed
47
+
48
+ - **Package name:** `pii-aware-mixin` → `sensitivity-mixin` (import: `sensitivity_mixin`)
49
+ - **Decorator:** `@phi_aware` → `@sensitive` (works with broadened taxonomy)
50
+ - **Metadata key:** `"phi"` → `"sensitivity"` with values: `"PHI"`, `"PII"`, `"PCI"`, `"SECRET"`
51
+ - **Default placeholder:** `<phi:redacted>` → `***` (simpler, more readable)
52
+ - **API:** `SensitiveDecorator` accepts optional `policies` tuple for per-class masking customization
53
+
54
+ ### Added
55
+
56
+ - `@sensitive` decorator with optional policy wiring via `SensitiveDecorator(policies=...)`
57
+ - `classify(instance)` → `SensitivityProfile`: introspect field-level sensitivity
58
+ - `Sensitivity` enum (PHI, PII, PCI, SECRET): taxonomy-driven classification
59
+ - `SensitivityProfile` value object with methods: `has()`, `fields_of()`, `sensitivity_of()`, `is_empty`
60
+ - `Compliance` enum (HIPAA, GDPR, PCI_DSS, NONE): governance regime labels for policies
61
+ - `ClassMakerAware` protocol: contract all sensitivity policies implement
62
+ - Four policy value objects: `PhiPolicyAware`, `PiiPolicyAware`, `PciPolicyAware`, `SecretPolicyAware`
63
+ - `SensitiveFieldSet` value object: field introspection + policy-driven masking
64
+
65
+ ### Removed
66
+
67
+ - `mask_for_logging()` helper: use `repr()` on decorated instances instead
68
+ - `to_dict()` helper: use `dataclasses.asdict()` directly
69
+ - Previous masking strategy removed; focus shifted to classification + repr-layer policies
70
+
71
+ ### Documentation
72
+
73
+ - `docs/apps/decorators/sensitive.md`: @sensitive decorator and SensitiveFieldSet
74
+ - `docs/apps/decorators/compliance.md`: Compliance enum and ClassMakerAware protocol
75
+ - `docs/apps/decorators/policies.md`: Four policy value objects with usage examples
76
+
77
+ ### Why
78
+
79
+ Broadened scope from PHI-only masking to a taxonomy-driven sensitivity framework supporting healthcare (PHI), personal data (PII), payment data (PCI), and secrets. Per-class policy value objects enable compliance-aware customization (HIPAA, GDPR, PCI-DSS) while keeping the decorator simple by default.
80
+
81
+ ## [0.2.0] - 2026-05-21
82
+
83
+ ### BREAKING CHANGES
84
+
85
+ API redesigned from mixin-inheritance to decorator-based. Consumers must migrate.
86
+
87
+ ### Removed
88
+
89
+ - `PiiAwareMixin` class: replaced by `@phi_aware` decorator
90
+ - `ReprMixin` class: functionality folded into `@phi_aware` decorator
91
+ - `ToDictMixin` class: replaced by module-level `to_dict()` helper
92
+ - Requirement for `repr=False` in consumer dataclasses (decorator works on plain `@dataclass(frozen=True, slots=True)`)
93
+
94
+ ### Added
95
+
96
+ - `@phi_aware` decorator: injects PHI-masking `__repr__` on frozen dataclasses
97
+ - `mask_for_logging(instance)` module-level helper: returns dict with PHI fields masked
98
+ - `to_dict(instance)` module-level helper: alias for `dataclasses.asdict()` for consistency
99
+ - Field metadata key renamed `"sensitive"` → `"phi"` (healthcare-aligned)
100
+ - 26 comprehensive tests covering decorator, helpers, edge cases, integration
101
+
102
+ ### Why
103
+
104
+ Compatible with the canonical "no mixin inheritance on data DTOs" pattern established in downstream QHCG project (2026-05-09).
105
+
106
+ v0.1.0 mixin-inheritance API (`class User(PiiAwareMixin, ReprMixin, ToDictMixin)`) caused R003 false-positives in dto-strict v0.2.x when consumers tried to adopt the "no mixin inheritance" rule. The new decorator API allows clean adoption:
107
+ - No inheritance, no MRO issues
108
+ - Works on plain frozen dataclasses (no `repr=False` boilerplate)
109
+ - Explicit module-level helpers (not instance methods)
110
+ - Compatible with static analysis tools
111
+
112
+ ### Migration Guide
113
+
114
+ **v0.1.0 (old, deprecated):**
115
+ ```python
116
+ from pii_aware_mixin import PiiAwareMixin, ReprMixin, ToDictMixin
117
+
118
+ @dataclass(frozen=True, slots=True, repr=False)
119
+ class User(PiiAwareMixin, ReprMixin, ToDictMixin):
120
+ id: int
121
+ api_token: str = field(metadata={"sensitive": True})
122
+
123
+ user = User(id=1, api_token="sk-123")
124
+ user.mask_for_logging() # instance method
125
+ repr(user) # mixin's __repr__
126
+ user.to_dict() # instance method
127
+ ```
128
+
129
+ **v0.2.0 (new):**
130
+ ```python
131
+ from pii_aware_mixin import phi_aware, mask_for_logging, to_dict
132
+
133
+ @phi_aware
134
+ @dataclass(frozen=True, slots=True)
135
+ class User:
136
+ id: int
137
+ api_token: str = field(metadata={"phi": True})
138
+
139
+ user = User(id=1, api_token="sk-123")
140
+ mask_for_logging(user) # module-level helper
141
+ repr(user) # decorator's __repr__
142
+ to_dict(user) # module-level helper
143
+ ```
144
+
145
+ **Changes per file:**
146
+ 1. Remove mixin inheritance from class definition
147
+ 2. Add `@phi_aware` decorator above `@dataclass`
148
+ 3. Remove `repr=False` from `@dataclass` (no longer needed)
149
+ 4. Change `metadata={"sensitive": True}` → `metadata={"phi": True}`
150
+ 5. Replace `instance.mask_for_logging()` → `mask_for_logging(instance)`
151
+ 6. Replace `instance.to_dict()` → `to_dict(instance)`
152
+
153
+ ## [0.1.0] - 2026-05-18
154
+
155
+ ### Added
156
+
157
+ - `PiiAwareMixin`: metadata-driven masking via `mask_for_logging()` instance method
158
+ - `ReprMixin`: structured `__repr__()` with automatic field masking
159
+ - `ToDictMixin`: JSON serialization via `to_dict()` and `from_dict()` instance methods
160
+ - Field metadata key `"sensitive"` to mark fields for masking
161
+ - Comprehensive tests for all three mixins and composition patterns
162
+ - README with quick start, API reference, logging integration examples
163
+
164
+ ### Design
165
+
166
+ Three composable mixin classes for frozen dataclasses, using Python's MRO for clean composition.
167
+ All work independently or together. Full-mask strategy: sensitive fields become `<MASKED>` in logs and `<masked: sensitive>` in reprs.
@@ -0,0 +1,31 @@
1
+ # Code of Conduct
2
+
3
+ ## My Commitment
4
+
5
+ I maintain this project solo, and I am committed to keeping it a welcoming space for everyone who participates in issues, pull requests, and discussions. I pledge to treat every participant with respect and dignity, regardless of age, body size, caste, disability, ethnicity, sex characteristics, gender, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, sexual identity and orientation, or any other dimension of diversity.
6
+
7
+ ## Standards
8
+
9
+ Behavior that keeps this a positive space:
10
+
11
+ - Welcoming and inclusive language
12
+ - Respect for differing viewpoints and experiences
13
+ - Gracefully accepting constructive criticism
14
+ - Focusing on what is best for the project
15
+ - Empathy toward other participants
16
+
17
+ Behavior I will not accept:
18
+
19
+ - Harassment or insulting or derogatory comments
20
+ - Personal attacks
21
+ - Public or private harassment
22
+ - Publishing someone's private information without consent
23
+ - Conduct that could reasonably be considered inappropriate
24
+
25
+ ## Enforcement
26
+
27
+ Report unacceptable behavior to me through GitHub, either by opening an issue or, where discretion is warranted, through private vulnerability reporting. I review and act on every report promptly and fairly, and I respect the privacy and security of anyone who reports.
28
+
29
+ ## Attribution
30
+
31
+ This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
@@ -0,0 +1,65 @@
1
+ # Contributing
2
+
3
+ Thank you for your interest in contributing! I welcome pull requests, bug reports, and feature suggestions.
4
+
5
+ ## Development Setup
6
+
7
+ ### Prerequisites
8
+
9
+ - Python 3.11+
10
+ - [uv](https://docs.astral.sh/uv/) for dependency management
11
+
12
+ ### Getting Started
13
+
14
+ 1. Clone the repository:
15
+ ```bash
16
+ git clone https://github.com/jekhator/mixin-sensitivity.git
17
+ cd mixin-sensitivity
18
+ ```
19
+
20
+ 2. Sync dependencies with uv:
21
+ ```bash
22
+ uv sync --all-extras
23
+ ```
24
+
25
+ 3. Run tests to verify setup:
26
+ ```bash
27
+ uv run pytest
28
+ ```
29
+
30
+ ## Code Quality
31
+
32
+ ### Testing
33
+
34
+ Run the test suite:
35
+ ```bash
36
+ uv run pytest
37
+ ```
38
+
39
+ For coverage:
40
+ ```bash
41
+ uv run pytest --cov
42
+ ```
43
+
44
+ ### Linting and Formatting
45
+
46
+ Format code with ruff:
47
+ ```bash
48
+ uv run ruff format
49
+ ```
50
+
51
+ Check for linting issues:
52
+ ```bash
53
+ uv run ruff check
54
+ ```
55
+
56
+ ## Pull Requests
57
+
58
+ - Open PRs against the `main` branch.
59
+ - Include a clear description of the change and the problem it solves.
60
+ - Ensure all tests pass and code is formatted before requesting review.
61
+ - The CI pipeline will verify linting, formatting, and test coverage.
62
+
63
+ ## License
64
+
65
+ By contributing, you agree that your contributions will be licensed under the Apache 2.0 License.