fraiseql-confiture 0.3.7__cp311-cp311-macosx_11_0_arm64.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.
Files changed (124) hide show
  1. confiture/__init__.py +48 -0
  2. confiture/_core.cpython-311-darwin.so +0 -0
  3. confiture/cli/__init__.py +0 -0
  4. confiture/cli/dry_run.py +116 -0
  5. confiture/cli/lint_formatter.py +193 -0
  6. confiture/cli/main.py +1893 -0
  7. confiture/config/__init__.py +0 -0
  8. confiture/config/environment.py +263 -0
  9. confiture/core/__init__.py +51 -0
  10. confiture/core/anonymization/__init__.py +0 -0
  11. confiture/core/anonymization/audit.py +485 -0
  12. confiture/core/anonymization/benchmarking.py +372 -0
  13. confiture/core/anonymization/breach_notification.py +652 -0
  14. confiture/core/anonymization/compliance.py +617 -0
  15. confiture/core/anonymization/composer.py +298 -0
  16. confiture/core/anonymization/data_subject_rights.py +669 -0
  17. confiture/core/anonymization/factory.py +319 -0
  18. confiture/core/anonymization/governance.py +737 -0
  19. confiture/core/anonymization/performance.py +1092 -0
  20. confiture/core/anonymization/profile.py +284 -0
  21. confiture/core/anonymization/registry.py +195 -0
  22. confiture/core/anonymization/security/kms_manager.py +547 -0
  23. confiture/core/anonymization/security/lineage.py +888 -0
  24. confiture/core/anonymization/security/token_store.py +686 -0
  25. confiture/core/anonymization/strategies/__init__.py +41 -0
  26. confiture/core/anonymization/strategies/address.py +359 -0
  27. confiture/core/anonymization/strategies/credit_card.py +374 -0
  28. confiture/core/anonymization/strategies/custom.py +161 -0
  29. confiture/core/anonymization/strategies/date.py +218 -0
  30. confiture/core/anonymization/strategies/differential_privacy.py +398 -0
  31. confiture/core/anonymization/strategies/email.py +141 -0
  32. confiture/core/anonymization/strategies/format_preserving_encryption.py +310 -0
  33. confiture/core/anonymization/strategies/hash.py +150 -0
  34. confiture/core/anonymization/strategies/ip_address.py +235 -0
  35. confiture/core/anonymization/strategies/masking_retention.py +252 -0
  36. confiture/core/anonymization/strategies/name.py +298 -0
  37. confiture/core/anonymization/strategies/phone.py +119 -0
  38. confiture/core/anonymization/strategies/preserve.py +85 -0
  39. confiture/core/anonymization/strategies/redact.py +101 -0
  40. confiture/core/anonymization/strategies/salted_hashing.py +322 -0
  41. confiture/core/anonymization/strategies/text_redaction.py +183 -0
  42. confiture/core/anonymization/strategies/tokenization.py +334 -0
  43. confiture/core/anonymization/strategy.py +241 -0
  44. confiture/core/anonymization/syncer_audit.py +357 -0
  45. confiture/core/blue_green.py +683 -0
  46. confiture/core/builder.py +500 -0
  47. confiture/core/checksum.py +358 -0
  48. confiture/core/connection.py +184 -0
  49. confiture/core/differ.py +522 -0
  50. confiture/core/drift.py +564 -0
  51. confiture/core/dry_run.py +182 -0
  52. confiture/core/health.py +313 -0
  53. confiture/core/hooks/__init__.py +87 -0
  54. confiture/core/hooks/base.py +232 -0
  55. confiture/core/hooks/context.py +146 -0
  56. confiture/core/hooks/execution_strategies.py +57 -0
  57. confiture/core/hooks/observability.py +220 -0
  58. confiture/core/hooks/phases.py +53 -0
  59. confiture/core/hooks/registry.py +295 -0
  60. confiture/core/large_tables.py +775 -0
  61. confiture/core/linting/__init__.py +70 -0
  62. confiture/core/linting/composer.py +192 -0
  63. confiture/core/linting/libraries/__init__.py +17 -0
  64. confiture/core/linting/libraries/gdpr.py +168 -0
  65. confiture/core/linting/libraries/general.py +184 -0
  66. confiture/core/linting/libraries/hipaa.py +144 -0
  67. confiture/core/linting/libraries/pci_dss.py +104 -0
  68. confiture/core/linting/libraries/sox.py +120 -0
  69. confiture/core/linting/schema_linter.py +491 -0
  70. confiture/core/linting/versioning.py +151 -0
  71. confiture/core/locking.py +389 -0
  72. confiture/core/migration_generator.py +298 -0
  73. confiture/core/migrator.py +882 -0
  74. confiture/core/observability/__init__.py +44 -0
  75. confiture/core/observability/audit.py +323 -0
  76. confiture/core/observability/logging.py +187 -0
  77. confiture/core/observability/metrics.py +174 -0
  78. confiture/core/observability/tracing.py +192 -0
  79. confiture/core/pg_version.py +418 -0
  80. confiture/core/pool.py +406 -0
  81. confiture/core/risk/__init__.py +39 -0
  82. confiture/core/risk/predictor.py +188 -0
  83. confiture/core/risk/scoring.py +248 -0
  84. confiture/core/rollback_generator.py +388 -0
  85. confiture/core/schema_analyzer.py +769 -0
  86. confiture/core/schema_to_schema.py +590 -0
  87. confiture/core/security/__init__.py +32 -0
  88. confiture/core/security/logging.py +201 -0
  89. confiture/core/security/validation.py +416 -0
  90. confiture/core/signals.py +371 -0
  91. confiture/core/syncer.py +540 -0
  92. confiture/exceptions.py +192 -0
  93. confiture/integrations/__init__.py +0 -0
  94. confiture/models/__init__.py +24 -0
  95. confiture/models/lint.py +193 -0
  96. confiture/models/migration.py +265 -0
  97. confiture/models/schema.py +203 -0
  98. confiture/models/sql_file_migration.py +225 -0
  99. confiture/scenarios/__init__.py +36 -0
  100. confiture/scenarios/compliance.py +586 -0
  101. confiture/scenarios/ecommerce.py +199 -0
  102. confiture/scenarios/financial.py +253 -0
  103. confiture/scenarios/healthcare.py +315 -0
  104. confiture/scenarios/multi_tenant.py +340 -0
  105. confiture/scenarios/saas.py +295 -0
  106. confiture/testing/FRAMEWORK_API.md +722 -0
  107. confiture/testing/__init__.py +100 -0
  108. confiture/testing/fixtures/__init__.py +11 -0
  109. confiture/testing/fixtures/data_validator.py +229 -0
  110. confiture/testing/fixtures/migration_runner.py +167 -0
  111. confiture/testing/fixtures/schema_snapshotter.py +352 -0
  112. confiture/testing/frameworks/__init__.py +10 -0
  113. confiture/testing/frameworks/mutation.py +587 -0
  114. confiture/testing/frameworks/performance.py +479 -0
  115. confiture/testing/loader.py +225 -0
  116. confiture/testing/pytest/__init__.py +38 -0
  117. confiture/testing/pytest_plugin.py +190 -0
  118. confiture/testing/sandbox.py +304 -0
  119. confiture/testing/utils/__init__.py +0 -0
  120. fraiseql_confiture-0.3.7.dist-info/METADATA +438 -0
  121. fraiseql_confiture-0.3.7.dist-info/RECORD +124 -0
  122. fraiseql_confiture-0.3.7.dist-info/WHEEL +4 -0
  123. fraiseql_confiture-0.3.7.dist-info/entry_points.txt +4 -0
  124. fraiseql_confiture-0.3.7.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
+ )