atdd 0.2.1__py3-none-any.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.
- atdd/__init__.py +6 -0
- atdd/__main__.py +4 -0
- atdd/cli.py +404 -0
- atdd/coach/__init__.py +0 -0
- atdd/coach/commands/__init__.py +0 -0
- atdd/coach/commands/add_persistence_metadata.py +215 -0
- atdd/coach/commands/analyze_migrations.py +188 -0
- atdd/coach/commands/consumers.py +720 -0
- atdd/coach/commands/infer_governance_status.py +149 -0
- atdd/coach/commands/initializer.py +177 -0
- atdd/coach/commands/interface.py +1078 -0
- atdd/coach/commands/inventory.py +565 -0
- atdd/coach/commands/migration.py +240 -0
- atdd/coach/commands/registry.py +1560 -0
- atdd/coach/commands/session.py +430 -0
- atdd/coach/commands/sync.py +405 -0
- atdd/coach/commands/test_interface.py +399 -0
- atdd/coach/commands/test_runner.py +141 -0
- atdd/coach/commands/tests/__init__.py +1 -0
- atdd/coach/commands/tests/test_telemetry_array_validation.py +235 -0
- atdd/coach/commands/traceability.py +4264 -0
- atdd/coach/conventions/session.convention.yaml +754 -0
- atdd/coach/overlays/__init__.py +2 -0
- atdd/coach/overlays/claude.md +2 -0
- atdd/coach/schemas/config.schema.json +34 -0
- atdd/coach/schemas/manifest.schema.json +101 -0
- atdd/coach/templates/ATDD.md +282 -0
- atdd/coach/templates/SESSION-TEMPLATE.md +327 -0
- atdd/coach/utils/__init__.py +0 -0
- atdd/coach/utils/graph/__init__.py +0 -0
- atdd/coach/utils/graph/urn.py +875 -0
- atdd/coach/validators/__init__.py +0 -0
- atdd/coach/validators/shared_fixtures.py +365 -0
- atdd/coach/validators/test_enrich_wagon_registry.py +167 -0
- atdd/coach/validators/test_registry.py +575 -0
- atdd/coach/validators/test_session_validation.py +1183 -0
- atdd/coach/validators/test_traceability.py +448 -0
- atdd/coach/validators/test_update_feature_paths.py +108 -0
- atdd/coach/validators/test_validate_contract_consumers.py +297 -0
- atdd/coder/__init__.py +1 -0
- atdd/coder/conventions/adapter.recipe.yaml +88 -0
- atdd/coder/conventions/backend.convention.yaml +460 -0
- atdd/coder/conventions/boundaries.convention.yaml +666 -0
- atdd/coder/conventions/commons.convention.yaml +460 -0
- atdd/coder/conventions/complexity.recipe.yaml +109 -0
- atdd/coder/conventions/component-naming.convention.yaml +178 -0
- atdd/coder/conventions/design.convention.yaml +327 -0
- atdd/coder/conventions/design.recipe.yaml +273 -0
- atdd/coder/conventions/dto.convention.yaml +660 -0
- atdd/coder/conventions/frontend.convention.yaml +542 -0
- atdd/coder/conventions/green.convention.yaml +1012 -0
- atdd/coder/conventions/presentation.convention.yaml +587 -0
- atdd/coder/conventions/refactor.convention.yaml +535 -0
- atdd/coder/conventions/technology.convention.yaml +206 -0
- atdd/coder/conventions/tests/__init__.py +0 -0
- atdd/coder/conventions/tests/test_adapter_recipe.py +302 -0
- atdd/coder/conventions/tests/test_complexity_recipe.py +289 -0
- atdd/coder/conventions/tests/test_component_taxonomy.py +278 -0
- atdd/coder/conventions/tests/test_component_urn_naming.py +165 -0
- atdd/coder/conventions/tests/test_thinness_recipe.py +286 -0
- atdd/coder/conventions/thinness.recipe.yaml +82 -0
- atdd/coder/conventions/train.convention.yaml +325 -0
- atdd/coder/conventions/verification.protocol.yaml +53 -0
- atdd/coder/schemas/design_system.schema.json +361 -0
- atdd/coder/validators/__init__.py +0 -0
- atdd/coder/validators/test_commons_structure.py +485 -0
- atdd/coder/validators/test_complexity.py +416 -0
- atdd/coder/validators/test_cross_language_consistency.py +431 -0
- atdd/coder/validators/test_design_system_compliance.py +413 -0
- atdd/coder/validators/test_dto_testing_patterns.py +268 -0
- atdd/coder/validators/test_green_cross_stack_layers.py +168 -0
- atdd/coder/validators/test_green_layer_dependencies.py +148 -0
- atdd/coder/validators/test_green_python_layer_structure.py +103 -0
- atdd/coder/validators/test_green_supabase_layer_structure.py +103 -0
- atdd/coder/validators/test_import_boundaries.py +396 -0
- atdd/coder/validators/test_init_file_urns.py +593 -0
- atdd/coder/validators/test_preact_layer_boundaries.py +221 -0
- atdd/coder/validators/test_presentation_convention.py +260 -0
- atdd/coder/validators/test_python_architecture.py +674 -0
- atdd/coder/validators/test_quality_metrics.py +420 -0
- atdd/coder/validators/test_station_master_pattern.py +244 -0
- atdd/coder/validators/test_train_infrastructure.py +454 -0
- atdd/coder/validators/test_train_urns.py +293 -0
- atdd/coder/validators/test_typescript_architecture.py +616 -0
- atdd/coder/validators/test_usecase_structure.py +421 -0
- atdd/coder/validators/test_wagon_boundaries.py +586 -0
- atdd/conftest.py +126 -0
- atdd/planner/__init__.py +1 -0
- atdd/planner/conventions/acceptance.convention.yaml +538 -0
- atdd/planner/conventions/appendix.convention.yaml +187 -0
- atdd/planner/conventions/artifact-naming.convention.yaml +852 -0
- atdd/planner/conventions/component.convention.yaml +670 -0
- atdd/planner/conventions/criteria.convention.yaml +141 -0
- atdd/planner/conventions/feature.convention.yaml +371 -0
- atdd/planner/conventions/interface.convention.yaml +382 -0
- atdd/planner/conventions/steps.convention.yaml +141 -0
- atdd/planner/conventions/train.convention.yaml +552 -0
- atdd/planner/conventions/wagon.convention.yaml +275 -0
- atdd/planner/conventions/wmbt.convention.yaml +258 -0
- atdd/planner/schemas/acceptance.schema.json +336 -0
- atdd/planner/schemas/appendix.schema.json +78 -0
- atdd/planner/schemas/component.schema.json +114 -0
- atdd/planner/schemas/feature.schema.json +197 -0
- atdd/planner/schemas/train.schema.json +192 -0
- atdd/planner/schemas/wagon.schema.json +281 -0
- atdd/planner/schemas/wmbt.schema.json +59 -0
- atdd/planner/validators/__init__.py +0 -0
- atdd/planner/validators/conftest.py +5 -0
- atdd/planner/validators/test_draft_wagon_registry.py +374 -0
- atdd/planner/validators/test_plan_cross_refs.py +240 -0
- atdd/planner/validators/test_plan_uniqueness.py +224 -0
- atdd/planner/validators/test_plan_urn_resolution.py +268 -0
- atdd/planner/validators/test_plan_wagons.py +174 -0
- atdd/planner/validators/test_train_validation.py +514 -0
- atdd/planner/validators/test_wagon_urn_chain.py +648 -0
- atdd/planner/validators/test_wmbt_consistency.py +327 -0
- atdd/planner/validators/test_wmbt_vocabulary.py +632 -0
- atdd/tester/__init__.py +1 -0
- atdd/tester/conventions/artifact.convention.yaml +257 -0
- atdd/tester/conventions/contract.convention.yaml +1009 -0
- atdd/tester/conventions/filename.convention.yaml +555 -0
- atdd/tester/conventions/migration.convention.yaml +509 -0
- atdd/tester/conventions/red.convention.yaml +797 -0
- atdd/tester/conventions/routing.convention.yaml +51 -0
- atdd/tester/conventions/telemetry.convention.yaml +458 -0
- atdd/tester/schemas/a11y.tmpl.json +17 -0
- atdd/tester/schemas/artifact.schema.json +189 -0
- atdd/tester/schemas/contract.schema.json +591 -0
- atdd/tester/schemas/contract.tmpl.json +95 -0
- atdd/tester/schemas/db.tmpl.json +20 -0
- atdd/tester/schemas/e2e.tmpl.json +17 -0
- atdd/tester/schemas/edge_function.tmpl.json +17 -0
- atdd/tester/schemas/event.tmpl.json +17 -0
- atdd/tester/schemas/http.tmpl.json +19 -0
- atdd/tester/schemas/job.tmpl.json +18 -0
- atdd/tester/schemas/load.tmpl.json +21 -0
- atdd/tester/schemas/metric.tmpl.json +19 -0
- atdd/tester/schemas/pack.schema.json +139 -0
- atdd/tester/schemas/realtime.tmpl.json +20 -0
- atdd/tester/schemas/rls.tmpl.json +18 -0
- atdd/tester/schemas/script.tmpl.json +16 -0
- atdd/tester/schemas/sec.tmpl.json +18 -0
- atdd/tester/schemas/storage.tmpl.json +18 -0
- atdd/tester/schemas/telemetry.schema.json +128 -0
- atdd/tester/schemas/telemetry_tracking_manifest.schema.json +143 -0
- atdd/tester/schemas/test_filename.schema.json +194 -0
- atdd/tester/schemas/test_intent.schema.json +179 -0
- atdd/tester/schemas/unit.tmpl.json +18 -0
- atdd/tester/schemas/visual.tmpl.json +18 -0
- atdd/tester/schemas/ws.tmpl.json +17 -0
- atdd/tester/utils/__init__.py +0 -0
- atdd/tester/utils/filename.py +300 -0
- atdd/tester/validators/__init__.py +0 -0
- atdd/tester/validators/cleanup_duplicate_headers.py +116 -0
- atdd/tester/validators/cleanup_duplicate_headers_v2.py +135 -0
- atdd/tester/validators/conftest.py +5 -0
- atdd/tester/validators/coverage_gap_report.py +321 -0
- atdd/tester/validators/fix_dual_ac_references.py +179 -0
- atdd/tester/validators/remove_duplicate_lines.py +93 -0
- atdd/tester/validators/test_acceptance_urn_filename_mapping.py +359 -0
- atdd/tester/validators/test_acceptance_urn_separator.py +166 -0
- atdd/tester/validators/test_artifact_naming_category.py +307 -0
- atdd/tester/validators/test_contract_schema_compliance.py +706 -0
- atdd/tester/validators/test_contracts_structure.py +200 -0
- atdd/tester/validators/test_coverage_adequacy.py +797 -0
- atdd/tester/validators/test_dual_ac_reference.py +225 -0
- atdd/tester/validators/test_fixture_validity.py +372 -0
- atdd/tester/validators/test_isolation.py +487 -0
- atdd/tester/validators/test_migration_coverage.py +204 -0
- atdd/tester/validators/test_migration_criteria.py +276 -0
- atdd/tester/validators/test_migration_generation.py +116 -0
- atdd/tester/validators/test_python_test_naming.py +410 -0
- atdd/tester/validators/test_red_layer_validation.py +95 -0
- atdd/tester/validators/test_red_python_layer_structure.py +87 -0
- atdd/tester/validators/test_red_supabase_layer_structure.py +90 -0
- atdd/tester/validators/test_telemetry_structure.py +634 -0
- atdd/tester/validators/test_typescript_test_naming.py +301 -0
- atdd/tester/validators/test_typescript_test_structure.py +84 -0
- atdd-0.2.1.dist-info/METADATA +221 -0
- atdd-0.2.1.dist-info/RECORD +184 -0
- atdd-0.2.1.dist-info/WHEEL +5 -0
- atdd-0.2.1.dist-info/entry_points.txt +2 -0
- atdd-0.2.1.dist-info/licenses/LICENSE +674 -0
- atdd-0.2.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
version: "1.1"
|
|
2
|
+
name: "Contract-Driven Migration Convention"
|
|
3
|
+
description: "Generate Supabase PostgreSQL migrations from contract JSON schemas and infrastructure tables"
|
|
4
|
+
|
|
5
|
+
# Workflow Integration
|
|
6
|
+
workflow:
|
|
7
|
+
trigger: "After contract generation by tester"
|
|
8
|
+
|
|
9
|
+
phases:
|
|
10
|
+
1_scan: "Identify contracts without corresponding migrations"
|
|
11
|
+
2_generate: "Create migration template with TODO markers"
|
|
12
|
+
3_validate: "Ensure all contracts have migrations (ATDD test)"
|
|
13
|
+
4_review: "Human completes TODOs (foreign keys, indexes, RLS)"
|
|
14
|
+
5_apply: "supabase db push"
|
|
15
|
+
|
|
16
|
+
automation_level: "template_generation"
|
|
17
|
+
requires_human_review: true
|
|
18
|
+
|
|
19
|
+
command: "python atdd/coach/command/migration.py"
|
|
20
|
+
|
|
21
|
+
# Table Naming Convention
|
|
22
|
+
table_naming:
|
|
23
|
+
pattern: "{theme}_{domain}_{aspect}"
|
|
24
|
+
description: "Derives from contract x-artifact-metadata and file path"
|
|
25
|
+
|
|
26
|
+
examples:
|
|
27
|
+
- contract: "contracts/match/dilemma/current.schema.json"
|
|
28
|
+
metadata: "{domain: 'dilemma', resource: 'current'}"
|
|
29
|
+
path_theme: "match"
|
|
30
|
+
table_name: "match_dilemma_current"
|
|
31
|
+
|
|
32
|
+
- contract: "contracts/commons/ux/foundations.schema.json"
|
|
33
|
+
metadata: "{domain: 'ux', resource: 'foundations'}"
|
|
34
|
+
path_theme: "commons"
|
|
35
|
+
table_name: "commons_ux_foundations"
|
|
36
|
+
|
|
37
|
+
normalization: "snake_case"
|
|
38
|
+
max_length: 63 # PostgreSQL limit
|
|
39
|
+
|
|
40
|
+
# Type Mapping: JSON Schema → PostgreSQL
|
|
41
|
+
type_mapping:
|
|
42
|
+
primitives:
|
|
43
|
+
string:
|
|
44
|
+
default: "TEXT"
|
|
45
|
+
with_pattern_uuid: "UUID"
|
|
46
|
+
with_format_date_time: "TIMESTAMPTZ"
|
|
47
|
+
with_format_date: "DATE"
|
|
48
|
+
with_format_time: "TIME"
|
|
49
|
+
with_format_email: "TEXT" # Consider VARCHAR(255) for indexing
|
|
50
|
+
with_format_uri: "TEXT"
|
|
51
|
+
|
|
52
|
+
integer: "INTEGER"
|
|
53
|
+
number: "NUMERIC"
|
|
54
|
+
boolean: "BOOLEAN"
|
|
55
|
+
|
|
56
|
+
complex:
|
|
57
|
+
object: "JSONB" # Flag for normalization review
|
|
58
|
+
array: "JSONB" # Flag for normalization review
|
|
59
|
+
|
|
60
|
+
special_cases:
|
|
61
|
+
enum: "TEXT" # Add CHECK constraint
|
|
62
|
+
const: "TEXT" # Add CHECK constraint with value
|
|
63
|
+
|
|
64
|
+
notes:
|
|
65
|
+
- "JSONB for complex types requires TODO marker to consider normalization"
|
|
66
|
+
- "UUID pattern: ^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
|
67
|
+
- "Enums generate CHECK constraints: CHECK (column IN ('val1', 'val2'))"
|
|
68
|
+
|
|
69
|
+
# Migration Template Structure
|
|
70
|
+
template_structure:
|
|
71
|
+
header:
|
|
72
|
+
- "-- Generated from {contract_path}"
|
|
73
|
+
- "-- Generation time: {timestamp}"
|
|
74
|
+
- "-- ⚠️ REVIEW REQUIRED BEFORE APPLYING"
|
|
75
|
+
- ""
|
|
76
|
+
|
|
77
|
+
table_definition:
|
|
78
|
+
primary_key: "id UUID PRIMARY KEY DEFAULT gen_random_uuid()"
|
|
79
|
+
columns: "Derived from contract properties"
|
|
80
|
+
constraints: "NOT NULL for required fields, CHECK for patterns"
|
|
81
|
+
|
|
82
|
+
standard_columns:
|
|
83
|
+
- "created_at TIMESTAMPTZ DEFAULT NOW()"
|
|
84
|
+
- "updated_at TIMESTAMPTZ DEFAULT NOW()"
|
|
85
|
+
|
|
86
|
+
todo_markers:
|
|
87
|
+
- "-- ⚠️ TODO: Add foreign key constraints"
|
|
88
|
+
- "-- ⚠️ TODO: Add indexes for common queries"
|
|
89
|
+
- "-- ⚠️ TODO: Add RLS policies"
|
|
90
|
+
- "-- ⚠️ TODO: Add trigger for updated_at"
|
|
91
|
+
- "-- ⚠️ TODO: Review JSONB columns for normalization"
|
|
92
|
+
|
|
93
|
+
footer:
|
|
94
|
+
- "-- End of migration"
|
|
95
|
+
|
|
96
|
+
# Required TODOs for Human Review
|
|
97
|
+
human_review_required:
|
|
98
|
+
foreign_keys:
|
|
99
|
+
description: "Identify relationships between tables"
|
|
100
|
+
example: "ALTER TABLE match_dilemma_current ADD CONSTRAINT fk_fragment_a FOREIGN KEY (fragment_a_id) REFERENCES fragments(id);"
|
|
101
|
+
rationale: "Contract doesn't define referential integrity"
|
|
102
|
+
|
|
103
|
+
indexes:
|
|
104
|
+
description: "Add indexes for query performance"
|
|
105
|
+
example: "CREATE INDEX idx_dilemma_created ON match_dilemma_current(created_at DESC);"
|
|
106
|
+
rationale: "Access patterns unknown at generation time"
|
|
107
|
+
|
|
108
|
+
rls_policies:
|
|
109
|
+
description: "Row-level security for multi-tenant isolation"
|
|
110
|
+
example: "CREATE POLICY 'users_own_data' ON match_dilemma_current FOR SELECT USING (auth.uid() = user_id);"
|
|
111
|
+
rationale: "Security requirements specific to application"
|
|
112
|
+
|
|
113
|
+
triggers:
|
|
114
|
+
description: "Lifecycle hooks like updated_at timestamps"
|
|
115
|
+
example: "CREATE TRIGGER set_updated_at BEFORE UPDATE ON match_dilemma_current FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();"
|
|
116
|
+
rationale: "Automation preferences vary"
|
|
117
|
+
|
|
118
|
+
normalization:
|
|
119
|
+
description: "Extract nested objects to separate tables"
|
|
120
|
+
example: "Instead of JSONB, create separate fragments table"
|
|
121
|
+
rationale: "Schema design requires domain knowledge"
|
|
122
|
+
|
|
123
|
+
# Validation Rules
|
|
124
|
+
validation:
|
|
125
|
+
coverage:
|
|
126
|
+
rule: "Every contract must have migration or existing table"
|
|
127
|
+
test: "atdd/tester/test_migration_coverage.py::test_all_contracts_have_migrations"
|
|
128
|
+
failure: "Lists contracts without migrations"
|
|
129
|
+
|
|
130
|
+
no_unreviewed_todos:
|
|
131
|
+
rule: "Migrations cannot be applied with TODO markers"
|
|
132
|
+
test: "atdd/tester/test_migration_coverage.py::test_migration_templates_reviewed"
|
|
133
|
+
failure: "Lists migrations with unresolved TODOs"
|
|
134
|
+
|
|
135
|
+
type_mapping_complete:
|
|
136
|
+
rule: "All JSON Schema types must map to PostgreSQL"
|
|
137
|
+
test: "atdd/tester/test_migration_generation.py::test_json_to_postgres_type_mapping"
|
|
138
|
+
failure: "Reports unmapped types"
|
|
139
|
+
|
|
140
|
+
# Output Paths
|
|
141
|
+
output:
|
|
142
|
+
migrations_directory: "supabase/migrations/"
|
|
143
|
+
filename_pattern: "{timestamp}_{theme}_{domain}_{aspect}.sql"
|
|
144
|
+
timestamp_format: "%Y%m%d%H%M%S"
|
|
145
|
+
|
|
146
|
+
example: "supabase/migrations/20251030143000_match_dilemma_current.sql"
|
|
147
|
+
|
|
148
|
+
# Integration with ATDD Workflow
|
|
149
|
+
atdd_integration:
|
|
150
|
+
phase: "tester"
|
|
151
|
+
trigger: "After contract generation"
|
|
152
|
+
|
|
153
|
+
steps:
|
|
154
|
+
1: "Generate contract from wagon interface"
|
|
155
|
+
2: "Generate migration template from contract"
|
|
156
|
+
3: "Run coverage validation (RED if missing)"
|
|
157
|
+
4: "Human reviews and completes TODOs"
|
|
158
|
+
5: "Run validation (GREEN when complete)"
|
|
159
|
+
6: "Apply migration: supabase db push"
|
|
160
|
+
|
|
161
|
+
tests:
|
|
162
|
+
generation: "atdd/tester/test_migration_generation.py"
|
|
163
|
+
coverage: "atdd/tester/test_migration_coverage.py"
|
|
164
|
+
|
|
165
|
+
# References
|
|
166
|
+
references:
|
|
167
|
+
contract_convention: ".claude/conventions/tester/contract.convention.yaml"
|
|
168
|
+
supabase_docs: "https://supabase.com/docs/guides/database/tables"
|
|
169
|
+
postgres_types: "https://www.postgresql.org/docs/current/datatype.html"
|
|
170
|
+
|
|
171
|
+
# Command Usage
|
|
172
|
+
usage: |
|
|
173
|
+
# Generate all missing migrations
|
|
174
|
+
python atdd/coach/command/migration.py
|
|
175
|
+
|
|
176
|
+
# Generate for specific contract
|
|
177
|
+
python atdd/coach/command/migration.py --contract contracts/match/dilemma/current.schema.json
|
|
178
|
+
|
|
179
|
+
# Validate coverage
|
|
180
|
+
python atdd/coach/command/migration.py --validate
|
|
181
|
+
|
|
182
|
+
# Check for unreviewed TODOs
|
|
183
|
+
pytest atdd/tester/test_migration_coverage.py::test_migration_templates_reviewed
|
|
184
|
+
|
|
185
|
+
# JSONB-First Storage Strategy
|
|
186
|
+
jsonb_strategy:
|
|
187
|
+
pattern: "JSONB blob storage for document-like wagon contracts"
|
|
188
|
+
rationale: |
|
|
189
|
+
Wagon architecture treats contracts as atomic documents consumed by wagons.
|
|
190
|
+
JSONB blob storage aligns with this pattern:
|
|
191
|
+
- Wagons process complete contracts, not normalized fields
|
|
192
|
+
- Contract validation happens at application layer (JSON Schema)
|
|
193
|
+
- No SQL joins needed (references resolved via URNs in application)
|
|
194
|
+
- Event-driven: hot path is in-memory, DB is persistence layer
|
|
195
|
+
- Bounded contexts: each entity lives in its lifecycle (match/session/scenario)
|
|
196
|
+
|
|
197
|
+
standard_table_structure: |
|
|
198
|
+
CREATE TABLE {theme}_{domain}_{aspect} (
|
|
199
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
200
|
+
data JSONB NOT NULL,
|
|
201
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
202
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
203
|
+
);
|
|
204
|
+
CREATE INDEX idx_{table}_data ON {table} USING GIN (data);
|
|
205
|
+
|
|
206
|
+
migrations_not_needed:
|
|
207
|
+
description: "Contract schema changes do NOT trigger database migrations"
|
|
208
|
+
scenarios:
|
|
209
|
+
- change: "Add optional field to contract"
|
|
210
|
+
migration: "❌ Not needed"
|
|
211
|
+
reason: "Old records lack field, app provides default"
|
|
212
|
+
- change: "Remove field from contract"
|
|
213
|
+
migration: "❌ Not needed"
|
|
214
|
+
reason: "Old records keep field in JSONB (ignored by app)"
|
|
215
|
+
- change: "Change field type (string → int)"
|
|
216
|
+
migration: "❌ Not needed"
|
|
217
|
+
reason: "App handles both formats via _version field"
|
|
218
|
+
- change: "Rename field"
|
|
219
|
+
migration: "❌ Not needed (but code complexity)"
|
|
220
|
+
reason: "App reads old and new field names during transition"
|
|
221
|
+
|
|
222
|
+
migrations_needed:
|
|
223
|
+
description: "Only structural changes require database migrations"
|
|
224
|
+
scenarios:
|
|
225
|
+
- change: "New contract introduced"
|
|
226
|
+
migration: "✅ CREATE TABLE"
|
|
227
|
+
tool: "migration.py from contract schema"
|
|
228
|
+
- change: "Contract retired/deleted"
|
|
229
|
+
migration: "✅ DROP TABLE (optional)"
|
|
230
|
+
tool: "Manual via Supabase CLI"
|
|
231
|
+
- change: "Add index for discovered query pattern"
|
|
232
|
+
migration: "✅ CREATE INDEX"
|
|
233
|
+
tool: "Supabase CLI: supabase db diff"
|
|
234
|
+
- change: "Extract column for performance"
|
|
235
|
+
migration: "✅ ALTER TABLE ADD COLUMN"
|
|
236
|
+
tool: "Manual optimization (rare)"
|
|
237
|
+
|
|
238
|
+
versioning:
|
|
239
|
+
description: "Dual versioning strategy for schema evolution"
|
|
240
|
+
|
|
241
|
+
schema_version:
|
|
242
|
+
location: "contracts/{theme}/{domain}/{aspect}.schema.json"
|
|
243
|
+
field: "version"
|
|
244
|
+
format: "Semantic versioning (1.0.0, 1.1.0, 2.0.0)"
|
|
245
|
+
purpose: "Documents contract schema definition version"
|
|
246
|
+
used_by: "Developers, validators, documentation"
|
|
247
|
+
example: |
|
|
248
|
+
{
|
|
249
|
+
"$id": "match:dilemma:current",
|
|
250
|
+
"version": "2.0.0", ← Contract schema is at v2.0.0
|
|
251
|
+
"properties": { ... }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
data_version:
|
|
255
|
+
location: "Database JSONB data blob"
|
|
256
|
+
field: "_version"
|
|
257
|
+
format: "String major version (\"1\", \"2\")"
|
|
258
|
+
purpose: "Tags which schema version this specific data conforms to"
|
|
259
|
+
used_by: "Application runtime code for backward compatibility"
|
|
260
|
+
example: |
|
|
261
|
+
// Data in Supabase JSONB column
|
|
262
|
+
{
|
|
263
|
+
"_version": "1", ← This record uses v1 format
|
|
264
|
+
"id": "abc123",
|
|
265
|
+
"amount": "100" // string in v1
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
version_skew_handling:
|
|
269
|
+
description: "Application code handles multiple data formats"
|
|
270
|
+
pattern: |
|
|
271
|
+
def parse_dilemma(data: dict) -> Dilemma:
|
|
272
|
+
version = data.get("_version", "1") # Default to v1
|
|
273
|
+
|
|
274
|
+
if version == "1":
|
|
275
|
+
# OLD FORMAT: amount is string
|
|
276
|
+
return Dilemma(
|
|
277
|
+
id=data["id"],
|
|
278
|
+
amount=int(data["amount"]) # Convert string → int
|
|
279
|
+
)
|
|
280
|
+
else: # version 2+
|
|
281
|
+
# NEW FORMAT: amount is int
|
|
282
|
+
return Dilemma(
|
|
283
|
+
id=data["id"],
|
|
284
|
+
amount=data["amount"] # Already int
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
semantic_versioning_rules: |
|
|
288
|
+
| Change | Version | Example | Migration Needed? |
|
|
289
|
+
|-----------------------|---------|----------------------|----------------------------|
|
|
290
|
+
| Remove required field | v2.0.0 | amount deleted | ❌ No (JSONB flexible) |
|
|
291
|
+
| Change field type | v2.0.0 | amount: string → int | ❌ No (app handles both) |
|
|
292
|
+
| Add optional field | v1.1.0 | description added | ❌ No (old records lack it) |
|
|
293
|
+
| Fix docs | v1.0.1 | Better descriptions | ❌ No |
|
|
294
|
+
|
|
295
|
+
lifecycle_signals: |
|
|
296
|
+
- v0.x.x = Draft (planner/tester iterating on structure)
|
|
297
|
+
- v1.x.x+ = Stable (published, used by runtime code)
|
|
298
|
+
- Major bumps = Breaking changes (old code can't read new format without _version check)
|
|
299
|
+
|
|
300
|
+
# Supabase CLI Integration
|
|
301
|
+
cli_integration:
|
|
302
|
+
description: "Supabase CLI for structural changes only, NOT contract evolution"
|
|
303
|
+
cli_version: "v2.54.11"
|
|
304
|
+
|
|
305
|
+
commands:
|
|
306
|
+
db_diff:
|
|
307
|
+
command: "supabase db diff -f <migration_name>"
|
|
308
|
+
purpose: "Generate migration from local schema changes"
|
|
309
|
+
use_case: "Adding indexes or rare table structure changes"
|
|
310
|
+
example: |
|
|
311
|
+
# After adding an index manually in local DB
|
|
312
|
+
supabase db diff -f add_dilemma_match_index
|
|
313
|
+
|
|
314
|
+
db_push:
|
|
315
|
+
command: "supabase db push"
|
|
316
|
+
purpose: "Apply migrations to remote database"
|
|
317
|
+
use_case: "Deploy new tables or index changes"
|
|
318
|
+
example: |
|
|
319
|
+
# Apply all pending migrations
|
|
320
|
+
supabase db push
|
|
321
|
+
|
|
322
|
+
db_pull:
|
|
323
|
+
command: "supabase db pull"
|
|
324
|
+
purpose: "Sync remote schema to local"
|
|
325
|
+
use_case: "Pull schema changes from production"
|
|
326
|
+
example: |
|
|
327
|
+
# Fetch remote schema
|
|
328
|
+
supabase db pull
|
|
329
|
+
|
|
330
|
+
gen_types_dart:
|
|
331
|
+
command: "supabase gen types dart --linked > lib/types/database.dart"
|
|
332
|
+
purpose: "Generate Dart types from database schema"
|
|
333
|
+
use_case: "Type safety for Dart client code"
|
|
334
|
+
example: |
|
|
335
|
+
# Generate types after schema changes
|
|
336
|
+
supabase gen types dart --linked > lib/types/database.dart
|
|
337
|
+
|
|
338
|
+
workflow:
|
|
339
|
+
description: "When to use migration.py vs Supabase CLI"
|
|
340
|
+
|
|
341
|
+
create_table:
|
|
342
|
+
trigger: "New contract introduced"
|
|
343
|
+
tool: "migration.py"
|
|
344
|
+
process: |
|
|
345
|
+
1. Tester generates contract from wagon interface
|
|
346
|
+
2. migration.py detects new contract
|
|
347
|
+
3. Generates CREATE TABLE migration with standard JSONB structure
|
|
348
|
+
4. Human reviews (no TODO markers for JSONB approach)
|
|
349
|
+
5. Apply: supabase db push
|
|
350
|
+
|
|
351
|
+
add_index:
|
|
352
|
+
trigger: "Discover slow query pattern in production"
|
|
353
|
+
tool: "Supabase CLI"
|
|
354
|
+
process: |
|
|
355
|
+
1. Identify query performance issue
|
|
356
|
+
2. Add index manually in local DB for testing
|
|
357
|
+
3. Generate migration: supabase db diff -f add_index_name
|
|
358
|
+
4. Review generated migration
|
|
359
|
+
5. Apply: supabase db push
|
|
360
|
+
|
|
361
|
+
contract_evolution:
|
|
362
|
+
trigger: "Contract schema changes (field add/remove/type change)"
|
|
363
|
+
tool: "❌ NO MIGRATION NEEDED"
|
|
364
|
+
process: |
|
|
365
|
+
1. Update contract schema version (e.g., 1.0.0 → 2.0.0)
|
|
366
|
+
2. Add _version field to contract properties if not present
|
|
367
|
+
3. Update application code to handle version skew
|
|
368
|
+
4. Deploy code changes
|
|
369
|
+
5. No database migration required
|
|
370
|
+
|
|
371
|
+
structural_vs_schema:
|
|
372
|
+
structural_migrations:
|
|
373
|
+
description: "Changes to database structure"
|
|
374
|
+
examples: ["CREATE TABLE", "DROP TABLE", "CREATE INDEX", "ALTER TABLE ADD COLUMN (rare)"]
|
|
375
|
+
tool: "migration.py (CREATE) or Supabase CLI (INDEX/ALTER)"
|
|
376
|
+
|
|
377
|
+
schema_evolution:
|
|
378
|
+
description: "Changes to contract schema"
|
|
379
|
+
examples: ["Add field", "Remove field", "Change type", "Rename field"]
|
|
380
|
+
tool: "❌ No migration - handled in application code via _version"
|
|
381
|
+
|
|
382
|
+
# ============================================================================
|
|
383
|
+
# TABLE CATEGORIES (Added in v1.1)
|
|
384
|
+
# ============================================================================
|
|
385
|
+
table_categories:
|
|
386
|
+
description: "Distinguish contract tables from infrastructure tables"
|
|
387
|
+
|
|
388
|
+
contract_tables:
|
|
389
|
+
definition: "Tables storing wagon contracts (data interchange between wagons)"
|
|
390
|
+
pattern: "JSONB blob storage (see jsonb_strategy section)"
|
|
391
|
+
examples: ["commons_ux_skin", "match_dilemma_current", "match_dilemma_paired"]
|
|
392
|
+
naming: "{theme}_{domain}_{aspect} (from contract path)"
|
|
393
|
+
rationale: |
|
|
394
|
+
- Wagons consume complete contracts as documents
|
|
395
|
+
- Schema evolution via _version field in JSONB
|
|
396
|
+
- No migrations needed for contract changes
|
|
397
|
+
|
|
398
|
+
infrastructure_tables:
|
|
399
|
+
definition: "Tables for event sourcing, caching, queuing (non-contract data)"
|
|
400
|
+
pattern: "Choose pattern based on access patterns and constraints"
|
|
401
|
+
examples: ["domain_impact_events", "preload_card_cache", "background_jobs"]
|
|
402
|
+
naming: "{feature}_{purpose} (descriptive, not contract-derived)"
|
|
403
|
+
rationale: |
|
|
404
|
+
- Optimize for query performance, concurrency, or consistency
|
|
405
|
+
- Pattern choice driven by infrastructure needs, not contract versioning
|
|
406
|
+
|
|
407
|
+
# ============================================================================
|
|
408
|
+
# INFRASTRUCTURE PATTERNS (Added in v1.1)
|
|
409
|
+
# ============================================================================
|
|
410
|
+
infrastructure_patterns:
|
|
411
|
+
event_logs:
|
|
412
|
+
use_when: "Implementing event sourcing pattern (append-only immutable logs)"
|
|
413
|
+
pattern: "Normalized columns for event schema + JSONB for metadata"
|
|
414
|
+
rationale: |
|
|
415
|
+
Event logs have different constraints than contract tables:
|
|
416
|
+
- Events are immutable (no updates → no schema migration risk)
|
|
417
|
+
- Query performance critical (event replay by aggregate_id, timestamp)
|
|
418
|
+
- Type safety critical (corrupt events break state reconstruction)
|
|
419
|
+
- Normalized columns outperform JSONB path queries by 5-8x
|
|
420
|
+
|
|
421
|
+
schema_template: |
|
|
422
|
+
CREATE TABLE {wagon}_events (
|
|
423
|
+
id BIGSERIAL PRIMARY KEY,
|
|
424
|
+
aggregate_id BIGINT NOT NULL,
|
|
425
|
+
event_type VARCHAR(50) NOT NULL,
|
|
426
|
+
event_data JSONB NOT NULL,
|
|
427
|
+
timestamp TIMESTAMPTZ NOT NULL,
|
|
428
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
429
|
+
-- NOTE: No updated_at - events are immutable
|
|
430
|
+
);
|
|
431
|
+
CREATE INDEX idx_{wagon}_events_aggregate ON {wagon}_events(aggregate_id, timestamp DESC);
|
|
432
|
+
|
|
433
|
+
real_example: |
|
|
434
|
+
CREATE TABLE domain_impact_events (
|
|
435
|
+
id BIGSERIAL PRIMARY KEY,
|
|
436
|
+
match_id BIGINT NOT NULL,
|
|
437
|
+
cell_id VARCHAR(2) NOT NULL CHECK (cell_id ~ '^[adfes][COPFI]$'),
|
|
438
|
+
delta NUMERIC(10, 2) NOT NULL CHECK (delta >= 0),
|
|
439
|
+
timestamp TIMESTAMPTZ NOT NULL,
|
|
440
|
+
event_metadata JSONB DEFAULT '{}'::jsonb,
|
|
441
|
+
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
442
|
+
);
|
|
443
|
+
CREATE INDEX idx_events_match ON domain_impact_events(match_id, timestamp);
|
|
444
|
+
|
|
445
|
+
caches:
|
|
446
|
+
use_when: "Temporary or preloaded data for query optimization"
|
|
447
|
+
pattern: "Hybrid (normalized lookup keys + JSONB payloads)"
|
|
448
|
+
rationale: "Lookups by FKs (normalized) + flexible cache payload (JSONB)"
|
|
449
|
+
|
|
450
|
+
schema_template: |
|
|
451
|
+
CREATE TABLE {feature}_cache (
|
|
452
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
453
|
+
lookup_key UUID NOT NULL,
|
|
454
|
+
cached_data JSONB NOT NULL,
|
|
455
|
+
expires_at TIMESTAMPTZ,
|
|
456
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
457
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
458
|
+
);
|
|
459
|
+
CREATE INDEX idx_{feature}_cache_lookup ON {feature}_cache (lookup_key);
|
|
460
|
+
|
|
461
|
+
queues:
|
|
462
|
+
use_when: "Background job queues, task scheduling"
|
|
463
|
+
pattern: "Normalized columns for queue operations + JSONB for task data"
|
|
464
|
+
|
|
465
|
+
schema_template: |
|
|
466
|
+
CREATE TABLE background_jobs (
|
|
467
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
468
|
+
job_type VARCHAR(50) NOT NULL,
|
|
469
|
+
status VARCHAR(20) NOT NULL CHECK (status IN ('pending', 'running', 'completed', 'failed')),
|
|
470
|
+
priority INT DEFAULT 0,
|
|
471
|
+
scheduled_at TIMESTAMPTZ NOT NULL,
|
|
472
|
+
job_data JSONB NOT NULL,
|
|
473
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
474
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
475
|
+
);
|
|
476
|
+
CREATE INDEX idx_jobs_queue ON background_jobs (status, priority DESC, scheduled_at)
|
|
477
|
+
WHERE status IN ('pending', 'running');
|
|
478
|
+
|
|
479
|
+
# ============================================================================
|
|
480
|
+
# PATTERN DECISION TREE (Added in v1.1)
|
|
481
|
+
# ============================================================================
|
|
482
|
+
pattern_decision_tree:
|
|
483
|
+
step_1:
|
|
484
|
+
question: "Is this a wagon contract table?"
|
|
485
|
+
yes: "Use JSONB blob pattern → See jsonb_strategy"
|
|
486
|
+
no: "Proceed to step 2"
|
|
487
|
+
|
|
488
|
+
step_2:
|
|
489
|
+
question: "What is the table's purpose?"
|
|
490
|
+
event_log: "Use normalized columns → See infrastructure_patterns.event_logs"
|
|
491
|
+
cache: "Use hybrid pattern → See infrastructure_patterns.caches"
|
|
492
|
+
queue: "Use normalized + JSONB → See infrastructure_patterns.queues"
|
|
493
|
+
other: "Analyze access patterns (high read = normalized, high write = JSONB)"
|
|
494
|
+
|
|
495
|
+
# ============================================================================
|
|
496
|
+
# CHANGELOG
|
|
497
|
+
# ============================================================================
|
|
498
|
+
changelog_v1_1:
|
|
499
|
+
date: "2025-11-08"
|
|
500
|
+
summary: "Add infrastructure patterns for event logs, caches, queues"
|
|
501
|
+
rationale: |
|
|
502
|
+
v1.0 assumed all tables are contract tables. This created confusion for
|
|
503
|
+
event sourcing infrastructure where normalized columns outperform JSONB
|
|
504
|
+
by 5-8x for event replay queries.
|
|
505
|
+
|
|
506
|
+
new_sections:
|
|
507
|
+
- "table_categories: Distinguish contract vs infrastructure"
|
|
508
|
+
- "infrastructure_patterns: Event logs, caches, queues"
|
|
509
|
+
- "pattern_decision_tree: Guide for choosing pattern"
|