fraiseql-confiture 0.3.4__cp311-cp311-win_amd64.whl
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.
- confiture/__init__.py +48 -0
- confiture/_core.cp311-win_amd64.pyd +0 -0
- confiture/cli/__init__.py +0 -0
- confiture/cli/dry_run.py +116 -0
- confiture/cli/lint_formatter.py +193 -0
- confiture/cli/main.py +1656 -0
- confiture/config/__init__.py +0 -0
- confiture/config/environment.py +263 -0
- confiture/core/__init__.py +51 -0
- confiture/core/anonymization/__init__.py +0 -0
- confiture/core/anonymization/audit.py +485 -0
- confiture/core/anonymization/benchmarking.py +372 -0
- confiture/core/anonymization/breach_notification.py +652 -0
- confiture/core/anonymization/compliance.py +617 -0
- confiture/core/anonymization/composer.py +298 -0
- confiture/core/anonymization/data_subject_rights.py +669 -0
- confiture/core/anonymization/factory.py +319 -0
- confiture/core/anonymization/governance.py +737 -0
- confiture/core/anonymization/performance.py +1092 -0
- confiture/core/anonymization/profile.py +284 -0
- confiture/core/anonymization/registry.py +195 -0
- confiture/core/anonymization/security/kms_manager.py +547 -0
- confiture/core/anonymization/security/lineage.py +888 -0
- confiture/core/anonymization/security/token_store.py +686 -0
- confiture/core/anonymization/strategies/__init__.py +41 -0
- confiture/core/anonymization/strategies/address.py +359 -0
- confiture/core/anonymization/strategies/credit_card.py +374 -0
- confiture/core/anonymization/strategies/custom.py +161 -0
- confiture/core/anonymization/strategies/date.py +218 -0
- confiture/core/anonymization/strategies/differential_privacy.py +398 -0
- confiture/core/anonymization/strategies/email.py +141 -0
- confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
- confiture/core/anonymization/strategies/hash.py +150 -0
- confiture/core/anonymization/strategies/ip_address.py +235 -0
- confiture/core/anonymization/strategies/masking_retention.py +252 -0
- confiture/core/anonymization/strategies/name.py +298 -0
- confiture/core/anonymization/strategies/phone.py +119 -0
- confiture/core/anonymization/strategies/preserve.py +85 -0
- confiture/core/anonymization/strategies/redact.py +101 -0
- confiture/core/anonymization/strategies/salted_hashing.py +322 -0
- confiture/core/anonymization/strategies/text_redaction.py +183 -0
- confiture/core/anonymization/strategies/tokenization.py +334 -0
- confiture/core/anonymization/strategy.py +241 -0
- confiture/core/anonymization/syncer_audit.py +357 -0
- confiture/core/blue_green.py +683 -0
- confiture/core/builder.py +500 -0
- confiture/core/checksum.py +358 -0
- confiture/core/connection.py +132 -0
- confiture/core/differ.py +522 -0
- confiture/core/drift.py +564 -0
- confiture/core/dry_run.py +182 -0
- confiture/core/health.py +313 -0
- confiture/core/hooks/__init__.py +87 -0
- confiture/core/hooks/base.py +232 -0
- confiture/core/hooks/context.py +146 -0
- confiture/core/hooks/execution_strategies.py +57 -0
- confiture/core/hooks/observability.py +220 -0
- confiture/core/hooks/phases.py +53 -0
- confiture/core/hooks/registry.py +295 -0
- confiture/core/large_tables.py +775 -0
- confiture/core/linting/__init__.py +70 -0
- confiture/core/linting/composer.py +192 -0
- confiture/core/linting/libraries/__init__.py +17 -0
- confiture/core/linting/libraries/gdpr.py +168 -0
- confiture/core/linting/libraries/general.py +184 -0
- confiture/core/linting/libraries/hipaa.py +144 -0
- confiture/core/linting/libraries/pci_dss.py +104 -0
- confiture/core/linting/libraries/sox.py +120 -0
- confiture/core/linting/schema_linter.py +491 -0
- confiture/core/linting/versioning.py +151 -0
- confiture/core/locking.py +389 -0
- confiture/core/migration_generator.py +298 -0
- confiture/core/migrator.py +793 -0
- confiture/core/observability/__init__.py +44 -0
- confiture/core/observability/audit.py +323 -0
- confiture/core/observability/logging.py +187 -0
- confiture/core/observability/metrics.py +174 -0
- confiture/core/observability/tracing.py +192 -0
- confiture/core/pg_version.py +418 -0
- confiture/core/pool.py +406 -0
- confiture/core/risk/__init__.py +39 -0
- confiture/core/risk/predictor.py +188 -0
- confiture/core/risk/scoring.py +248 -0
- confiture/core/rollback_generator.py +388 -0
- confiture/core/schema_analyzer.py +769 -0
- confiture/core/schema_to_schema.py +590 -0
- confiture/core/security/__init__.py +32 -0
- confiture/core/security/logging.py +201 -0
- confiture/core/security/validation.py +416 -0
- confiture/core/signals.py +371 -0
- confiture/core/syncer.py +540 -0
- confiture/exceptions.py +192 -0
- confiture/integrations/__init__.py +0 -0
- confiture/models/__init__.py +0 -0
- confiture/models/lint.py +193 -0
- confiture/models/migration.py +180 -0
- confiture/models/schema.py +203 -0
- confiture/scenarios/__init__.py +36 -0
- confiture/scenarios/compliance.py +586 -0
- confiture/scenarios/ecommerce.py +199 -0
- confiture/scenarios/financial.py +253 -0
- confiture/scenarios/healthcare.py +315 -0
- confiture/scenarios/multi_tenant.py +340 -0
- confiture/scenarios/saas.py +295 -0
- confiture/testing/FRAMEWORK_API.md +722 -0
- confiture/testing/__init__.py +38 -0
- confiture/testing/fixtures/__init__.py +11 -0
- confiture/testing/fixtures/data_validator.py +229 -0
- confiture/testing/fixtures/migration_runner.py +167 -0
- confiture/testing/fixtures/schema_snapshotter.py +352 -0
- confiture/testing/frameworks/__init__.py +10 -0
- confiture/testing/frameworks/mutation.py +587 -0
- confiture/testing/frameworks/performance.py +479 -0
- confiture/testing/utils/__init__.py +0 -0
- fraiseql_confiture-0.3.4.dist-info/METADATA +438 -0
- fraiseql_confiture-0.3.4.dist-info/RECORD +119 -0
- fraiseql_confiture-0.3.4.dist-info/WHEEL +4 -0
- fraiseql_confiture-0.3.4.dist-info/entry_points.txt +2 -0
- fraiseql_confiture-0.3.4.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Rule Library System.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- Rule versioning with deprecation paths
|
|
5
|
+
- Conflict detection and resolution
|
|
6
|
+
- Compliance libraries (HIPAA, SOX, GDPR, PCI-DSS, General)
|
|
7
|
+
- Transparent audit trails
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from .composer import (
|
|
13
|
+
ComposedRuleSet,
|
|
14
|
+
ConflictResolution,
|
|
15
|
+
ConflictType,
|
|
16
|
+
RuleConflict,
|
|
17
|
+
RuleConflictError,
|
|
18
|
+
RuleLibrary,
|
|
19
|
+
RuleLibraryComposer,
|
|
20
|
+
)
|
|
21
|
+
from .libraries import (
|
|
22
|
+
GDPRLibrary,
|
|
23
|
+
GeneralLibrary,
|
|
24
|
+
HIPAALibrary,
|
|
25
|
+
PCI_DSSLibrary,
|
|
26
|
+
SOXLibrary,
|
|
27
|
+
)
|
|
28
|
+
from .schema_linter import (
|
|
29
|
+
LintConfig,
|
|
30
|
+
LintReport,
|
|
31
|
+
LintViolation,
|
|
32
|
+
RuleSeverity,
|
|
33
|
+
SchemaLinter,
|
|
34
|
+
)
|
|
35
|
+
from .versioning import (
|
|
36
|
+
LintSeverity,
|
|
37
|
+
Rule,
|
|
38
|
+
RuleRemovedError,
|
|
39
|
+
RuleVersion,
|
|
40
|
+
RuleVersionManager,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
# Versioning
|
|
45
|
+
"RuleVersion",
|
|
46
|
+
"Rule",
|
|
47
|
+
"LintSeverity",
|
|
48
|
+
"RuleVersionManager",
|
|
49
|
+
"RuleRemovedError",
|
|
50
|
+
# Composition
|
|
51
|
+
"RuleLibrary",
|
|
52
|
+
"RuleLibraryComposer",
|
|
53
|
+
"ComposedRuleSet",
|
|
54
|
+
"RuleConflict",
|
|
55
|
+
"RuleConflictError",
|
|
56
|
+
"ConflictResolution",
|
|
57
|
+
"ConflictType",
|
|
58
|
+
# Libraries
|
|
59
|
+
"GeneralLibrary",
|
|
60
|
+
"HIPAALibrary",
|
|
61
|
+
"SOXLibrary",
|
|
62
|
+
"GDPRLibrary",
|
|
63
|
+
"PCI_DSSLibrary",
|
|
64
|
+
# Schema Linter
|
|
65
|
+
"SchemaLinter",
|
|
66
|
+
"LintConfig",
|
|
67
|
+
"LintReport",
|
|
68
|
+
"LintViolation",
|
|
69
|
+
"RuleSeverity",
|
|
70
|
+
]
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Rule library composition with explicit conflict handling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from enum import Enum
|
|
8
|
+
|
|
9
|
+
from .versioning import LintSeverity, Rule, RuleVersion
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConflictResolution(Enum):
|
|
15
|
+
"""How to handle rule conflicts."""
|
|
16
|
+
|
|
17
|
+
ERROR = "error" # Raise exception
|
|
18
|
+
WARN = "warn" # Log warning, continue
|
|
19
|
+
PREFER_FIRST = "prefer_first" # Use first added library's rule
|
|
20
|
+
PREFER_LAST = "prefer_last" # Use last added library's rule
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ConflictType(Enum):
|
|
24
|
+
"""Type of conflict between rules."""
|
|
25
|
+
|
|
26
|
+
DUPLICATE = "duplicate" # Same rule ID
|
|
27
|
+
INCOMPATIBLE = "incompatible" # Conflicting requirements
|
|
28
|
+
OVERLAPPING = "overlapping" # Similar functionality
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class RuleConflict:
|
|
33
|
+
"""Represents a conflict between rules."""
|
|
34
|
+
|
|
35
|
+
rule_id: str
|
|
36
|
+
library_a: str
|
|
37
|
+
library_b: str
|
|
38
|
+
conflict_type: ConflictType
|
|
39
|
+
severity: LintSeverity
|
|
40
|
+
description: str
|
|
41
|
+
suggested_resolution: str
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RuleConflictError(Exception):
|
|
45
|
+
"""Exception raised when rule conflicts are detected."""
|
|
46
|
+
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class RuleLibrary:
|
|
51
|
+
"""Collection of related rules."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
name: str,
|
|
56
|
+
version: RuleVersion,
|
|
57
|
+
rules: list[Rule],
|
|
58
|
+
tags: list[str] | None = None,
|
|
59
|
+
):
|
|
60
|
+
self.name = name
|
|
61
|
+
self.version = version
|
|
62
|
+
self.rules = {r.rule_id: r for r in rules}
|
|
63
|
+
self.tags = tags or []
|
|
64
|
+
|
|
65
|
+
def get_rules(self) -> list[Rule]:
|
|
66
|
+
"""Get all rules in this library."""
|
|
67
|
+
return list(self.rules.values())
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RuleLibraryComposer:
|
|
71
|
+
"""Compose multiple libraries with explicit conflict handling."""
|
|
72
|
+
|
|
73
|
+
def __init__(self):
|
|
74
|
+
self.libraries: list[RuleLibrary] = []
|
|
75
|
+
self.overrides: dict[str, Rule] = {}
|
|
76
|
+
self.disabled_rules: set[str] = set()
|
|
77
|
+
self.conflict_log: list[RuleConflict] = []
|
|
78
|
+
|
|
79
|
+
def add_library(
|
|
80
|
+
self,
|
|
81
|
+
library: RuleLibrary,
|
|
82
|
+
on_conflict: ConflictResolution = ConflictResolution.ERROR,
|
|
83
|
+
) -> RuleLibraryComposer:
|
|
84
|
+
"""Add library with conflict handling."""
|
|
85
|
+
conflicts = self._detect_conflicts(library)
|
|
86
|
+
|
|
87
|
+
if conflicts:
|
|
88
|
+
self.conflict_log.extend(conflicts)
|
|
89
|
+
|
|
90
|
+
if on_conflict == ConflictResolution.ERROR:
|
|
91
|
+
raise RuleConflictError(f"Found {len(conflicts)} rule conflicts in {library.name}")
|
|
92
|
+
elif on_conflict == ConflictResolution.WARN:
|
|
93
|
+
for conflict in conflicts:
|
|
94
|
+
logger.warning(
|
|
95
|
+
f"Rule conflict: {conflict.rule_id} in {conflict.library_a} "
|
|
96
|
+
f"vs {conflict.library_b}. {conflict.suggested_resolution}"
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
self.libraries.append(library)
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
def override_rule(self, rule_id: str, new_rule: Rule) -> RuleLibraryComposer:
|
|
103
|
+
"""Override a specific rule."""
|
|
104
|
+
self.overrides[rule_id] = new_rule
|
|
105
|
+
logger.info(f"Overridden rule {rule_id}")
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def disable_rule(self, rule_id: str) -> RuleLibraryComposer:
|
|
109
|
+
"""Disable a rule from any library."""
|
|
110
|
+
self.disabled_rules.add(rule_id)
|
|
111
|
+
logger.info(f"Disabled rule {rule_id}")
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def build(self) -> ComposedRuleSet:
|
|
115
|
+
"""Build final rule set with conflicts resolved."""
|
|
116
|
+
all_rules = {}
|
|
117
|
+
|
|
118
|
+
for library in self.libraries:
|
|
119
|
+
for rule_id, rule in library.rules.items():
|
|
120
|
+
if rule_id in self.disabled_rules:
|
|
121
|
+
continue
|
|
122
|
+
all_rules[rule_id] = rule
|
|
123
|
+
|
|
124
|
+
# Apply overrides
|
|
125
|
+
all_rules.update(self.overrides)
|
|
126
|
+
|
|
127
|
+
# Create audit trail
|
|
128
|
+
return ComposedRuleSet(
|
|
129
|
+
rules=list(all_rules.values()),
|
|
130
|
+
libraries=[lib.name for lib in self.libraries],
|
|
131
|
+
disabled_rules=list(self.disabled_rules),
|
|
132
|
+
overridden_rules=list(self.overrides.keys()),
|
|
133
|
+
conflicts=self.conflict_log,
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def _detect_conflicts(self, new_library: RuleLibrary) -> list[RuleConflict]:
|
|
137
|
+
"""Detect conflicts with existing libraries."""
|
|
138
|
+
conflicts = []
|
|
139
|
+
new_rule_ids = set(new_library.rules.keys())
|
|
140
|
+
|
|
141
|
+
for existing_library in self.libraries:
|
|
142
|
+
for existing_rule_id in existing_library.rules:
|
|
143
|
+
if existing_rule_id in new_rule_ids:
|
|
144
|
+
conflicts.append(
|
|
145
|
+
RuleConflict(
|
|
146
|
+
rule_id=existing_rule_id,
|
|
147
|
+
library_a=existing_library.name,
|
|
148
|
+
library_b=new_library.name,
|
|
149
|
+
conflict_type=ConflictType.DUPLICATE,
|
|
150
|
+
severity=LintSeverity.WARNING,
|
|
151
|
+
description=f"Rule {existing_rule_id} exists in both libraries",
|
|
152
|
+
suggested_resolution=(
|
|
153
|
+
"Use override_rule() to select preferred version"
|
|
154
|
+
),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return conflicts
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@dataclass
|
|
162
|
+
class ComposedRuleSet:
|
|
163
|
+
"""Result of composing multiple rule libraries."""
|
|
164
|
+
|
|
165
|
+
rules: list[Rule]
|
|
166
|
+
libraries: list[str] # Which libraries were composed
|
|
167
|
+
disabled_rules: list[str] = field(default_factory=list) # Which rules were disabled
|
|
168
|
+
overridden_rules: list[str] = field(default_factory=list) # Which rules were overridden
|
|
169
|
+
conflicts: list[RuleConflict] = field(default_factory=list)
|
|
170
|
+
|
|
171
|
+
def get_audit_trail(self) -> str:
|
|
172
|
+
"""Get human-readable audit trail."""
|
|
173
|
+
lines = [
|
|
174
|
+
f"Libraries: {', '.join(self.libraries)}",
|
|
175
|
+
f"Total rules: {len(self.rules)}",
|
|
176
|
+
f"Disabled: {len(self.disabled_rules)} ({', '.join(self.disabled_rules)})",
|
|
177
|
+
f"Overridden: {len(self.overridden_rules)}",
|
|
178
|
+
f"Conflicts: {len(self.conflicts)}",
|
|
179
|
+
]
|
|
180
|
+
return "\n".join(lines)
|
|
181
|
+
|
|
182
|
+
def get_rules_by_severity(self, severity: LintSeverity) -> list[Rule]:
|
|
183
|
+
"""Get all rules of a specific severity."""
|
|
184
|
+
return [r for r in self.rules if r.severity == severity]
|
|
185
|
+
|
|
186
|
+
def get_enabled_rules(self) -> list[Rule]:
|
|
187
|
+
"""Get all enabled rules."""
|
|
188
|
+
return [r for r in self.rules if r.enabled_by_default]
|
|
189
|
+
|
|
190
|
+
def get_disabled_rules_info(self) -> list[str]:
|
|
191
|
+
"""Get info about disabled rules."""
|
|
192
|
+
return self.disabled_rules
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Compliance and best-practices rule libraries."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from .gdpr import GDPRLibrary
|
|
6
|
+
from .general import GeneralLibrary
|
|
7
|
+
from .hipaa import HIPAALibrary
|
|
8
|
+
from .pci_dss import PCI_DSSLibrary
|
|
9
|
+
from .sox import SOXLibrary
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"GeneralLibrary",
|
|
13
|
+
"HIPAALibrary",
|
|
14
|
+
"SOXLibrary",
|
|
15
|
+
"GDPRLibrary",
|
|
16
|
+
"PCI_DSSLibrary",
|
|
17
|
+
]
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""GDPR compliance rule library."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..composer import RuleLibrary
|
|
6
|
+
from ..versioning import LintSeverity, Rule, RuleVersion
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GDPRLibrary(RuleLibrary):
|
|
10
|
+
"""GDPR compliance rule library (18 rules)."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
rules = [
|
|
14
|
+
Rule(
|
|
15
|
+
rule_id="gdpr_001",
|
|
16
|
+
name="personal_data_encryption",
|
|
17
|
+
description="Personal data must be encrypted at rest and in transit",
|
|
18
|
+
version=RuleVersion(1, 0, 0),
|
|
19
|
+
severity=LintSeverity.CRITICAL,
|
|
20
|
+
enabled_by_default=True,
|
|
21
|
+
),
|
|
22
|
+
Rule(
|
|
23
|
+
rule_id="gdpr_002",
|
|
24
|
+
name="consent_tracking",
|
|
25
|
+
description="Track consent for each piece of personal data",
|
|
26
|
+
version=RuleVersion(1, 0, 0),
|
|
27
|
+
severity=LintSeverity.ERROR,
|
|
28
|
+
enabled_by_default=True,
|
|
29
|
+
),
|
|
30
|
+
Rule(
|
|
31
|
+
rule_id="gdpr_003",
|
|
32
|
+
name="data_minimization",
|
|
33
|
+
description="Only collect necessary personal data",
|
|
34
|
+
version=RuleVersion(1, 0, 0),
|
|
35
|
+
severity=LintSeverity.WARNING,
|
|
36
|
+
enabled_by_default=True,
|
|
37
|
+
),
|
|
38
|
+
Rule(
|
|
39
|
+
rule_id="gdpr_004",
|
|
40
|
+
name="purpose_limitation",
|
|
41
|
+
description="Data use must be limited to stated purposes",
|
|
42
|
+
version=RuleVersion(1, 0, 0),
|
|
43
|
+
severity=LintSeverity.ERROR,
|
|
44
|
+
enabled_by_default=True,
|
|
45
|
+
),
|
|
46
|
+
Rule(
|
|
47
|
+
rule_id="gdpr_005",
|
|
48
|
+
name="right_to_deletion",
|
|
49
|
+
description="Implement right to be forgotten capability",
|
|
50
|
+
version=RuleVersion(1, 0, 0),
|
|
51
|
+
severity=LintSeverity.CRITICAL,
|
|
52
|
+
enabled_by_default=True,
|
|
53
|
+
),
|
|
54
|
+
Rule(
|
|
55
|
+
rule_id="gdpr_006",
|
|
56
|
+
name="data_portability",
|
|
57
|
+
description="Enable data portability (export in machine-readable format)",
|
|
58
|
+
version=RuleVersion(1, 0, 0),
|
|
59
|
+
severity=LintSeverity.ERROR,
|
|
60
|
+
enabled_by_default=True,
|
|
61
|
+
),
|
|
62
|
+
Rule(
|
|
63
|
+
rule_id="gdpr_007",
|
|
64
|
+
name="access_request_tracking",
|
|
65
|
+
description="Track and respond to data access requests",
|
|
66
|
+
version=RuleVersion(1, 0, 0),
|
|
67
|
+
severity=LintSeverity.ERROR,
|
|
68
|
+
enabled_by_default=True,
|
|
69
|
+
),
|
|
70
|
+
Rule(
|
|
71
|
+
rule_id="gdpr_008",
|
|
72
|
+
name="breach_notification",
|
|
73
|
+
description="Implement breach notification within 72 hours",
|
|
74
|
+
version=RuleVersion(1, 0, 0),
|
|
75
|
+
severity=LintSeverity.CRITICAL,
|
|
76
|
+
enabled_by_default=True,
|
|
77
|
+
),
|
|
78
|
+
Rule(
|
|
79
|
+
rule_id="gdpr_009",
|
|
80
|
+
name="dpa_impact_assessment",
|
|
81
|
+
description="Conduct data protection impact assessment",
|
|
82
|
+
version=RuleVersion(1, 0, 0),
|
|
83
|
+
severity=LintSeverity.ERROR,
|
|
84
|
+
enabled_by_default=True,
|
|
85
|
+
),
|
|
86
|
+
Rule(
|
|
87
|
+
rule_id="gdpr_010",
|
|
88
|
+
name="dpo_designation",
|
|
89
|
+
description="Data Protection Officer must be designated",
|
|
90
|
+
version=RuleVersion(1, 0, 0),
|
|
91
|
+
severity=LintSeverity.WARNING,
|
|
92
|
+
enabled_by_default=True,
|
|
93
|
+
),
|
|
94
|
+
Rule(
|
|
95
|
+
rule_id="gdpr_011",
|
|
96
|
+
name="data_processing_agreement",
|
|
97
|
+
description="Document data processing agreements",
|
|
98
|
+
version=RuleVersion(1, 0, 0),
|
|
99
|
+
severity=LintSeverity.ERROR,
|
|
100
|
+
enabled_by_default=True,
|
|
101
|
+
),
|
|
102
|
+
Rule(
|
|
103
|
+
rule_id="gdpr_012",
|
|
104
|
+
name="third_party_transfer",
|
|
105
|
+
description="Document third-party data transfers",
|
|
106
|
+
version=RuleVersion(1, 0, 0),
|
|
107
|
+
severity=LintSeverity.ERROR,
|
|
108
|
+
enabled_by_default=True,
|
|
109
|
+
),
|
|
110
|
+
Rule(
|
|
111
|
+
rule_id="gdpr_013",
|
|
112
|
+
name="international_transfer",
|
|
113
|
+
description="Manage international data transfers properly",
|
|
114
|
+
version=RuleVersion(1, 0, 0),
|
|
115
|
+
severity=LintSeverity.CRITICAL,
|
|
116
|
+
enabled_by_default=True,
|
|
117
|
+
),
|
|
118
|
+
Rule(
|
|
119
|
+
rule_id="gdpr_014",
|
|
120
|
+
name="consent_withdrawal",
|
|
121
|
+
description="Allow easy consent withdrawal",
|
|
122
|
+
version=RuleVersion(1, 0, 0),
|
|
123
|
+
severity=LintSeverity.ERROR,
|
|
124
|
+
enabled_by_default=True,
|
|
125
|
+
),
|
|
126
|
+
Rule(
|
|
127
|
+
rule_id="gdpr_015",
|
|
128
|
+
name="legitimate_interest",
|
|
129
|
+
description="Document legitimate interests assessment",
|
|
130
|
+
version=RuleVersion(1, 0, 0),
|
|
131
|
+
severity=LintSeverity.WARNING,
|
|
132
|
+
enabled_by_default=True,
|
|
133
|
+
),
|
|
134
|
+
Rule(
|
|
135
|
+
rule_id="gdpr_016",
|
|
136
|
+
name="child_protection",
|
|
137
|
+
description="Implement special protection for children's data",
|
|
138
|
+
version=RuleVersion(1, 0, 0),
|
|
139
|
+
severity=LintSeverity.ERROR,
|
|
140
|
+
enabled_by_default=True,
|
|
141
|
+
),
|
|
142
|
+
Rule(
|
|
143
|
+
rule_id="gdpr_017",
|
|
144
|
+
name="audit_logging",
|
|
145
|
+
description="Maintain audit logs of all data access",
|
|
146
|
+
version=RuleVersion(1, 0, 0),
|
|
147
|
+
severity=LintSeverity.CRITICAL,
|
|
148
|
+
enabled_by_default=True,
|
|
149
|
+
),
|
|
150
|
+
Rule(
|
|
151
|
+
rule_id="gdpr_018",
|
|
152
|
+
name="privacy_by_design",
|
|
153
|
+
description="Privacy must be built into system design",
|
|
154
|
+
version=RuleVersion(1, 0, 0),
|
|
155
|
+
severity=LintSeverity.ERROR,
|
|
156
|
+
enabled_by_default=True,
|
|
157
|
+
),
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
# Verify rule count matches docstring
|
|
161
|
+
assert len(rules) == 18, f"Expected 18 rules in GDPRLibrary, got {len(rules)}"
|
|
162
|
+
|
|
163
|
+
super().__init__(
|
|
164
|
+
name="GDPR",
|
|
165
|
+
version=RuleVersion(major=1, minor=0, patch=0),
|
|
166
|
+
rules=rules,
|
|
167
|
+
tags=["privacy", "compliance", "gdpr", "eu"],
|
|
168
|
+
)
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""General best practices rule library."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ..composer import RuleLibrary
|
|
6
|
+
from ..versioning import LintSeverity, Rule, RuleVersion
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class GeneralLibrary(RuleLibrary):
|
|
10
|
+
"""General best practices rule library (20 rules)."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
rules = [
|
|
14
|
+
Rule(
|
|
15
|
+
rule_id="general_001",
|
|
16
|
+
name="no_implicit_casts",
|
|
17
|
+
description="Avoid implicit type casts in migrations",
|
|
18
|
+
version=RuleVersion(1, 0, 0),
|
|
19
|
+
severity=LintSeverity.WARNING,
|
|
20
|
+
enabled_by_default=True,
|
|
21
|
+
),
|
|
22
|
+
Rule(
|
|
23
|
+
rule_id="general_002",
|
|
24
|
+
name="explicit_column_types",
|
|
25
|
+
description="All columns must have explicit types",
|
|
26
|
+
version=RuleVersion(1, 0, 0),
|
|
27
|
+
severity=LintSeverity.ERROR,
|
|
28
|
+
enabled_by_default=True,
|
|
29
|
+
),
|
|
30
|
+
Rule(
|
|
31
|
+
rule_id="general_003",
|
|
32
|
+
name="no_null_without_default",
|
|
33
|
+
description="NULLABLE columns should have explicit default",
|
|
34
|
+
version=RuleVersion(1, 0, 0),
|
|
35
|
+
severity=LintSeverity.WARNING,
|
|
36
|
+
enabled_by_default=False,
|
|
37
|
+
),
|
|
38
|
+
Rule(
|
|
39
|
+
rule_id="general_004",
|
|
40
|
+
name="index_naming_convention",
|
|
41
|
+
description="Indexes must follow naming convention",
|
|
42
|
+
version=RuleVersion(1, 0, 0),
|
|
43
|
+
severity=LintSeverity.WARNING,
|
|
44
|
+
enabled_by_default=True,
|
|
45
|
+
),
|
|
46
|
+
Rule(
|
|
47
|
+
rule_id="general_005",
|
|
48
|
+
name="constraint_naming_convention",
|
|
49
|
+
description="Constraints must follow naming convention",
|
|
50
|
+
version=RuleVersion(1, 0, 0),
|
|
51
|
+
severity=LintSeverity.WARNING,
|
|
52
|
+
enabled_by_default=True,
|
|
53
|
+
),
|
|
54
|
+
Rule(
|
|
55
|
+
rule_id="general_006",
|
|
56
|
+
name="table_naming_convention",
|
|
57
|
+
description="Tables must follow naming convention",
|
|
58
|
+
version=RuleVersion(1, 0, 0),
|
|
59
|
+
severity=LintSeverity.WARNING,
|
|
60
|
+
enabled_by_default=True,
|
|
61
|
+
),
|
|
62
|
+
Rule(
|
|
63
|
+
rule_id="general_007",
|
|
64
|
+
name="column_naming_convention",
|
|
65
|
+
description="Columns must follow naming convention",
|
|
66
|
+
version=RuleVersion(1, 0, 0),
|
|
67
|
+
severity=LintSeverity.WARNING,
|
|
68
|
+
enabled_by_default=True,
|
|
69
|
+
),
|
|
70
|
+
Rule(
|
|
71
|
+
rule_id="general_008",
|
|
72
|
+
name="primary_key_required",
|
|
73
|
+
description="All tables should have a primary key",
|
|
74
|
+
version=RuleVersion(1, 0, 0),
|
|
75
|
+
severity=LintSeverity.WARNING,
|
|
76
|
+
enabled_by_default=True,
|
|
77
|
+
),
|
|
78
|
+
Rule(
|
|
79
|
+
rule_id="general_009",
|
|
80
|
+
name="no_reserved_keywords",
|
|
81
|
+
description="Identifiers must not use reserved keywords",
|
|
82
|
+
version=RuleVersion(1, 0, 0),
|
|
83
|
+
severity=LintSeverity.ERROR,
|
|
84
|
+
enabled_by_default=True,
|
|
85
|
+
),
|
|
86
|
+
Rule(
|
|
87
|
+
rule_id="general_010",
|
|
88
|
+
name="charset_specified",
|
|
89
|
+
description="Character set must be explicitly specified",
|
|
90
|
+
version=RuleVersion(1, 0, 0),
|
|
91
|
+
severity=LintSeverity.WARNING,
|
|
92
|
+
enabled_by_default=False,
|
|
93
|
+
),
|
|
94
|
+
Rule(
|
|
95
|
+
rule_id="general_011",
|
|
96
|
+
name="no_large_transactions",
|
|
97
|
+
description="Avoid migrations that take >30 seconds",
|
|
98
|
+
version=RuleVersion(1, 0, 0),
|
|
99
|
+
severity=LintSeverity.WARNING,
|
|
100
|
+
enabled_by_default=True,
|
|
101
|
+
),
|
|
102
|
+
Rule(
|
|
103
|
+
rule_id="general_012",
|
|
104
|
+
name="no_concurrent_index_creation",
|
|
105
|
+
description="Avoid creating indexes during peak hours",
|
|
106
|
+
version=RuleVersion(1, 0, 0),
|
|
107
|
+
severity=LintSeverity.WARNING,
|
|
108
|
+
enabled_by_default=True,
|
|
109
|
+
),
|
|
110
|
+
Rule(
|
|
111
|
+
rule_id="general_013",
|
|
112
|
+
name="foreign_key_naming",
|
|
113
|
+
description="Foreign keys must follow naming convention",
|
|
114
|
+
version=RuleVersion(1, 0, 0),
|
|
115
|
+
severity=LintSeverity.WARNING,
|
|
116
|
+
enabled_by_default=True,
|
|
117
|
+
),
|
|
118
|
+
Rule(
|
|
119
|
+
rule_id="general_014",
|
|
120
|
+
name="no_duplicate_indexes",
|
|
121
|
+
description="Avoid creating duplicate indexes",
|
|
122
|
+
version=RuleVersion(1, 0, 0),
|
|
123
|
+
severity=LintSeverity.ERROR,
|
|
124
|
+
enabled_by_default=True,
|
|
125
|
+
),
|
|
126
|
+
Rule(
|
|
127
|
+
rule_id="general_015",
|
|
128
|
+
name="data_type_precision",
|
|
129
|
+
description="Numeric types should specify precision",
|
|
130
|
+
version=RuleVersion(1, 0, 0),
|
|
131
|
+
severity=LintSeverity.WARNING,
|
|
132
|
+
enabled_by_default=True,
|
|
133
|
+
),
|
|
134
|
+
Rule(
|
|
135
|
+
rule_id="general_016",
|
|
136
|
+
name="no_text_in_indexes",
|
|
137
|
+
description="Avoid indexing TEXT columns directly",
|
|
138
|
+
version=RuleVersion(1, 0, 0),
|
|
139
|
+
severity=LintSeverity.WARNING,
|
|
140
|
+
enabled_by_default=True,
|
|
141
|
+
),
|
|
142
|
+
Rule(
|
|
143
|
+
rule_id="general_017",
|
|
144
|
+
name="rollback_strategy_defined",
|
|
145
|
+
description="Rollback strategy should be documented",
|
|
146
|
+
version=RuleVersion(1, 0, 0),
|
|
147
|
+
severity=LintSeverity.WARNING,
|
|
148
|
+
enabled_by_default=True,
|
|
149
|
+
),
|
|
150
|
+
Rule(
|
|
151
|
+
rule_id="general_018",
|
|
152
|
+
name="no_data_loss_without_warning",
|
|
153
|
+
description="Destructive operations must be explicit",
|
|
154
|
+
version=RuleVersion(1, 0, 0),
|
|
155
|
+
severity=LintSeverity.ERROR,
|
|
156
|
+
enabled_by_default=True,
|
|
157
|
+
),
|
|
158
|
+
Rule(
|
|
159
|
+
rule_id="general_019",
|
|
160
|
+
name="constraint_validation",
|
|
161
|
+
description="All constraints should be validated",
|
|
162
|
+
version=RuleVersion(1, 0, 0),
|
|
163
|
+
severity=LintSeverity.WARNING,
|
|
164
|
+
enabled_by_default=True,
|
|
165
|
+
),
|
|
166
|
+
Rule(
|
|
167
|
+
rule_id="general_020",
|
|
168
|
+
name="migration_reversibility",
|
|
169
|
+
description="Migrations should be reversible when possible",
|
|
170
|
+
version=RuleVersion(1, 0, 0),
|
|
171
|
+
severity=LintSeverity.WARNING,
|
|
172
|
+
enabled_by_default=True,
|
|
173
|
+
),
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
# Verify rule count matches docstring
|
|
177
|
+
assert len(rules) == 20, f"Expected 20 rules in GeneralLibrary, got {len(rules)}"
|
|
178
|
+
|
|
179
|
+
super().__init__(
|
|
180
|
+
name="General",
|
|
181
|
+
version=RuleVersion(major=1, minor=0, patch=0),
|
|
182
|
+
rules=rules,
|
|
183
|
+
tags=["general", "best-practices", "performance", "naming"],
|
|
184
|
+
)
|