hecks 2.7.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 +11 -0
- 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 +18 -99
- 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 +15 -37
- 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 +32 -142
- 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 +17 -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 +38 -26
- 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 +17 -65
- 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 -189
- 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 -106
- 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 +54 -268
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -308
- 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 +10 -91
- data/lib/hecks/bluebook/model_check.rb +102 -600
- 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 +84 -0
- data/lib/hecks/cli/document.rb +86 -0
- data/lib/hecks/cli/ir.rb +45 -0
- data/lib/hecks/cli/mcp.rb +31 -0
- data/lib/hecks/cli/mcp_door.rb +366 -0
- data/lib/hecks/cli/model_check.rb +203 -0
- data/lib/hecks/cli/project_cli.rb +90 -0
- data/lib/hecks/cli/project_diagrams.rb +36 -0
- data/lib/hecks/cli/run.rb +173 -0
- data/lib/hecks/cli/smoke_test.rb +45 -0
- data/lib/hecks/cli/stores.rb +53 -0
- data/lib/hecks/cli.rb +160 -0
- 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 +98 -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 +95 -0
- 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 +62 -65
- 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 +7 -26
- 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 +22 -122
- 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 +151 -679
- 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 +9 -16
- 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 +46 -138
- 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 +93 -0
- data/lib/hecks/release/runner/commands.rb +66 -0
- data/lib/hecks/release/runner/console.rb +52 -0
- data/lib/hecks/release/runner/gem_publisher.rb +43 -0
- data/lib/hecks/release/runner/git.rb +48 -0
- data/lib/hecks/release/runner/npm_publisher.rb +75 -0
- data/lib/hecks/release/runner/preflight.rb +104 -0
- data/lib/hecks/release/runner/published.rb +55 -0
- data/lib/hecks/release/runner/tagger.rb +80 -0
- data/lib/hecks/release/runner.rb +215 -0
- 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 +125 -307
- 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 +65 -220
- 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 +139 -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 +103 -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 +146 -567
- 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/vocabulary.rb +5 -0
- data/lib/hecks.rb +32 -82
- 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 +30 -59
- data/lib/hecks/bench/cli.rb +0 -74
- data/lib/hecks/bench/environment.rb +0 -100
- data/lib/hecks/bench/postgres_probe.rb +0 -41
- data/lib/hecks/bench/report.rb +0 -116
- data/lib/hecks/bench/ruby_runner.rb +0 -151
- data/lib/hecks/bench/run.rb +0 -55
- data/lib/hecks/bench/rust_runner.rb +0 -165
- data/lib/hecks/bench/stats.rb +0 -75
- data/lib/hecks/bench/suite.rb +0 -172
- data/lib/hecks/bench/workload.rb +0 -137
- data/lib/hecks/bench.rb +0 -28
- data/lib/hecks/codemod.rb +0 -443
- data/lib/hecks/corpus.rb +0 -479
- data/lib/hecks/doc/reference.rb +0 -585
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +0 -684
- data/lib/hecks/fuzzing/combination_miner.rb +0 -178
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +0 -469
- data/lib/hecks/fuzzing/coverage_campaign.rb +0 -161
- data/lib/hecks/fuzzing/differential.rb +0 -192
- data/lib/hecks/fuzzing/domain_generator.rb +0 -871
- data/lib/hecks/fuzzing/era_boundary.rb +0 -154
- data/lib/hecks/fuzzing/form_census.rb +0 -285
- data/lib/hecks/fuzzing/generated_domain_check.rb +0 -171
- data/lib/hecks/fuzzing/invalid_value_generator.rb +0 -125
- data/lib/hecks/fuzzing/isolated_boot.rb +0 -658
- data/lib/hecks/fuzzing/nondeterministic.rb +0 -79
- data/lib/hecks/fuzzing/persistence_parity.rb +0 -253
- data/lib/hecks/fuzzing/properties/corrections.rb +0 -125
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +0 -688
- data/lib/hecks/fuzzing/properties/guards.rb +0 -398
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +0 -399
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +0 -137
- data/lib/hecks/fuzzing/properties/outbox.rb +0 -180
- data/lib/hecks/fuzzing/properties/querying.rb +0 -260
- data/lib/hecks/fuzzing/properties.rb +0 -267
- data/lib/hecks/fuzzing/qa_settings.rb +0 -164
- data/lib/hecks/fuzzing/replay.rb +0 -930
- data/lib/hecks/fuzzing/rotation_priority.rb +0 -114
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +0 -139
- data/lib/hecks/fuzzing/self_consistency.rb +0 -825
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +0 -532
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +0 -171
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +0 -127
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +0 -116
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +0 -138
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +0 -344
- data/lib/hecks/fuzzing/sequence_generator.rb +0 -311
- data/lib/hecks/fuzzing/shrinker.rb +0 -309
- data/lib/hecks/fuzzing/structural_skips.rb +0 -53
- data/lib/hecks/fuzzing/sweep_depth.rb +0 -61
- data/lib/hecks/fuzzing/target_capabilities.rb +0 -255
- data/lib/hecks/fuzzing/value_generator.rb +0 -340
- data/lib/hecks/fuzzing.rb +0 -23
- data/lib/hecks/grammar/evolve.rb +0 -524
- data/lib/hecks/query_ir.rb +0 -482
|
@@ -15,38 +15,8 @@ require_relative "../../runtime/instance"
|
|
|
15
15
|
|
|
16
16
|
module Hecks
|
|
17
17
|
module Adapters
|
|
18
|
-
# The plain Postgres store
|
|
19
|
-
#
|
|
20
|
-
# exactly the shape `Sqlite` already made for its own table. Sibling
|
|
21
|
-
# to `PostgresEra` (postgres_era.rb), which is the same database with
|
|
22
|
-
# full lineage/era machinery on top — pick this one unless a domain
|
|
23
|
-
# actually needs to survive a live shape change. See
|
|
24
|
-
# docs/implemented/postgres-era-adapter-split-plan.md for why the two are split.
|
|
25
|
-
#
|
|
26
|
-
# ## What it is not
|
|
27
|
-
#
|
|
28
|
-
# No `hecks_eras`, no lineage, no advisory-lock-per-write for era
|
|
29
|
-
# tracking, no `lineage_capable?`/`era_check!` — this class simply
|
|
30
|
-
# doesn't define those methods at all, and the capability idiom
|
|
31
|
-
# elsewhere already treats their absence as "not lineage-capable".
|
|
32
|
-
#
|
|
33
|
-
# ## Storage shape
|
|
34
|
-
#
|
|
35
|
-
# See postgres/schema_builder.rb for the DDL, postgres/codec.rb for the
|
|
36
|
-
# encode/decode.
|
|
37
|
-
#
|
|
38
|
-
# - One real column per attribute, typed for a scalar
|
|
39
|
-
# (`SQL_TYPES`, `text` default), `jsonb` for a nested (value-object)
|
|
40
|
-
# or list-typed attribute — never JSON-in-text the way `Sqlite` has
|
|
41
|
-
# to, since Postgres has a native jsonb type.
|
|
42
|
-
# - `append` and `project` are two real Postgres statements — `save`/
|
|
43
|
-
# `delete` wrap them in one transaction, same "the journal insert
|
|
44
|
-
# and the snapshot stay atomic" reasoning `PostgresEra#append`'s own
|
|
45
|
-
# comment gives: a crash between the two must never leave a
|
|
46
|
-
# half-written state. `append`/`project` stay plain, individually-
|
|
47
|
-
# callable methods (never wrapping their own transaction) so
|
|
48
|
-
# `AppendOnly#recover!`'s replay — `project` alone, no `append` —
|
|
49
|
-
# keeps working the same way it does on every other adapter.
|
|
18
|
+
# The plain Postgres store: one table per aggregate, real typed columns
|
|
19
|
+
# for scalars, jsonb for nested/list attributes. See `PostgresEra` for lineage/era support.
|
|
50
20
|
class Postgres
|
|
51
21
|
include SqlQueryBuilder
|
|
52
22
|
include SchemaBuilder
|
|
@@ -94,11 +64,8 @@ module Hecks
|
|
|
94
64
|
PG.connect(dbname: declared)
|
|
95
65
|
end
|
|
96
66
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
# with other domains, so every unqualified reference this
|
|
100
|
-
# adapter constructs resolves through search_path. A domain with
|
|
101
|
-
# no `schema` setting keeps Postgres's own default (public).
|
|
67
|
+
# A domain with `schema` set shares this Postgres instance, so every
|
|
68
|
+
# unqualified reference resolves through search_path.
|
|
102
69
|
schema = settings.key?(:schema) ? settings[:schema] : settings["schema"]
|
|
103
70
|
connection.exec("SET search_path TO #{connection.quote_ident(schema)}") if schema.to_s != ""
|
|
104
71
|
|
|
@@ -127,10 +94,8 @@ module Hecks
|
|
|
127
94
|
@aggregate = aggregate
|
|
128
95
|
@settings = settings
|
|
129
96
|
@db = self.class.connect_for(aggregate.name, settings)
|
|
130
|
-
#
|
|
131
|
-
#
|
|
132
|
-
# directly-instantiated adapter (specs), same fallback shape
|
|
133
|
-
# Sqlite's own @domain already uses.
|
|
97
|
+
# Scopes saga rows; falls back to the aggregate's storage name when
|
|
98
|
+
# settings gives no domain (e.g. a directly-instantiated adapter).
|
|
134
99
|
@domain = (
|
|
135
100
|
if settings.key?(:domain)
|
|
136
101
|
settings[:domain]
|
|
@@ -146,6 +111,7 @@ module Hecks
|
|
|
146
111
|
create_event_table!
|
|
147
112
|
create_saga_table!
|
|
148
113
|
create_outbox_table!
|
|
114
|
+
create_checkpoint_table!
|
|
149
115
|
end
|
|
150
116
|
|
|
151
117
|
# Names the aggregate's table; the journal table and outbox rows are keyed off it.
|
|
@@ -169,9 +135,7 @@ module Hecks
|
|
|
169
135
|
|
|
170
136
|
# Lists every stored record, ordered by id unless an ordering attribute is given.
|
|
171
137
|
#
|
|
172
|
-
# order_by is a runtime value
|
|
173
|
-
# whitelisted the identical way before it ever reaches
|
|
174
|
-
# order_expression.
|
|
138
|
+
# order_by is a runtime value, whitelisted the same way Sqlite#all does.
|
|
175
139
|
#
|
|
176
140
|
# @param order_by [String, Symbol, nil] attribute (or dotted value-object path) to sort
|
|
177
141
|
# by, with id as the tie-break; nil orders by id alone
|
|
@@ -205,54 +169,39 @@ module Hecks
|
|
|
205
169
|
|
|
206
170
|
# Inserts one journal row, outside any transaction of its own.
|
|
207
171
|
#
|
|
172
|
+
# Stamps the row's assigned `sequence` onto `entry` in place, so a `project` call right
|
|
173
|
+
# after (same object, same transaction) can advance the checkpoint with no extra query.
|
|
174
|
+
#
|
|
208
175
|
# @param entry [Ports::Persistence::Entry] the save or delete to journal; `state` is
|
|
209
176
|
# encoded through the state codec and `mirrors` stored as JSON, or NULL when nil
|
|
210
|
-
# @return [Ports::Persistence::Entry] the same `entry`
|
|
177
|
+
# @return [Ports::Persistence::Entry] the same `entry`, `sequence` now set
|
|
211
178
|
# @raise [PG::Error] if the insert fails
|
|
212
179
|
def append(entry)
|
|
213
|
-
pg_exec_params(
|
|
214
|
-
"INSERT INTO #{quoted_entry_table} (aggregate_id, operation, state, mirrors) VALUES ($1, $2, $3, $4)"
|
|
215
|
-
|
|
216
|
-
# mirrors hash must bind
|
|
217
|
-
#
|
|
218
|
-
# check against it would never match. Same guard `sqlite.rb`/
|
|
219
|
-
# `d1.rb`/`postgres_era.rb` already use for their own journal's
|
|
220
|
-
# `mirrors` column.
|
|
180
|
+
result = pg_exec_params(
|
|
181
|
+
"INSERT INTO #{quoted_entry_table} (aggregate_id, operation, state, mirrors) VALUES ($1, $2, $3, $4) " \
|
|
182
|
+
"RETURNING sequence",
|
|
183
|
+
# `mirrors` is nullable; an absent hash must bind SQL NULL, not the
|
|
184
|
+
# string "null", or a future IS NULL check would never match.
|
|
221
185
|
[entry.id, entry.operation, state_json(entry.state), entry.mirrors && JSON.generate(entry.mirrors)]
|
|
222
186
|
)
|
|
187
|
+
entry.sequence = result[0]["sequence"].to_i
|
|
223
188
|
entry
|
|
224
189
|
end
|
|
225
190
|
|
|
226
191
|
# Upserts or deletes the aggregate's row for one journal entry, bumping its version.
|
|
227
192
|
#
|
|
228
|
-
# `expected_version
|
|
229
|
-
# `persistence_capabilities`/`Ports::Persistence::AppendOnly#save`).
|
|
230
|
-
# `hecks_version` is adapter bookkeeping — never in `persisted_fields`
|
|
231
|
-
# (Codec), so it never reaches `decode`'s domain-state hash. It goes
|
|
232
|
-
# in the INSERT column list at `1` (a genuinely new row) and bumps by
|
|
233
|
-
# one in the `ON CONFLICT DO UPDATE` branch; when `expected_version`
|
|
234
|
-
# is given, that update branch additionally requires
|
|
235
|
-
# `hecks_version = expected_version` to apply at all — Postgres's own
|
|
236
|
-
# `INSERT ... ON CONFLICT DO UPDATE ... WHERE`, which gates only
|
|
237
|
-
# whether the conflict branch's update applies. A genuinely new row
|
|
238
|
-
# never reaches that branch at all, so it always inserts regardless
|
|
239
|
-
# of this where. `RETURNING hecks_version` plus `ntuples.zero?` is
|
|
240
|
-
# how a real version mismatch is told apart from an ordinary write:
|
|
241
|
-
# zero rows back means the conflict branch's where excluded the row
|
|
242
|
-
# entirely — the version had already moved — so `nil` is returned
|
|
243
|
-
# for the caller (`AppendOnly#save`) to treat as "stale, no-op".
|
|
193
|
+
# `expected_version` requests optimistic-concurrency CAS; nil back means it didn't match.
|
|
244
194
|
#
|
|
245
195
|
# @param entry [Ports::Persistence::Entry] the save or delete to materialize
|
|
246
196
|
# @param expected_version [Integer, nil] the `hecks_version` the row must still hold for
|
|
247
197
|
# an update to apply; nil writes unconditionally. Ignored for a delete
|
|
248
|
-
# @return [Runtime::Instance, PG::Result, nil]
|
|
249
|
-
#
|
|
250
|
-
# `expected_version` no longer matched and nothing was written; for a delete, the
|
|
251
|
-
# `DELETE` statement's `PG::Result`
|
|
198
|
+
# @return [Runtime::Instance, PG::Result, nil] a new instance with `version` set, nil on
|
|
199
|
+
# CAS mismatch, or the `DELETE` statement's `PG::Result`
|
|
252
200
|
# @raise [PG::Error] if the statement fails
|
|
253
201
|
# rubocop:disable Metrics/AbcSize -- the CAS/plain upsert split is one
|
|
254
202
|
# protocol; splitting it would hide the version handshake.
|
|
255
203
|
def project(entry, expected_version: nil)
|
|
204
|
+
advance_checkpoint!(entry.sequence) if entry.sequence
|
|
256
205
|
return pg_exec_params("DELETE FROM #{quoted_table} WHERE id = $1", [entry.id]) if entry.delete?
|
|
257
206
|
|
|
258
207
|
instance = Runtime::Instance.new(aggregate: @aggregate, id: entry.id, state: entry.state)
|
|
@@ -286,15 +235,66 @@ module Hecks
|
|
|
286
235
|
# @raise [PG::Error] if the statement fails
|
|
287
236
|
# @raise [JSON::ParserError] if a stored `state` or `mirrors` value is not valid JSON
|
|
288
237
|
def entries
|
|
289
|
-
|
|
290
|
-
state
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
238
|
+
entries_matching(
|
|
239
|
+
"SELECT aggregate_id, operation, state, mirrors, sequence FROM #{quoted_entry_table} ORDER BY sequence"
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Reads only the journal rows past a given `sequence`, for `AppendOnly#recover!` to replay
|
|
244
|
+
# after a checkpoint instead of the whole journal.
|
|
245
|
+
#
|
|
246
|
+
# @param sequence [Integer] the highest `sequence` already projected; rows at or below it
|
|
247
|
+
# are skipped
|
|
248
|
+
# @return [Array<Ports::Persistence::Entry>] the journalled entries past `sequence`, in
|
|
249
|
+
# append order, decoded exactly as `entries` decodes them; `[]` when none are newer
|
|
250
|
+
# @raise [PG::Error] if the statement fails
|
|
251
|
+
# @raise [JSON::ParserError] if a stored `state` or `mirrors` value is not valid JSON
|
|
252
|
+
def entries_since(sequence)
|
|
253
|
+
entries_matching(
|
|
254
|
+
"SELECT aggregate_id, operation, state, mirrors, sequence FROM #{quoted_entry_table} " \
|
|
255
|
+
"WHERE sequence > $1 ORDER BY sequence",
|
|
256
|
+
[sequence]
|
|
257
|
+
)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# Reads the highest journal `sequence` this table has already had projected into it.
|
|
261
|
+
#
|
|
262
|
+
# @return [Integer] `0` when the table has never been checkpointed (a fresh table, or one
|
|
263
|
+
# from before this bookkeeping existed) — `entries_since(0)` then reads the whole journal,
|
|
264
|
+
# matching `entries`' own full replay
|
|
265
|
+
# @raise [PG::Error] if the statement fails
|
|
266
|
+
def checkpoint
|
|
267
|
+
result = pg_exec_params("SELECT last_sequence FROM hecks_checkpoints WHERE aggregate_table = $1", [table])
|
|
268
|
+
result.ntuples.zero? ? 0 : result[0]["last_sequence"].to_i
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Reads the highest journal `sequence` compaction has already deleted from this table.
|
|
272
|
+
#
|
|
273
|
+
# @return [Integer] `0` when nothing has ever been compacted
|
|
274
|
+
# @raise [PG::Error] if the statement fails
|
|
275
|
+
def compacted_through
|
|
276
|
+
result = pg_exec_params("SELECT compacted_through FROM hecks_checkpoints WHERE aggregate_table = $1", [table])
|
|
277
|
+
result.ntuples.zero? ? 0 : result[0]["compacted_through"].to_i
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Deletes every journal row at or before `through` and records it, so a `:strict`
|
|
281
|
+
# projection catch-up can tell whether it still has the history it needs.
|
|
282
|
+
#
|
|
283
|
+
# Never moves `compacted_through` backwards, and never touches the aggregate's own
|
|
284
|
+
# table — only the journal, which a `:refresh` projection rebuild never needs.
|
|
285
|
+
#
|
|
286
|
+
# @param through [Integer] the highest `sequence` to delete
|
|
287
|
+
# @return [Integer] the number of journal rows deleted
|
|
288
|
+
# @raise [PG::Error] if a statement fails
|
|
289
|
+
def compact_entries!(through:)
|
|
290
|
+
removed = pg_exec_params("DELETE FROM #{quoted_entry_table} WHERE sequence <= $1", [through]).cmd_tuples
|
|
291
|
+
pg_exec_params(
|
|
292
|
+
"INSERT INTO hecks_checkpoints (aggregate_table, compacted_through) VALUES ($1, $2) " \
|
|
293
|
+
"ON CONFLICT (aggregate_table) DO UPDATE SET " \
|
|
294
|
+
"compacted_through = GREATEST(hecks_checkpoints.compacted_through, excluded.compacted_through)",
|
|
295
|
+
[table, through]
|
|
296
|
+
)
|
|
297
|
+
removed
|
|
298
298
|
end
|
|
299
299
|
|
|
300
300
|
# Deletes every row of the aggregate's table and its journal; events, saga rows and
|
|
@@ -310,13 +310,8 @@ module Hecks
|
|
|
310
310
|
|
|
311
311
|
# Journals and upserts an instance's current state atomically.
|
|
312
312
|
#
|
|
313
|
-
# One transaction,
|
|
314
|
-
#
|
|
315
|
-
# real Postgres ACID atomicity is sitting right there, so a crash
|
|
316
|
-
# between the journal insert and the table upsert must not leave
|
|
317
|
-
# the two disagreeing. `append`/`project` themselves stay plain,
|
|
318
|
-
# transaction-free methods (see the class comment above) — the
|
|
319
|
-
# transaction lives here, the one caller that runs both together.
|
|
313
|
+
# One transaction, so a crash between the journal insert and the row upsert
|
|
314
|
+
# can never leave the two disagreeing.
|
|
320
315
|
#
|
|
321
316
|
# @param instance [Runtime::Instance] the instance to store
|
|
322
317
|
# @return [Runtime::Instance] a new instance over the saved state, `version` set to the
|
|
@@ -405,13 +400,32 @@ module Hecks
|
|
|
405
400
|
end
|
|
406
401
|
end
|
|
407
402
|
|
|
403
|
+
# Reads back one record's recorded events, oldest first — pushed down as a `WHERE`
|
|
404
|
+
# clause (`hecks_events_aggregate_id_idx`) instead of filtering `#events`'s whole-table
|
|
405
|
+
# read, since a `corrects` command's history lookup only ever needs this one record.
|
|
406
|
+
#
|
|
407
|
+
# @param aggregate [String] the `"domain::AggregateName"` key events are stored under
|
|
408
|
+
# @param id [String, Object] the record's identity, matched as `id.to_s`
|
|
409
|
+
# @return [Array<Runtime::Event>] the record's stored events; `[]` when it has none
|
|
410
|
+
# @raise [PG::Error] if the statement fails
|
|
411
|
+
def events_for(aggregate:, id:)
|
|
412
|
+
pg_exec_params(
|
|
413
|
+
"SELECT * FROM events WHERE aggregate = $1 AND aggregate_id = $2 ORDER BY id",
|
|
414
|
+
[aggregate, id.to_s]
|
|
415
|
+
).map do |row|
|
|
416
|
+
Runtime::Event.new(
|
|
417
|
+
name: row["name"],
|
|
418
|
+
aggregate: row["aggregate"],
|
|
419
|
+
id: row["aggregate_id"],
|
|
420
|
+
payload: JSON.parse(row["payload"], symbolize_names: true),
|
|
421
|
+
occurred_at: row["occurred_at"]
|
|
422
|
+
)
|
|
423
|
+
end
|
|
424
|
+
end
|
|
425
|
+
|
|
408
426
|
# Upserts one saga instance's checkpoint, keyed by domain, process manager and
|
|
409
427
|
# correlation.
|
|
410
428
|
#
|
|
411
|
-
# ── the optional saga-persistence capability (§2) — same DDL and
|
|
412
|
-
# shape as PostgresEra's own (postgres_era.rb), not lineage-
|
|
413
|
-
# specific, copied verbatim.
|
|
414
|
-
#
|
|
415
429
|
# @param process_manager [String, Symbol] the process manager's name
|
|
416
430
|
# @param correlation [String, Object] the instance's correlation value, stored as
|
|
417
431
|
# `correlation.to_s`
|
|
@@ -475,8 +489,6 @@ module Hecks
|
|
|
475
489
|
|
|
476
490
|
private
|
|
477
491
|
|
|
478
|
-
# ── SqlQueryBuilder's dialect hooks ─────────────────────────────
|
|
479
|
-
|
|
480
492
|
def select_list = "*"
|
|
481
493
|
def from_relation = quoted_table
|
|
482
494
|
def dialect_name = "Postgres"
|
|
@@ -491,11 +503,8 @@ module Hecks
|
|
|
491
503
|
"position(#{placeholder} in #{expression}) > 0"
|
|
492
504
|
end
|
|
493
505
|
|
|
494
|
-
#
|
|
495
|
-
#
|
|
496
|
-
# own version does, since every attribute there shares one `state`
|
|
497
|
-
# column). Here, `column` names a real column of its own, already
|
|
498
|
-
# jsonb, already the array.
|
|
506
|
+
# `column` is already a real jsonb column and already the array — no shared
|
|
507
|
+
# `state` blob to walk into first, unlike PostgresEra's own version.
|
|
499
508
|
def list_contains_clause(column, member, placeholder)
|
|
500
509
|
target = member.empty? ? "elem #>> '{}'" : "elem ->> #{text_literal(member)}"
|
|
501
510
|
elements = "jsonb_array_elements(#{quote_ident(column)}) AS elem"
|
|
@@ -504,28 +513,15 @@ module Hecks
|
|
|
504
513
|
|
|
505
514
|
def plain_column(name) = quote_ident(name)
|
|
506
515
|
|
|
507
|
-
#
|
|
508
|
-
# shared `state`
|
|
509
|
-
# there. Here the attribute name is the column: the path into it
|
|
510
|
-
# is whatever is left after the column, never the column name
|
|
511
|
-
# repeated inside its own path.
|
|
516
|
+
# The attribute name is the column itself here, not part of the path (unlike
|
|
517
|
+
# PostgresEra's shared `state` blob) — the path is whatever follows the column.
|
|
512
518
|
def nested_expression(name, path, member)
|
|
513
519
|
segments = path.empty? ? [(member || "value").to_s] : path
|
|
514
520
|
jsonb_path(name, segments)
|
|
515
521
|
end
|
|
516
522
|
|
|
517
|
-
#
|
|
518
|
-
#
|
|
519
|
-
# no cast at all, unlike PostgresEra's shared jsonb `state` blob,
|
|
520
|
-
# where even a top-level scalar only ever comes out of `#>>` as
|
|
521
|
-
# text. A jsonb-extracted value (a value-object member reached
|
|
522
|
-
# through `nested_expression`/`jsonb_path` above) still comes out
|
|
523
|
-
# of `#>>` as text the exact same way PostgresEra's own does, and
|
|
524
|
-
# still needs the same `::numeric` cast to compare/sort
|
|
525
|
-
# numerically rather than lexicographically. `jsonb_extraction?`
|
|
526
|
-
# tells the two apart by inspecting the expression `query_expression`
|
|
527
|
-
# already built — never a second, hand-rolled field walk that
|
|
528
|
-
# could disagree with the one the SQL actually uses.
|
|
523
|
+
# A jsonb-extracted value still comes out as text; cast it to compare/sort
|
|
524
|
+
# numerically instead of lexicographically.
|
|
529
525
|
def comparable_expression(expression, value)
|
|
530
526
|
value.is_a?(Numeric) && jsonb_extraction?(expression) ? "(#{expression})::numeric" : expression
|
|
531
527
|
end
|
|
@@ -534,19 +530,14 @@ module Hecks
|
|
|
534
530
|
pg_exec_params(sql, binds).map { |row| instance_from_row(row) }
|
|
535
531
|
end
|
|
536
532
|
|
|
537
|
-
# Stamps `.version`
|
|
538
|
-
#
|
|
539
|
-
# every Instance this adapter builds from a real stored row, so a
|
|
540
|
-
# later `save`'s optimistic-concurrency CAS has something to check
|
|
541
|
-
# against.
|
|
533
|
+
# Stamps `.version` from `hecks_version` so a later CAS save has something
|
|
534
|
+
# to check against.
|
|
542
535
|
def instance_from_row(row)
|
|
543
536
|
instance = Runtime::Instance.new(aggregate: @aggregate, id: row["id"], state: decode(row))
|
|
544
537
|
instance.version = row["hecks_version"].to_i
|
|
545
538
|
instance
|
|
546
539
|
end
|
|
547
540
|
|
|
548
|
-
# ── the rest of the dialect ─────────────────────────────────────
|
|
549
|
-
|
|
550
541
|
def quote_ident(name) = PG::Connection.quote_ident(name.to_s)
|
|
551
542
|
def quoted_table = quote_ident(table)
|
|
552
543
|
def entry_table = "#{table}_entries"
|
|
@@ -559,11 +550,8 @@ module Hecks
|
|
|
559
550
|
jsonb_extraction?(expression) && numeric_field?(field) ? "(#{expression})::numeric" : expression
|
|
560
551
|
end
|
|
561
552
|
|
|
562
|
-
# Postgres
|
|
563
|
-
#
|
|
564
|
-
# answers identically no matter which adapter serves it (the
|
|
565
|
-
# port's in-memory semantics, which Sqlite's own default happens
|
|
566
|
-
# to match, put null rows FIRST ascending and LAST descending).
|
|
553
|
+
# Overrides Postgres's default NULLS LAST on ASC so every adapter answers
|
|
554
|
+
# a declared query's null ordering identically.
|
|
567
555
|
def order_clause(order_by, policy)
|
|
568
556
|
direction = order_by.direction.to_s.downcase == "desc" ? "DESC" : "ASC"
|
|
569
557
|
nulls = case policy&.mode.to_s
|
|
@@ -584,15 +572,38 @@ module Hecks
|
|
|
584
572
|
end
|
|
585
573
|
end
|
|
586
574
|
|
|
587
|
-
# Array[...]
|
|
588
|
-
#
|
|
589
|
-
# hand-rolled '{a,b,c}' array literal has no escaping at all, and
|
|
590
|
-
# a segment is a field or value-object member name this method has
|
|
591
|
-
# no way to know is always schema-declared.
|
|
575
|
+
# Builds an escaped Array[...] literal rather than a hand-rolled '{a,b,c}'
|
|
576
|
+
# string, since a segment name isn't guaranteed schema-declared.
|
|
592
577
|
def jsonb_path(column, segments)
|
|
593
578
|
"#{quote_ident(column)} #>> ARRAY[#{segments.map { |segment| text_literal(segment) }.join(', ')}]::text[]"
|
|
594
579
|
end
|
|
595
580
|
|
|
581
|
+
# Shared decode step for `entries`/`entries_since` — runs a query already selecting
|
|
582
|
+
# `aggregate_id, operation, state, mirrors, sequence` and builds one Entry per row.
|
|
583
|
+
def entries_matching(sql, binds = [])
|
|
584
|
+
pg_exec_params(sql, binds).map do |row|
|
|
585
|
+
state = JSON.parse(row["state"])
|
|
586
|
+
Ports::Persistence::Entry.new(
|
|
587
|
+
operation: row["operation"] || "save",
|
|
588
|
+
id: row["aggregate_id"],
|
|
589
|
+
state: Ports::Persistence::StateCodec.decode(@aggregate, state),
|
|
590
|
+
mirrors: row["mirrors"] && JSON.parse(row["mirrors"]),
|
|
591
|
+
sequence: row["sequence"].to_i
|
|
592
|
+
)
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
|
|
596
|
+
# Advances this table's checkpoint to `sequence`, never backwards — two overlapping
|
|
597
|
+
# `project` calls for out-of-order entries must not let an older sequence win.
|
|
598
|
+
def advance_checkpoint!(sequence)
|
|
599
|
+
pg_exec_params(
|
|
600
|
+
"INSERT INTO hecks_checkpoints (aggregate_table, last_sequence) VALUES ($1, $2) " \
|
|
601
|
+
"ON CONFLICT (aggregate_table) DO UPDATE SET " \
|
|
602
|
+
"last_sequence = GREATEST(hecks_checkpoints.last_sequence, EXCLUDED.last_sequence)",
|
|
603
|
+
[table, sequence]
|
|
604
|
+
)
|
|
605
|
+
end
|
|
606
|
+
|
|
596
607
|
def text_literal(text) = "'#{text.to_s.gsub("'", "''")}'"
|
|
597
608
|
end
|
|
598
609
|
end
|
|
@@ -1,22 +1,11 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
3
|
-
# declaration stays here, unmoved. `Adapters::Folder#load_library` globs
|
|
4
|
-
# `.adapter` files by DIRECTORY, not by require path, on every boot,
|
|
5
|
-
# regardless of which adapters a given domain actually binds — the same
|
|
6
|
-
# reason every other adapter's own `.adapter` file lives beside this one.
|
|
7
|
-
# Pure metadata (which port, which fields) — no era-specific code, and
|
|
8
|
-
# harmless for a domain that never binds `persisted_by "PostgresEra"`.
|
|
9
|
-
# `registry.adapter_class("PostgresEra")` is only ever resolved for a
|
|
10
|
-
# domain that actually binds to it, at which point the plugin must
|
|
11
|
-
# already be `require`d for the class to exist.
|
|
1
|
+
# Declares the PostgresEra adapter's port and fields; the class lives in the era plugin.
|
|
2
|
+
# This file is loaded on every boot, so it stays metadata only.
|
|
12
3
|
Hecks.adapter "PostgresEra" do
|
|
13
4
|
port "persistence"
|
|
14
5
|
field :database
|
|
15
6
|
field :role
|
|
16
7
|
field :schema
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# BYPASSRLS, which the era write-fence (row-level security) cannot
|
|
20
|
-
# bite. Refused by default; see `Lineage#check_fence_applies!` (BUG#24).
|
|
8
|
+
# Opt-in to boot over a superuser or BYPASSRLS role, which the era write fence cannot
|
|
9
|
+
# bite. Refused by default; see `Lineage#check_fence_applies!`.
|
|
21
10
|
field :allow_superuser
|
|
22
11
|
end
|
|
@@ -5,13 +5,8 @@ module Hecks
|
|
|
5
5
|
module Adapters
|
|
6
6
|
class NotExtractable < StandardError; end
|
|
7
7
|
|
|
8
|
-
# Extracts a `given`/`ensures`/invariant block's own source text
|
|
9
|
-
#
|
|
10
|
-
# parses it with the `prism` gem, and canonicalises it — how a rule's
|
|
11
|
-
# predicate becomes readable, comparable text (era diffing, docs) rather
|
|
12
|
-
# than an opaque compiled Proc. Per-process `TREES` cache keyed by file
|
|
13
|
-
# path; `forget`/`forget_all` exist for a caller that reloads an edited
|
|
14
|
-
# file in-process (see their own comment).
|
|
8
|
+
# Extracts a `given`/`ensures`/invariant block's own source text via
|
|
9
|
+
# `prism`, canonicalised into readable, comparable text (era diffing, docs).
|
|
15
10
|
module Prism
|
|
16
11
|
# Not frozen — a real cache, keyed by file path and mutated by
|
|
17
12
|
# #tree_for below (`TREES[file] ||= ...`) and #forget/#forget_all.
|
|
@@ -67,32 +62,14 @@ module Hecks
|
|
|
67
62
|
# @param file [String] the file path to parse and cache under
|
|
68
63
|
# @return [Prism::ProgramNode] the parsed syntax tree's root node
|
|
69
64
|
def tree_for(file)
|
|
70
|
-
# ::Prism.parse_file
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
# the Ruby level instead of the OS level (e.g. tebako's memfs,
|
|
74
|
-
# which presses a hecks-based app into a single executable —
|
|
75
|
-
# see domain/README.md's "Deploying" section in a client site's domain for
|
|
76
|
-
# why that matters). ::Prism.parse(File.read(file)) parses the
|
|
77
|
-
# exact same bytes, just read through Ruby's File.read first,
|
|
78
|
-
# which those tools do intercept.
|
|
65
|
+
# ::Prism.parse_file reads the file at the C-extension level, bypassing
|
|
66
|
+
# Ruby's File/IO layer — invisible to tools that virtualize the filesystem
|
|
67
|
+
# there (e.g. tebako's memfs). Read through File.read first so they intercept it.
|
|
79
68
|
TREES[file] ||= ::Prism.parse(File.read(file)).value
|
|
80
69
|
end
|
|
81
70
|
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# loads once per process: one `bin/ir` run, one rspec worker,
|
|
85
|
-
# never edited out from under it), but wrong for anything that
|
|
86
|
-
# legitimately reloads an edited file in-process: a stale cached
|
|
87
|
-
# tree reports a `given`/`ensures` block at its old line number,
|
|
88
|
-
# which no longer matches the freshly re-executed file's own
|
|
89
|
-
# `block.source_location` — surfacing as "did not survive
|
|
90
|
-
# extraction" on a perfectly valid file. Built for real building
|
|
91
|
-
# `Hecks::Codemod` (lib/hecks/codemod.rb), which needs exactly this
|
|
92
|
-
# invalidation rather than reaching into `TREES.clear` directly — a
|
|
93
|
-
# private implementation detail poked from outside. `forget`/
|
|
94
|
-
# `forget_all` are the real API so nothing else that reloads an
|
|
95
|
-
# edited file in-process has to know `TREES` exists at all.
|
|
71
|
+
# Drops one file from the process-wide parse cache, for a caller (`Hecks::Codemod`)
|
|
72
|
+
# that reloads an edited file in-process and needs the stale tree invalidated.
|
|
96
73
|
#
|
|
97
74
|
# @param file [String] the file path to drop from the cache
|
|
98
75
|
# @return [Prism::ProgramNode, nil] the cached tree that was removed, or nil if
|