hecks 2.8.0 → 2.9.0
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.
- checksums.yaml +4 -4
- data/exe/hecks +4 -6
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +17 -98
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +31 -141
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +9 -90
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +150 -678
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +2 -15
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +99 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +141 -568
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +26 -81
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +3 -2
|
@@ -5,22 +5,13 @@ require_relative "../../runtime/instance"
|
|
|
5
5
|
|
|
6
6
|
module Hecks
|
|
7
7
|
module Adapters
|
|
8
|
-
# The default
|
|
9
|
-
#
|
|
10
|
-
# with an append-only `@entries`/`@events` log alongside it. Every
|
|
11
|
-
# behaviors suite must resolve to this one (see
|
|
12
|
-
# Behaviors::Expectations#guard_memory_only!) precisely because a fresh
|
|
13
|
-
# instance really is a fresh store, with nothing shared across tests or
|
|
14
|
-
# tenants.
|
|
8
|
+
# The default in-process adapter: a Hash of `Runtime::Instance` records
|
|
9
|
+
# per aggregate, plus an append-only `@entries`/`@events` log.
|
|
15
10
|
class Memory
|
|
16
11
|
# Reports that two tenant boots of this adapter never share state.
|
|
17
12
|
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# two `Runtime.boot` calls build two entirely separate Registry
|
|
21
|
-
# objects and, through them, two entirely separate Memory
|
|
22
|
-
# instances, so two tenant boots never share this adapter's state
|
|
23
|
-
# by construction — nothing here needs to know "tenant" exists.
|
|
13
|
+
# Trivially true: `@records` is a plain ivar, and each `Runtime.boot`
|
|
14
|
+
# builds a wholly separate Memory instance.
|
|
24
15
|
#
|
|
25
16
|
# @return [Boolean] always true
|
|
26
17
|
def self.tenant_capable? = true
|
|
@@ -81,11 +72,10 @@ module Hecks
|
|
|
81
72
|
|
|
82
73
|
# Records one journal entry, holding a codec copy of its state.
|
|
83
74
|
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
# plain-Hash shape a Heki/Sqlite/Postgres entry has.
|
|
75
|
+
# Like every durable adapter, the journal holds `StateCodec.copy` —
|
|
76
|
+
# what an encode-to-JSON then decode would hand back — never the
|
|
77
|
+
# caller's own live state objects, so an entry read back here has
|
|
78
|
+
# the same deep-symbol, plain-Hash shape a Heki/Sqlite/Postgres entry has.
|
|
89
79
|
#
|
|
90
80
|
# @param entry [Ports::Persistence::Entry] the save or delete to journal
|
|
91
81
|
# @return [Ports::Persistence::Entry] the caller's own `entry`, not the journalled copy
|
|
@@ -163,18 +153,8 @@ module Hecks
|
|
|
163
153
|
|
|
164
154
|
# Empties the records, journal, event log and outbox, keeping the adapter itself.
|
|
165
155
|
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
# forwards to it and only raises when the wrapped adapter doesn't
|
|
169
|
-
# respond to `reset!` at all, and nothing about "in memory" implies
|
|
170
|
-
# "cannot be cleared". Existing callers that fully re-`Hecks.boot`
|
|
171
|
-
# a domain per test case don't need this — they get a brand new
|
|
172
|
-
# `Memory` instance, with brand new empty `@records`/`@events`/
|
|
173
|
-
# `@entries`, for free. This is for the other case: a caller that
|
|
174
|
-
# deliberately keeps one booted runtime across many cases (to skip
|
|
175
|
-
# `load_domain`'s own per-boot parse/verify cost) and wants each
|
|
176
|
-
# case to start from the same clean slate `Hecks.boot` would have
|
|
177
|
-
# given it, without paying for a fresh boot to get there.
|
|
156
|
+
# Lets a caller that keeps one booted runtime across many cases reset
|
|
157
|
+
# to a clean slate without paying for a fresh boot.
|
|
178
158
|
#
|
|
179
159
|
# @return [Adapters::Memory] self, now empty
|
|
180
160
|
def reset!
|
|
@@ -253,74 +233,13 @@ module Hecks
|
|
|
253
233
|
|
|
254
234
|
private
|
|
255
235
|
|
|
256
|
-
#
|
|
257
|
-
#
|
|
258
|
-
#
|
|
259
|
-
#
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
#
|
|
263
|
-
# have no storage of their own (S17, ADR 0026). A table of N member
|
|
264
|
-
# rows costs O(N) dispatches, each paying twice for the aggregate's own
|
|
265
|
-
# size-N state: once in `StateCodec.copy` (`append`/`project`'s own
|
|
266
|
-
# encode-then-decode round trip) and again in `Instance#initialize`'s
|
|
267
|
-
# `hydrate_with_defaults`, which re-walks and re-validates every
|
|
268
|
-
# element of an entity list on every save regardless of how many of
|
|
269
|
-
# them were already valid as of the previous one
|
|
270
|
-
# (`Value::EntityListCoercion#hydrate_entity_list` has no "already
|
|
271
|
-
# hydrated" shortcut for entity elements — only a value-object list
|
|
272
|
-
# element gets one). Both are O(N) per save, so a table of N rows
|
|
273
|
-
# costs O(N^2) total — and it is paid by every rspec worker and every
|
|
274
|
-
# `bin/*` subprocess that boots the language at all (found live: PR
|
|
275
|
-
# #738's 128-row `RefusalSiteArgument` table alone tripled this one
|
|
276
|
-
# aggregate's own save time, 6.7s -> 18.2s).
|
|
277
|
-
#
|
|
278
|
-
# `Runtime::Value.judge_bootstrapping?` (judge.rb's own `send_to`,
|
|
279
|
-
# wrapping every dispatch the judge makes) is already the flag that
|
|
280
|
-
# marks exactly this window and nothing else — "never for a real
|
|
281
|
-
# domain's own declared value objects... which Judge never dispatches
|
|
282
|
-
# commands against" (coercion.rb's own comment on the same flag). It
|
|
283
|
-
# is reused here rather than a new toggle for the same reason: one
|
|
284
|
-
# flag, one meaning, checked by two unrelated callers for two
|
|
285
|
-
# unrelated purposes (loosening a scalar-shape check there, skipping
|
|
286
|
-
# both round trips here) is simpler to reason about than two flags
|
|
287
|
-
# that would always be true or false together.
|
|
288
|
-
#
|
|
289
|
-
# Why skipping both is safe here, and only here: `entry.state` a save
|
|
290
|
-
# ever hands this adapter is always `instance.state.dup`
|
|
291
|
-
# (`AppendOnly#save`) — a shallow copy of an already-hydrated,
|
|
292
|
-
# already-validated live `Instance`'s own state, built by the very
|
|
293
|
-
# same `Value.for_attribute`/`hydrate_with_defaults` machinery
|
|
294
|
-
# `StateCodec.copy` and `Instance.new`'s default (`hydrate: true`)
|
|
295
|
-
# path would otherwise redo. Every value inside it — a `Runtime::
|
|
296
|
-
# Value` (frozen through, see value.rb's own header) or a `list_of`
|
|
297
|
-
# attribute's own array (`Freezer.deep`d the moment it was built,
|
|
298
|
-
# instance.rb's own header on `Instance#dup`) — is already immutable,
|
|
299
|
-
# so a bare top-level `.dup` is exactly as safe as `Instance#dup`
|
|
300
|
-
# already trusts it to be everywhere else in this codebase; nothing
|
|
301
|
-
# below the top level is ever mutated in place. Re-deriving the same
|
|
302
|
-
# answer through the codec and through re-hydration is therefore
|
|
303
|
-
# pure, avoidable cost for this one caller — never a correctness
|
|
304
|
-
# requirement.
|
|
305
|
-
#
|
|
306
|
-
# `bootstrap_fast_path?` is the guard, and it is deliberately cheap —
|
|
307
|
-
# O(this aggregate's own declared attribute count), never O(N) — so
|
|
308
|
-
# it cannot reintroduce the very cost it exists to avoid: `StateCodec.
|
|
309
|
-
# decoded?` (the obvious-looking alternative) recurses into every
|
|
310
|
-
# `list_of` element to check it, which is exactly the O(N) walk this
|
|
311
|
-
# whole change removes. A `Hash` with every top-level key already a
|
|
312
|
-
# `Symbol` is what `Instance#state` always looks like (`Value.hydrate`
|
|
313
|
-
# refuses anything else, coercion.rb's own header), so it is checked
|
|
314
|
-
# here instead — true for the actual shape every real save has,
|
|
315
|
-
# false (falling back to the always-correct slow path) for anything
|
|
316
|
-
# that somehow doesn't.
|
|
317
|
-
#
|
|
318
|
-
# Verified empirically before landing, not just argued: instrumenting
|
|
319
|
-
# every bootstrapping-time save across a full `grammar_registry` boot
|
|
320
|
-
# and comparing this fast path's own state, journal entry, and stored
|
|
321
|
-
# `Instance` against the unmodified `StateCodec.copy` + `Instance.new
|
|
322
|
-
# (hydrate: true)` pipeline's found them equal (`==`) for every one of
|
|
323
|
-
# 6,617 saves, zero mismatches.
|
|
236
|
+
# Skips the codec copy and rehydration round trip while judging the
|
|
237
|
+
# self-hosted grammar (S17, ADR 0026), where re-validating already-valid
|
|
238
|
+
# entity state costs O(N) per save and O(N^2) across an N-row table.
|
|
239
|
+
# Safe only here: `entry.state` is already hydrated, validated, deep-
|
|
240
|
+
# frozen state from the same Value machinery the skipped paths rerun.
|
|
241
|
+
# The check itself must stay O(this aggregate's attribute count), not
|
|
242
|
+
# O(N), so it can't reintroduce the cost it exists to avoid.
|
|
324
243
|
def bootstrap_fast_path?(state)
|
|
325
244
|
Runtime::Value.judge_bootstrapping? && state.is_a?(Hash) && state.keys.all?(Symbol)
|
|
326
245
|
end
|
|
@@ -2,20 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
|
-
# A
|
|
6
|
-
#
|
|
7
|
-
# adapter a project pairs it with by shape alone
|
|
8
|
-
# (`create_session(event:, registration_id:, success_url:, cancel_url:)
|
|
9
|
-
# -> a URL string`), the same way `Memory` stands in for a real
|
|
10
|
-
# persistence adapter without knowing anything about the domain bound
|
|
11
|
-
# to it. Never reads `event`'s own fields — a real adapter needs the
|
|
12
|
-
# price/name inside it to build a session a payer actually sees;
|
|
13
|
-
# this only needs to look enough like one to swap in.
|
|
14
|
-
#
|
|
15
|
-
# The URL it returns carries the same `registration_id` a real
|
|
16
|
-
# webhook's metadata would, so a caller building its own signed
|
|
17
|
-
# webhook against it (a smoke test, CI) has something real to key
|
|
18
|
-
# off of — the real shape production has, minus the network hop.
|
|
5
|
+
# A network-free stand-in for a Checkout-Session-style payment adapter, matching it by
|
|
6
|
+
# method shape alone. The returned URL carries `registration_id`, as webhook metadata would.
|
|
19
7
|
class MockStripeAdapter
|
|
20
8
|
# Accepts and discards the shared adapter constructor arguments; this adapter holds no
|
|
21
9
|
# state of its own.
|
|
@@ -29,14 +17,10 @@ module Hecks
|
|
|
29
17
|
# Builds a fake checkout URL carrying `registration_id`, standing in for a real
|
|
30
18
|
# Checkout-Session call.
|
|
31
19
|
#
|
|
32
|
-
# @param event [Object] the record data a real adapter would read
|
|
33
|
-
#
|
|
34
|
-
# operation supplies as `event`) and never read here
|
|
35
|
-
# @param registration_id [String] the registration id to key a signed webhook off of;
|
|
36
|
-
# embedded in the returned URL, never read otherwise
|
|
20
|
+
# @param event [Object] the record data a real adapter would read; never read here
|
|
21
|
+
# @param registration_id [String] embedded in the returned URL, never read otherwise
|
|
37
22
|
# @param success_url [String] the base URL to redirect a successful payer to
|
|
38
|
-
# @param cancel_url [String] accepted for the shared
|
|
39
|
-
# never read
|
|
23
|
+
# @param cancel_url [String] accepted for the shared call shape and never read
|
|
40
24
|
# @return [String] `success_url` with `mock_checkout=1` and `mock_registration_id`
|
|
41
25
|
# query parameters appended
|
|
42
26
|
def create_session(event:, registration_id:, success_url:, cancel_url:)
|
|
@@ -3,15 +3,8 @@ require_relative "../../../runtime/value"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class Postgres
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# shape as Sqlite::Codec (one column per attribute, JSON for
|
|
9
|
-
# nested/list) — not its mechanics. Sqlite's own columns carry no
|
|
10
|
-
# type affinity worth trusting on the way back out (SQLite gives it
|
|
11
|
-
# back typed anyway via the driver), but `pg` hands every column
|
|
12
|
-
# back as text unless a type map says otherwise, so a real `bigint`/
|
|
13
|
-
# `double precision` column needs its own coercion on decode that
|
|
14
|
-
# Sqlite never had to write.
|
|
6
|
+
# Encodes and decodes a state to and from its columns, coercing `pg`'s text back to
|
|
7
|
+
# numbers.
|
|
15
8
|
module Codec
|
|
16
9
|
private
|
|
17
10
|
|
|
@@ -23,9 +16,7 @@ module Hecks
|
|
|
23
16
|
fields << { name: lifecycle.field, attribute: nil, sql_type: "text" } if lifecycle && fields.none? do |field|
|
|
24
17
|
field[:name] == lifecycle.field
|
|
25
18
|
end
|
|
26
|
-
# `projects` fields
|
|
27
|
-
# Sqlite::Codec#persisted_fields' own comment; identical reasoning,
|
|
28
|
-
# `text` to match this file's own lowercase SQL type spelling.
|
|
19
|
+
# `projects` fields are local columns too (ADR 0025).
|
|
29
20
|
@aggregate.projected_fields.each do |field|
|
|
30
21
|
next if fields.any? { |f| f[:name] == field.name }
|
|
31
22
|
|
|
@@ -35,10 +26,7 @@ module Hecks
|
|
|
35
26
|
end
|
|
36
27
|
|
|
37
28
|
def encode(attr, value)
|
|
38
|
-
#
|
|
39
|
-
# comment: a list attribute nothing has ever appended to has to
|
|
40
|
-
# stay NULL to answer the same as Memory does, not become `[]`
|
|
41
|
-
# invented by this adapter's own storage.
|
|
29
|
+
# A never-set list stays NULL rather than becoming `[]`, matching Memory.
|
|
42
30
|
return (value.nil? ? nil : state_json(value)) if attr.list?
|
|
43
31
|
return state_json(value) if value.is_a?(Hash) || value.is_a?(Runtime::Value)
|
|
44
32
|
|
|
@@ -51,13 +39,9 @@ module Hecks
|
|
|
51
39
|
encode(field[:attribute], value)
|
|
52
40
|
end
|
|
53
41
|
|
|
54
|
-
# Every jsonb column's text goes through the state codec's `encode`
|
|
55
|
-
# (PR A3) — see Sqlite::Codec#state_json.
|
|
56
42
|
def state_json(value) = JSON.generate(Ports::Persistence::StateCodec.encode(@aggregate, value))
|
|
57
43
|
|
|
58
|
-
#
|
|
59
|
-
# (PR A3) — see Sqlite::Codec#decode, including why a NULL
|
|
60
|
-
# projected-only column reads back absent.
|
|
44
|
+
# A NULL projected-only column reads back absent.
|
|
61
45
|
def decode(row)
|
|
62
46
|
state = persisted_fields.each_with_object({}) do |field, raw_state|
|
|
63
47
|
attr = field[:attribute]
|
|
@@ -84,13 +68,7 @@ module Hecks
|
|
|
84
68
|
@aggregate.projected_fields.any? { |projected| projected.name.to_sym == field[:name].to_sym }
|
|
85
69
|
end
|
|
86
70
|
|
|
87
|
-
# `pg` returns every column as a
|
|
88
|
-
# map installed) — a `bigint`/`double precision` column has to be
|
|
89
|
-
# coerced back on the way out, unlike Sqlite3's own driver, which
|
|
90
|
-
# already types a row by the column's declared affinity. Mirrors
|
|
91
|
-
# SQL_TYPES' own mapping: whatever gets a real numeric column here
|
|
92
|
-
# gets converted back here, everything else (String, references,
|
|
93
|
-
# enums, ...) passes through as the text it already is.
|
|
71
|
+
# `pg` returns every column as a String, so numeric columns are coerced back.
|
|
94
72
|
def coerce_scalar(attr, raw)
|
|
95
73
|
return nil if raw.nil?
|
|
96
74
|
|
|
@@ -3,21 +3,12 @@ require_relative "../../../runtime/outbox"
|
|
|
3
3
|
|
|
4
4
|
module Hecks
|
|
5
5
|
module Adapters
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# `hecks_saga_instances` DDL is copied between them: nothing here is
|
|
9
|
-
# lineage-specific. Needs `@db` (a `PG::Connection`) and `table`
|
|
10
|
-
# (the aggregate's storage name) from the including class. See
|
|
11
|
-
# `Runtime::Outbox` for what the four verbs mean, and Sqlite's copy
|
|
12
|
-
# for the SQL idioms (`ON CONFLICT DO NOTHING` = idempotent enqueue,
|
|
13
|
-
# `WHERE status = 'pending'` = the compare-and-set claim).
|
|
6
|
+
# Outbox storage shared by `Postgres` and `PostgresEra`; needs `@db` and `table`.
|
|
7
|
+
# See `Runtime::Outbox` for the four verbs.
|
|
14
8
|
module PostgresOutbox
|
|
15
9
|
# Runs the block inside one Postgres transaction, joining an already-open one.
|
|
16
10
|
#
|
|
17
|
-
#
|
|
18
|
-
# transaction around save+emit+outbox and the adapter's own
|
|
19
|
-
# `append`/`atomic_put`/`delete` each open theirs; PG refuses
|
|
20
|
-
# BEGIN inside BEGIN, so an inner call joins the open one.
|
|
11
|
+
# Re-entrant: PG refuses BEGIN inside BEGIN, so an inner call joins the open one.
|
|
21
12
|
#
|
|
22
13
|
# @yield the writes to commit together; an exception raised inside rolls the
|
|
23
14
|
# outermost transaction back
|
|
@@ -1,28 +1,7 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Adapters
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
# nothing here is lineage-specific, and `PostgresOutbox`'s own
|
|
6
|
-
# `@db.exec*` calls route through this module's `pg_exec`/
|
|
7
|
-
# `pg_exec_params` too, since both classes mix it in. Needs `@db` (a
|
|
8
|
-
# `PG::Connection`), `@aggregate`, and `@settings` from the including
|
|
9
|
-
# class — the same three `self.class.connect_for(@aggregate.name,
|
|
10
|
-
# @settings)` already needs to build one in the first place.
|
|
11
|
-
#
|
|
12
|
-
# A backend killed out from under an adapter (a DBA's own
|
|
13
|
-
# `pg_terminate_backend`, a load balancer's failover, a restart) —
|
|
14
|
-
# chaos-tested against the plain `Postgres` adapter: `PG::ConnectionBad`
|
|
15
|
-
# on the query that hit it, and permanently on every query after,
|
|
16
|
-
# since nothing ever replaced `@db` with a live connection.
|
|
17
|
-
# `pg_exec`/`pg_exec_params` are the two primitives every other method
|
|
18
|
-
# in either class funnels through — wrapping them here, once,
|
|
19
|
-
# self-heals `@db` for the next caller. The current call still
|
|
20
|
-
# raises — reconnecting cannot tell a caller whether its own write
|
|
21
|
-
# reached the server before the connection died, so silently
|
|
22
|
-
# retrying it here could silently double it; that ambiguity is
|
|
23
|
-
# exactly why `Runtime::SagaInterpreter`'s own defect-retry exists
|
|
24
|
-
# one layer up, where a dispatch is retried as a whole (fresh
|
|
25
|
-
# hydrate, fresh `given`s), not as a lone SQL statement.
|
|
3
|
+
# Replaces a dead `@db` after `PG::ConnectionBad`; shared by `Postgres` and `PostgresEra`.
|
|
4
|
+
# Needs `@db`, `@aggregate` and `@settings`.
|
|
26
5
|
module PostgresReconnect
|
|
27
6
|
# Runs one parameterless statement, replacing a dead connection before re-raising.
|
|
28
7
|
#
|
|
@@ -56,13 +35,8 @@ module Hecks
|
|
|
56
35
|
|
|
57
36
|
private
|
|
58
37
|
|
|
59
|
-
#
|
|
60
|
-
# is
|
|
61
|
-
# unchanged; the `PG::ConnectionBad` already being re-raised by
|
|
62
|
-
# `pg_exec`/`pg_exec_params` above still reaches the caller either
|
|
63
|
-
# way, so swallowing a failed reconnect attempt here loses no
|
|
64
|
-
# information — it only avoids masking the original error with a
|
|
65
|
-
# second one.
|
|
38
|
+
# The failing call still raises and is never retried: it may have reached the server.
|
|
39
|
+
# A failed reconnect is swallowed so it does not mask the original error.
|
|
66
40
|
def reconnect!
|
|
67
41
|
@db = self.class.connect_for(@aggregate.name, @settings)
|
|
68
42
|
rescue PG::Error
|
|
@@ -3,14 +3,8 @@ require "digest"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Adapters
|
|
5
5
|
class Postgres
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# table beside it, the shared events/saga tables — and the automatic
|
|
9
|
-
# indexing this adapter carries that `PostgresEra` does not (see
|
|
10
|
-
# docs/implemented/postgres-era-adapter-split-plan.md, governing principle 3:
|
|
11
|
-
# every field a declared query filters or sorts on is safe to index
|
|
12
|
-
# directly here, since there is no `head_view` reduction to route
|
|
13
|
-
# around the way `PostgresEra`'s own cache-table story exists for).
|
|
6
|
+
# DDL for the aggregate, entry, event and saga tables, plus the automatic
|
|
7
|
+
# indexes derived from declared queries.
|
|
14
8
|
module SchemaBuilder
|
|
15
9
|
private
|
|
16
10
|
|
|
@@ -19,40 +13,15 @@ module Hecks
|
|
|
19
13
|
@db.exec(
|
|
20
14
|
"CREATE TABLE IF NOT EXISTS #{quoted_table} (id text PRIMARY KEY#{', ' unless columns.empty?}#{columns.join(', ')})"
|
|
21
15
|
)
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# column to an already-existing table (a committed database from
|
|
25
|
-
# before optimistic-concurrency CAS existed), so this runs
|
|
26
|
-
# unconditionally on every boot and no-ops once the column is
|
|
27
|
-
# there. Adapter bookkeeping only — never listed in
|
|
28
|
-
# `persisted_fields` (Codec), so it never appears in `decode`'s
|
|
29
|
-
# domain-state hash or `Instance#to_h`.
|
|
16
|
+
# CREATE TABLE IF NOT EXISTS never adds a column to an existing table, so
|
|
17
|
+
# the bookkeeping column is healed on every boot.
|
|
30
18
|
@db.exec("ALTER TABLE #{quoted_table} ADD COLUMN IF NOT EXISTS hecks_version bigint NOT NULL DEFAULT 1")
|
|
31
|
-
# Same
|
|
32
|
-
#
|
|
33
|
-
# column; a bluebook attribute added (or, via `translations/`,
|
|
34
|
-
# renamed) after this table already exists is not bookkeeping,
|
|
35
|
-
# but the identical gap applies: `CREATE TABLE IF NOT EXISTS`
|
|
36
|
-
# is a no-op against the existing table, so without this a new
|
|
37
|
-
# attribute boots clean and then dies `PG::UndefinedColumn` on
|
|
38
|
-
# the first `project` — discovered chaos-testing a live rename
|
|
39
|
-
# against this adapter (no era, no translation prompt; those
|
|
40
|
-
# live in `PostgresEra` — this is the plain adapter's own,
|
|
41
|
-
# simpler contract: "the table always has every column the
|
|
42
|
-
# bluebook currently declares"). No `NOT NULL`, no `DEFAULT` —
|
|
43
|
-
# existing rows get SQL NULL for a column they never had a
|
|
44
|
-
# value for, exactly what a fresh row would get for an unset
|
|
45
|
-
# optional attribute (`encode_field`'s own nil handling).
|
|
19
|
+
# Same healing for declared attributes: existing rows get NULL, as an unset
|
|
20
|
+
# optional attribute would.
|
|
46
21
|
persisted_fields.each do |field|
|
|
47
22
|
@db.exec("ALTER TABLE #{quoted_table} ADD COLUMN IF NOT EXISTS " \
|
|
48
23
|
"#{quote_ident(field[:name])} #{field[:sql_type]}")
|
|
49
24
|
end
|
|
50
|
-
# Right here, not a separate step in `Postgres#initialize` —
|
|
51
|
-
# same idiom Sqlite::SchemaBuilder's own `create_aggregate_table!`
|
|
52
|
-
# uses: index creation runs unconditionally, right after the
|
|
53
|
-
# table it indexes exists, the same self-healing shape
|
|
54
|
-
# `ensure_head_snapshot!`/`ensure_first_head!` already use on
|
|
55
|
-
# `PostgresEra`'s side.
|
|
56
25
|
ensure_indexes!
|
|
57
26
|
end
|
|
58
27
|
|
|
@@ -68,8 +37,6 @@ module Hecks
|
|
|
68
37
|
SQL
|
|
69
38
|
end
|
|
70
39
|
|
|
71
|
-
# Same DDL as `PostgresEra`'s own — not lineage-specific, copied
|
|
72
|
-
# verbatim (see docs/implemented/postgres-era-adapter-split-plan.md).
|
|
73
40
|
def create_event_table!
|
|
74
41
|
@db.exec(<<~SQL)
|
|
75
42
|
CREATE TABLE IF NOT EXISTS events (
|
|
@@ -81,10 +48,27 @@ module Hecks
|
|
|
81
48
|
occurred_at text
|
|
82
49
|
)
|
|
83
50
|
SQL
|
|
51
|
+
# Backs #events_for's per-record lookup (a `corrects` command's history read).
|
|
52
|
+
@db.exec(
|
|
53
|
+
"CREATE INDEX IF NOT EXISTS hecks_events_aggregate_id_idx ON events (aggregate, aggregate_id)"
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# One row per aggregate table, naming the highest entry `sequence` this table has
|
|
58
|
+
# already had projected into it — shared across every aggregate, since it is keyed
|
|
59
|
+
# by table name rather than declared per aggregate.
|
|
60
|
+
def create_checkpoint_table!
|
|
61
|
+
@db.exec(<<~SQL)
|
|
62
|
+
CREATE TABLE IF NOT EXISTS hecks_checkpoints (
|
|
63
|
+
aggregate_table text PRIMARY KEY,
|
|
64
|
+
last_sequence bigint NOT NULL DEFAULT 0
|
|
65
|
+
)
|
|
66
|
+
SQL
|
|
67
|
+
# CREATE TABLE IF NOT EXISTS never adds a column to an existing table, so
|
|
68
|
+
# the bookkeeping column is healed on every boot, same as create_aggregate_table!.
|
|
69
|
+
@db.exec("ALTER TABLE hecks_checkpoints ADD COLUMN IF NOT EXISTS compacted_through bigint NOT NULL DEFAULT 0")
|
|
84
70
|
end
|
|
85
71
|
|
|
86
|
-
# Same DDL as `PostgresEra`'s own — not lineage-specific, copied
|
|
87
|
-
# verbatim (see docs/implemented/postgres-era-adapter-split-plan.md).
|
|
88
72
|
def create_saga_table!
|
|
89
73
|
@db.exec(<<~SQL)
|
|
90
74
|
CREATE TABLE IF NOT EXISTS hecks_saga_instances (
|
|
@@ -98,11 +82,7 @@ module Hecks
|
|
|
98
82
|
PRIMARY KEY (domain, process_manager, correlation)
|
|
99
83
|
)
|
|
100
84
|
SQL
|
|
101
|
-
#
|
|
102
|
-
# this same domain already created before this column existed
|
|
103
|
-
# — the same idiom `rust/host/src/journal.rs`'s own
|
|
104
|
-
# `sagas_backfilled` column addition already uses, for the
|
|
105
|
-
# identical reason.
|
|
85
|
+
# Heals a table created before this column existed.
|
|
106
86
|
@db.exec("ALTER TABLE hecks_saga_instances ADD COLUMN IF NOT EXISTS completed_compensations jsonb " \
|
|
107
87
|
"NOT NULL DEFAULT '[]'::jsonb")
|
|
108
88
|
end
|
|
@@ -113,24 +93,8 @@ module Hecks
|
|
|
113
93
|
SQL_TYPES.fetch(attr.type, "text")
|
|
114
94
|
end
|
|
115
95
|
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
# Derived from every declared query's `where`/`order_by` fields —
|
|
119
|
-
# no bluebook author opts in, matching every other self-healing
|
|
120
|
-
# schema move this adapter makes. Walks the aggregate's own
|
|
121
|
-
# queries and each entity's own queries — the same enumeration
|
|
122
|
-
# `dsl/aggregate_builder.rb`'s own `query_surfaces` walks at
|
|
123
|
-
# declaration-seal time — but an entity's own fields never
|
|
124
|
-
# produce an index below: an entity has no table of its own, its
|
|
125
|
-
# rows live inside a list-typed attribute on the aggregate's own
|
|
126
|
-
# table (`QueryInterpreter#entity_rows` resolves it that way, and
|
|
127
|
-
# answers an entity query entirely in memory over `repository.all`
|
|
128
|
-
# — it never reaches this adapter's own `query` method at all).
|
|
129
|
-
# Every list-typed attribute is excluded below regardless (see
|
|
130
|
-
# `index_field!`), so an entity's own field would always resolve
|
|
131
|
-
# to "skip" anyway. Walked explicitly rather than silently
|
|
132
|
-
# dropped, so a reader can see entities were considered here, not
|
|
133
|
-
# forgotten.
|
|
96
|
+
# Indexes every field a declared query filters or sorts on. Entity queries are
|
|
97
|
+
# skipped: entities have no table, and their queries run in memory.
|
|
134
98
|
def ensure_indexes!
|
|
135
99
|
query_surfaces.each do |owner, queries|
|
|
136
100
|
next unless owner.equal?(@aggregate)
|
|
@@ -149,50 +113,18 @@ module Hecks
|
|
|
149
113
|
index_field!(query.order_by.field) if query.order_by
|
|
150
114
|
end
|
|
151
115
|
|
|
152
|
-
# A
|
|
153
|
-
#
|
|
154
|
-
# FROM jsonb_array_elements(...) ...)` (see `list_contains_clause`
|
|
155
|
-
# below), a SQL shape neither a plain btree on the raw jsonb
|
|
156
|
-
# column nor even a GIN jsonb index (`@>`, `?`) accelerates —
|
|
157
|
-
# Postgres's own GIN jsonb operators match a different SQL shape
|
|
158
|
-
# than the `EXISTS` + `jsonb_array_elements` this codebase always
|
|
159
|
-
# compiles a list `contains` to. An index here would be dead
|
|
160
|
-
# weight, not free correctness, so none is attempted.
|
|
116
|
+
# A list attribute is never indexed: `contains` compiles to EXISTS over
|
|
117
|
+
# jsonb_array_elements, which neither a btree nor a GIN jsonb index accelerates.
|
|
161
118
|
def index_field!(field)
|
|
162
|
-
# A hop path ("owner/field"
|
|
163
|
-
#
|
|
164
|
-
# `aggregate_builder.rb`'s own `field.to_s.include?("/")`
|
|
165
|
-
# checks) is never indexed here — same reasoning as `list_of`
|
|
166
|
-
# below: `query_expression`/`nested_expression` below have no
|
|
167
|
-
# dialect for this shape at all (they'd compile the whole
|
|
168
|
-
# "owner/field" string as one bare column/jsonb-path segment,
|
|
169
|
-
# which is not a real column and errors PG::UndefinedColumn on
|
|
170
|
-
# `CREATE INDEX`, discovered fuzzing a real Postgres boot of
|
|
171
|
-
# `examples/banking` — Account's own `projects :customer_status,
|
|
172
|
-
# from: :"customer.status"` compiles to exactly this shape).
|
|
173
|
-
# Skipping the index is always safe, the same way skipping a
|
|
174
|
-
# `list_of` index is: an index is a perf optimization, not
|
|
175
|
-
# correctness, so "never built" beats "built wrong." Whether a
|
|
176
|
-
# hop-path field can be queried at all against this adapter is
|
|
177
|
-
# a separate, still-open question this skip does not answer —
|
|
178
|
-
# see docs/future-features.md's fuzzer-adapter entry, and
|
|
179
|
-
# docs/1.0-readiness.md item 2 for this gap's other two
|
|
180
|
-
# independent failure points (Postgres querying, Rust codegen's
|
|
181
|
-
# `OpenForSuspendedCustomers` — bin/rust_coverage's own
|
|
182
|
-
# allowlist) — tracked together, not as three unrelated bugs.
|
|
119
|
+
# A hop path ("owner/field") has no column or jsonb path to index; it would
|
|
120
|
+
# fail CREATE INDEX with PG::UndefinedColumn.
|
|
183
121
|
return if field.to_s.include?("/")
|
|
184
122
|
|
|
185
123
|
name, *_path = field.to_s.split(".")
|
|
186
124
|
attribute = @aggregate.attribute(name)
|
|
187
125
|
return if attribute&.list?
|
|
188
126
|
|
|
189
|
-
#
|
|
190
|
-
# `query_expression`/`plain_column`, the real dialect methods,
|
|
191
|
-
# rather than a second, hand-rolled derivation of the same
|
|
192
|
-
# path that could silently drift from it. An index whose
|
|
193
|
-
# expression doesn't match the planner's own candidate
|
|
194
|
-
# expression byte-for-byte is invisible to the planner,
|
|
195
|
-
# created or not.
|
|
127
|
+
# Built from the query's own expression so the planner can match it byte for byte.
|
|
196
128
|
expression = query_expression(field.to_s)
|
|
197
129
|
if expression == plain_column(name)
|
|
198
130
|
create_plain_index!(name)
|
|
@@ -211,13 +143,7 @@ module Hecks
|
|
|
211
143
|
@db.exec("CREATE INDEX IF NOT EXISTS #{quote_ident(name)} ON #{quoted_table} ((#{expression}))")
|
|
212
144
|
end
|
|
213
145
|
|
|
214
|
-
#
|
|
215
|
-
# together keeps every generated index name well under that no
|
|
216
|
-
# matter how long an aggregate or attribute name gets, and keeps
|
|
217
|
-
# two distinct fields — on the same table or different ones —
|
|
218
|
-
# from ever colliding, or truncating into the same name the way
|
|
219
|
-
# a naive `"idx_#{table}_#{field}"` could once either got long
|
|
220
|
-
# enough.
|
|
146
|
+
# Hashed so the name stays under Postgres's 63-byte identifier limit and never collides.
|
|
221
147
|
def index_name(field)
|
|
222
148
|
"hecks_idx_#{Digest::SHA256.hexdigest("#{table}:#{field}")[0, 40]}"
|
|
223
149
|
end
|