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
|
@@ -6,28 +6,10 @@ require_relative "../../runtime/value"
|
|
|
6
6
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Adapters
|
|
9
|
-
# The
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
# predicate, the comma-separated `in` convention, and the identity
|
|
14
|
-
# ORDER BY fallback are spelled once, here.
|
|
15
|
-
#
|
|
16
|
-
# What a dialect actually differs on is narrow, and each adapter
|
|
17
|
-
# supplies exactly those hooks:
|
|
18
|
-
#
|
|
19
|
-
# placeholder(binds, value) — "?" or "$N", recording the bind
|
|
20
|
-
# contains_clause(expr, ph) — instr() or position(), for a scalar field
|
|
21
|
-
# list_contains_clause(name, member, ph) — real element containment,
|
|
22
|
-
# for a `list_of` field (json_each /
|
|
23
|
-
# jsonb_array_elements)
|
|
24
|
-
# empty_in_clause — "0" or "FALSE"
|
|
25
|
-
# comparable_expression(e, v) — ::numeric cast, where the dialect casts
|
|
26
|
-
# plain_column(name) — a real column or a jsonb path
|
|
27
|
-
# nested_expression(name, path, member) — json_extract or state #>>
|
|
28
|
-
# order_clause(order_by, policy)
|
|
29
|
-
# execute_query(sql, binds) — run it, return instances
|
|
30
|
-
# dialect_name — for the refusal message
|
|
9
|
+
# The SQL query compilation shared by the SQLite and Postgres adapters. Each dialect
|
|
10
|
+
# supplies hooks: placeholder, contains_clause, list_contains_clause, empty_in_clause,
|
|
11
|
+
# comparable_expression, plain_column, nested_expression, order_clause, execute_query
|
|
12
|
+
# and dialect_name.
|
|
31
13
|
module SqlQueryBuilder
|
|
32
14
|
COMPARATORS = {
|
|
33
15
|
"eq" => "=", "ne" => "<>", "gt" => ">", "gte" => ">=", "lt" => "<", "lte" => "<="
|
|
@@ -52,11 +34,7 @@ module Hecks
|
|
|
52
34
|
sql << " WHERE #{clauses.join(' AND ')}" unless clauses.empty?
|
|
53
35
|
sql << order_by_sql(declared)
|
|
54
36
|
sql << " LIMIT #{placeholder(binds, query_value(declared.limit.value, args).to_i)}" if declared.limit
|
|
55
|
-
#
|
|
56
|
-
# outright (LIMIT -1 is its unbounded idiom) while Postgres accepts
|
|
57
|
-
# one, so the dialect supplies its own unbounded spelling. Found by
|
|
58
|
-
# the adapter-agreement gate on its first run, not by either
|
|
59
|
-
# adapter's own spec: each was self-consistent, they just disagreed.
|
|
37
|
+
# SQLite refuses a bare OFFSET, so the dialect spells its own unbounded limit.
|
|
60
38
|
sql << unbounded_limit if !declared.limit && declared.offset
|
|
61
39
|
sql << " OFFSET #{placeholder(binds, query_value(declared.offset.value, args).to_i)}" if declared.offset
|
|
62
40
|
execute_query(sql, binds)
|
|
@@ -64,12 +42,7 @@ module Hecks
|
|
|
64
42
|
|
|
65
43
|
private
|
|
66
44
|
|
|
67
|
-
#
|
|
68
|
-
# null-policy predicate short-circuits the ordinary comparator path
|
|
69
|
-
# per clause, same as it did inline. `binds` is populated in place
|
|
70
|
-
# (the same array `query` goes on to push LIMIT/OFFSET placeholders
|
|
71
|
-
# into afterward), so pulling this loop out changes nothing about
|
|
72
|
-
# bind-parameter order.
|
|
45
|
+
# `binds` is filled in place so LIMIT/OFFSET placeholders follow the where binds.
|
|
73
46
|
def where_clauses(declared, args, binds)
|
|
74
47
|
declared.wheres.each_with_object([]) do |clause, clauses|
|
|
75
48
|
value = query_value(clause.value, args)
|
|
@@ -103,71 +76,26 @@ module Hecks
|
|
|
103
76
|
members = in_members(value)
|
|
104
77
|
return empty_in_clause if members.empty?
|
|
105
78
|
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
# stringified members, and `in_members` above stringifies its
|
|
109
|
-
# own — so a numeric field was the one shape where the engines
|
|
110
|
-
# could not agree: `in_members` binds '100', while the column
|
|
111
|
-
# expression yields the number 100, and `100 IN ('100','300')`
|
|
112
|
-
# is false in SQLite (a json_extract result carries no column
|
|
113
|
-
# affinity to coerce it) where Memory matched. Casting the
|
|
114
|
-
# expression rather than typing the members keeps one rule for
|
|
115
|
-
# every field: no type inference, and `in` means the same thing
|
|
116
|
-
# on a String, an Integer, and a value-object member.
|
|
117
|
-
#
|
|
118
|
-
# `eq`/`gt`/`lt` never had this problem because they bind the
|
|
119
|
-
# value's own type and route through `comparable_expression`,
|
|
120
|
-
# which Postgres overrides to cast numerics.
|
|
79
|
+
# `in` reads as text everywhere: casting the column keeps a numeric field
|
|
80
|
+
# matching the stringified members (SQLite's json_extract carries no affinity).
|
|
121
81
|
"CAST(#{expression} AS TEXT) IN (#{members.map { |member| placeholder(binds, member) }.join(', ')})"
|
|
122
82
|
else
|
|
123
83
|
raise ArgumentError, "#{dialect_name} query adapter does not support #{oper.inspect}"
|
|
124
84
|
end
|
|
125
85
|
end
|
|
126
86
|
|
|
127
|
-
# A real array
|
|
128
|
-
#
|
|
129
|
-
# domain value (Naming::IDENTITY_JOIN joins a composite identity's
|
|
130
|
-
# own parts, and the parts it joins are whatever an identity path
|
|
131
|
-
# pulled out of real data), so a name carrying a comma would
|
|
132
|
-
# silently become two members matching the wrong rows, or nothing.
|
|
133
|
-
# Calling `value.to_s` unconditionally would mean only a
|
|
134
|
-
# comma-joined string ever works here, while
|
|
135
|
-
# Ports::Query::InMemory and QueryInterpreter's own `members`
|
|
136
|
-
# handle a real Array — three implementations of `in`,
|
|
137
|
-
# two readings of it. Narrower than those two on one point,
|
|
138
|
-
# deliberately: they unwrap a value-object/Hash element to its
|
|
139
|
-
# own scalar first (`comparable`) before stringifying, a rule
|
|
140
|
-
# this doesn't reproduce — every real caller of `in` today, and
|
|
141
|
-
# the cross-aggregate hop this exists for, passes plain scalar
|
|
142
|
-
# ids, never a value-object element.
|
|
87
|
+
# A real array is not re-split on commas: an id is a domain value and may hold one.
|
|
88
|
+
# Unlike the other engines, a Hash or value-object element is not unwrapped.
|
|
143
89
|
def in_members(value)
|
|
144
90
|
return value.map(&:to_s).reject(&:empty?) if value.is_a?(Array)
|
|
145
91
|
|
|
146
92
|
value.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
147
93
|
end
|
|
148
94
|
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
# member
|
|
153
|
-
# falling back to the one-field convention `value`.
|
|
154
|
-
#
|
|
155
|
-
# A reference is an ID, stored as a bare scalar (never wrapped —
|
|
156
|
-
# `Runtime::Value.refuse_object_reference` guarantees it), so it
|
|
157
|
-
# takes the same plain-column path as any other non-value-object
|
|
158
|
-
# attribute. Excluding it into the value-object member-picking logic
|
|
159
|
-
# compiles a nested read against a row that has no nested key — a
|
|
160
|
-
# path that can never match. Measured, not assumed: a `where` on a
|
|
161
|
-
# has_one/belongs_to/reference_to field returned zero rows against
|
|
162
|
-
# real data until that exclusion was removed.
|
|
163
|
-
# `member` resolves through two ordered fallback tiers — the bound
|
|
164
|
-
# value's own numeric field first, then the declared value object's
|
|
165
|
-
# numeric-or-sole attribute — each documented above as fixing a
|
|
166
|
-
# real, measured bug (a reference field matching nothing, a
|
|
167
|
-
# single-attribute VO silently falling to the wrong convention).
|
|
168
|
-
# Splitting the tiers apart would separate two writes to the same
|
|
169
|
-
# `member` local across method boundaries, hiding the fallback
|
|
170
|
-
# order that makes them correct together.
|
|
95
|
+
# Compiles a declared field into an expression over the stored shape. A value-object
|
|
96
|
+
# field compares through its numeric member, else its sole attribute, else `value`.
|
|
97
|
+
# A reference is a bare id, so it takes the plain-column path.
|
|
98
|
+
# The two member fallbacks stay in one method so their order is visible.
|
|
171
99
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
172
100
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
173
101
|
def query_expression(field, value: nil)
|
|
@@ -180,40 +108,20 @@ module Hecks
|
|
|
180
108
|
member = if path.empty? && value
|
|
181
109
|
hash = value.is_a?(Runtime::Value) ? value.to_h : value
|
|
182
110
|
numeric = hash.is_a?(Hash) && hash.find { |_key, item| item.is_a?(Numeric) }
|
|
183
|
-
#
|
|
184
|
-
# back false) as well as nil (.find came back empty) — `&.`
|
|
185
|
-
# only guards nil, so `false.first` raises. False positive.
|
|
111
|
+
# `numeric` can be false, which `&.` does not guard.
|
|
186
112
|
# rubocop:disable-next Style/SafeNavigation
|
|
187
113
|
numeric ? numeric.first : nil
|
|
188
114
|
end
|
|
189
115
|
member ||= if path.empty? && attribute && value_object?(attribute)
|
|
190
116
|
object = Runtime::Value.value_object_for(@aggregate, attribute.type)
|
|
191
|
-
# A
|
|
192
|
-
# numeric — a single-attribute value object is its one
|
|
193
|
-
# field whatever that field is named (`Behaviour::
|
|
194
|
-
# ValueObject#sole_attribute`, the same strict rule
|
|
195
|
-
# `Runtime::Value`'s own `.value` alias enforces), so
|
|
196
|
-
# `EmailAddress{address}` compiles to `$.address`
|
|
197
|
-
# rather than falling through to the dialects' own
|
|
198
|
-
# `member || "value"` convention and silently matching
|
|
199
|
-
# nothing. Multi-field non-numeric shapes still fall
|
|
200
|
-
# through to that convention, unchanged — there is no
|
|
201
|
-
# single field to honestly pick for them here either.
|
|
117
|
+
# A single-attribute value object is its one field, whatever it is named.
|
|
202
118
|
(Forms::ValueObjectShape.numeric_member(object) || object.sole_attribute)&.name
|
|
203
119
|
end
|
|
204
120
|
nested_expression(name, path, member)
|
|
205
121
|
end
|
|
206
122
|
|
|
207
|
-
#
|
|
208
|
-
#
|
|
209
|
-
# otherwise the same single-field fallback `query_expression`'s own
|
|
210
|
-
# `nested_expression(name, path, member || "value")` already
|
|
211
|
-
# makes for the column side — a plain `attribute :value, String`
|
|
212
|
-
# value object (IdentityId, RoleName, ...) has no numeric member at
|
|
213
|
-
# all, and returning nil there silently turned an equality
|
|
214
|
-
# comparison into `IS NULL`, matching nothing. Both sides of one
|
|
215
|
-
# comparison have to agree on which field they mean, or they
|
|
216
|
-
# compare two different things and call it a where clause.
|
|
123
|
+
# Picks the same member `query_expression` does, so both sides of a comparison
|
|
124
|
+
# agree on which field they mean.
|
|
217
125
|
def query_value(value, args)
|
|
218
126
|
value = args[value] if value.is_a?(Symbol)
|
|
219
127
|
hash = value.is_a?(Runtime::Value) ? value.to_h : value
|
|
@@ -233,17 +141,9 @@ module Hecks
|
|
|
233
141
|
!attr.list? && !Runtime::Value.value_object_for(@aggregate, attr.type).nil?
|
|
234
142
|
end
|
|
235
143
|
|
|
236
|
-
# `contains` on a
|
|
237
|
-
#
|
|
238
|
-
#
|
|
239
|
-
# real Array, and a substring search would disagree with it (a
|
|
240
|
-
# substring match over `[{"value":"not_high_risk"}]` falsely matches
|
|
241
|
-
# "high_risk"). Returns nil for a non-list field (the caller falls
|
|
242
|
-
# back to `contains_clause`, unchanged), "" for a list of bare
|
|
243
|
-
# scalars (no member to walk into), or the one field name a
|
|
244
|
-
# single-field value-object element carries. A list of a
|
|
245
|
-
# multi-field value object has no one scalar to compare against and
|
|
246
|
-
# is refused rather than guessed.
|
|
144
|
+
# `contains` on a list means element membership, not a substring search over JSON text.
|
|
145
|
+
# Returns nil for a non-list field, "" for bare scalars, else the sole member name.
|
|
146
|
+
# Raises for a multi-field value object.
|
|
247
147
|
def list_member(field)
|
|
248
148
|
return nil if field.to_s.include?(".")
|
|
249
149
|
|
|
@@ -16,18 +16,9 @@ module Hecks
|
|
|
16
16
|
fields << { name: lifecycle.field, attribute: nil, sql_type: "TEXT" } if lifecycle && fields.none? do |field|
|
|
17
17
|
field[:name] == lifecycle.field
|
|
18
18
|
end
|
|
19
|
-
# `projects` fields
|
|
20
|
-
# `
|
|
21
|
-
#
|
|
22
|
-
# any other field, so a column has to exist to hold it or
|
|
23
|
-
# `save`'s own `columns =`/`values =` build (this file, not
|
|
24
|
-
# here) silently drops it on every write. No `attribute` of
|
|
25
|
-
# their own to carry (`ProjectedField` is a bare name/
|
|
26
|
-
# reference/remote_field triple, not a typed attribute) —
|
|
27
|
-
# treated as a raw scalar, same as the lifecycle field just
|
|
28
|
-
# above: every real corpus use projects a status/lifecycle
|
|
29
|
-
# string, and `encode_field`/`decode` both already pass a
|
|
30
|
-
# `attribute: nil` field through untouched, not JSON-encoded.
|
|
19
|
+
# `projects` fields are written straight into `Instance#state`, so they need a column
|
|
20
|
+
# or `project` drops them. They carry no attribute and are stored as raw scalars,
|
|
21
|
+
# like the lifecycle field.
|
|
31
22
|
@aggregate.projected_fields.each do |field|
|
|
32
23
|
next if fields.any? { |f| f[:name] == field.name }
|
|
33
24
|
|
|
@@ -37,13 +28,8 @@ module Hecks
|
|
|
37
28
|
end
|
|
38
29
|
|
|
39
30
|
def encode(attr, value)
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
# ever appended to — the shape every other aggregate in the corpus
|
|
43
|
-
# happened not to carry, until one declared a list-typed head
|
|
44
|
-
# attribute that no creating command populates by default — has to
|
|
45
|
-
# stay NULL to answer the same as Memory does. Forcing it to `[]`
|
|
46
|
-
# here is how a persistence topology invented data no dispatch wrote.
|
|
31
|
+
# A list nothing has appended to stays NULL, as under Memory; forcing `[]` would
|
|
32
|
+
# invent data no dispatch wrote.
|
|
47
33
|
return (value.nil? ? nil : state_json(value)) if attr.list?
|
|
48
34
|
return state_json(value) if value.is_a?(Hash) || value.is_a?(Runtime::Value)
|
|
49
35
|
|
|
@@ -56,14 +42,8 @@ module Hecks
|
|
|
56
42
|
encode(field[:attribute], value)
|
|
57
43
|
end
|
|
58
44
|
|
|
59
|
-
# Every JSON column's text goes through the state codec's `encode`
|
|
60
|
-
# (PR A3) — string keys at every depth, Values materialized.
|
|
61
45
|
def state_json(value) = JSON.generate(Ports::Persistence::StateCodec.encode(@aggregate, value))
|
|
62
46
|
|
|
63
|
-
# The row's columns reassembled into the stored state, then decoded
|
|
64
|
-
# once through the state codec (PR A3) — the same deep, IR-driven
|
|
65
|
-
# key spelling every other adapter's read now produces, instead of
|
|
66
|
-
# this codec's own `symbolize_names:` walk.
|
|
67
47
|
def decode(row)
|
|
68
48
|
state = persisted_fields.each_with_object({}) do |field, raw_state|
|
|
69
49
|
attr = field[:attribute]
|
|
@@ -79,27 +59,15 @@ module Hecks
|
|
|
79
59
|
if attr.list? || value_object?(attr)
|
|
80
60
|
raw ? JSON.parse(raw) : nil
|
|
81
61
|
else
|
|
82
|
-
# A reference
|
|
83
|
-
# the id of a head, which is text — `JSON.parse("acct-1")` raises,
|
|
84
|
-
# so reading it as JSON was only ever survivable while the column
|
|
85
|
-
# held an object.
|
|
62
|
+
# A reference holds a head's id, which is text and would fail JSON.parse.
|
|
86
63
|
raw
|
|
87
64
|
end
|
|
88
65
|
end
|
|
89
66
|
Ports::Persistence::StateCodec.decode(@aggregate, state)
|
|
90
67
|
end
|
|
91
68
|
|
|
92
|
-
# A NULL `projects` column
|
|
93
|
-
#
|
|
94
|
-
# holding only what was written) already answer it. A column holds
|
|
95
|
-
# every persisted field whether or not the record ever had one, so
|
|
96
|
-
# NULL is the only spelling "never set" has here; and nothing ever
|
|
97
|
-
# stores a nil projected value to confuse it with —
|
|
98
|
-
# `CommandInterpreter#seed_projected_fields` and
|
|
99
|
-
# `RebuildSweep#refresh` both skip a nil remote value. Absent is also
|
|
100
|
-
# what `RebuildSweep#refresh`'s `record.key?` and
|
|
101
|
-
# `Registry#projection_current?`'s row comparison expect. The
|
|
102
|
-
# lifecycle field (the other `attribute: nil` column) keeps its NULL.
|
|
69
|
+
# A NULL `projects` column means never seeded, so it reads back absent as under Heki and
|
|
70
|
+
# PostgresEra; nothing stores a nil projected value. The lifecycle field keeps its NULL.
|
|
103
71
|
def projected_only?(field)
|
|
104
72
|
@aggregate.lifecycle&.field&.to_sym != field[:name].to_sym &&
|
|
105
73
|
@aggregate.projected_fields.any? { |projected| projected.name.to_sym == field[:name].to_sym }
|
|
@@ -13,9 +13,7 @@ require_relative "../sqlite"
|
|
|
13
13
|
module Hecks
|
|
14
14
|
module Adapters
|
|
15
15
|
# Rebuilds a read store from the authoritative journal, entry by entry.
|
|
16
|
-
#
|
|
17
|
-
# bare id it holds — the same shapes the command path writes, so there
|
|
18
|
-
# is no second representation to accept here any more.
|
|
16
|
+
# Values are written as JSON, references as bare ids — the shapes the command path writes.
|
|
19
17
|
class SqliteProjection < Sqlite
|
|
20
18
|
# Replaces or deletes the read store's row for one journal entry, encoding the entry's
|
|
21
19
|
# state directly rather than through a `Runtime::Instance`.
|
|
@@ -36,68 +34,31 @@ module Hecks
|
|
|
36
34
|
entry
|
|
37
35
|
end
|
|
38
36
|
|
|
39
|
-
#
|
|
40
|
-
# tables. The report shape is assembled from SQL-selected rows, rather
|
|
41
|
-
# than scanning repositories and matching references in Ruby.
|
|
37
|
+
# Executes a declared read model against the projected read-store tables.
|
|
42
38
|
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
# `Runtime::ReadModelInterpreter#project` (the in-process path) on
|
|
46
|
-
# the two counts where a native path can most easily diverge:
|
|
39
|
+
# A missing root raises, matching the in-process interpreter; a head
|
|
40
|
+
# matches against any already-projected source, not only the root.
|
|
47
41
|
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
# **Chained-include join scope**: matching a non-root head against
|
|
57
|
-
# the root's own id alone, regardless of what it actually
|
|
58
|
-
# references, is correct for a head that references the root
|
|
59
|
-
# directly and silently empty for one that references another
|
|
60
|
-
# included head instead (`Leaf` -> `Mid` -> `Root`, `Leaf` itself
|
|
61
|
-
# has no attribute referencing `Root` at all, so its `references`
|
|
62
|
-
# would always be `[]`). The in-process path
|
|
63
|
-
# (`ReadModelInterpreter#project`'s "root first, always" comment)
|
|
64
|
-
# matches a head against any already-projected source, not
|
|
65
|
-
# only the root — `select_related` does the same: each head is
|
|
66
|
-
# matched against every source resolved so far (root first, then
|
|
67
|
-
# declared order — the same one-level-of-declaration-order
|
|
68
|
-
# dependency the in-process path itself has, documented
|
|
69
|
-
# there as L2, not a gap introduced here).
|
|
70
|
-
#
|
|
71
|
-
# @param _domain [String, Symbol] name of the domain declaring the read model; not read
|
|
72
|
-
# @param model [Bluebook::ReadModel, Runtime::TenantScope::Scoped] the read model to
|
|
73
|
-
# answer, or the tenant-scoping delegator around one; must have a reference target
|
|
74
|
-
# @param args [Hash{Symbol => Object}] the read model's arguments; the entry under
|
|
75
|
-
# `model.reference_name` is the root record's id
|
|
76
|
-
# @param bluebook [Bluebook::Chapter, nil] the domain's bluebook, which resolves each
|
|
77
|
-
# included aggregate by name; nil is refused
|
|
78
|
-
# @return [Array<Hash>] a one-element Array holding the report Hash, keyed by each
|
|
79
|
-
# head's `as` name: an Array of plain state Hashes (each with `:id`) for a `many`
|
|
80
|
-
# head, otherwise one such Hash, or nil when that head matched no row
|
|
81
|
-
# @raise [ArgumentError] if `bluebook` is nil
|
|
82
|
-
# @raise [KeyError] if `args` has no entry for the model's reference argument
|
|
83
|
-
# @raise [Runtime::NotFound] if no projected row has the referenced root id
|
|
84
|
-
# @raise [SQLite3::Exception] if a statement fails
|
|
42
|
+
# @param _domain [String, Symbol] domain name; not read
|
|
43
|
+
# @param model [Bluebook::ReadModel, Runtime::TenantScope::Scoped] read model to answer
|
|
44
|
+
# @param args [Hash] arguments; `model.reference_name` holds the root id
|
|
45
|
+
# @param bluebook [Bluebook::Chapter, nil] resolves each included aggregate; nil refused
|
|
46
|
+
# @return [Array<Hash>] one-element Array of the report, keyed by each head's `as`
|
|
47
|
+
# @raise [ArgumentError] if bluebook is nil
|
|
48
|
+
# @raise [Runtime::NotFound] if no projected row matches the referenced root id
|
|
85
49
|
def query_read_model(_domain, model, args, bluebook = nil)
|
|
86
50
|
raise ArgumentError, "projection query needs its domain bluebook" unless bluebook
|
|
87
51
|
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
# declared as a path and followed.
|
|
52
|
+
# Read the same way ReadModelInterpreter reads it: an identity is a
|
|
53
|
+
# declared path, not unwrapped.
|
|
91
54
|
reference_id = args.fetch(model.reference_name).to_s
|
|
92
55
|
# Plural (ADR 0055) — `on:` lets `where`/`order_by`/`limit`/`offset`
|
|
93
|
-
# each name a different many-side head, so more than one
|
|
94
|
-
# eligible in the same read model
|
|
56
|
+
# each name a different many-side head, so more than one may be
|
|
57
|
+
# eligible in the same read model.
|
|
95
58
|
eligible = model.filtered_head_names
|
|
96
59
|
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
# identical reason: a later head's own join has to be able to
|
|
100
|
-
# match against a root (or another head) already resolved.
|
|
60
|
+
# Root first, always (see this method's own header): a later head's
|
|
61
|
+
# join must match against a root or head already resolved.
|
|
101
62
|
root_heads, other_heads = model.aggregate_heads.partition { |head| head[:aggregate] == model.reference_target }
|
|
102
63
|
projected = []
|
|
103
64
|
reports = {}
|
|
@@ -130,12 +91,8 @@ module Hecks
|
|
|
130
91
|
row && projected_instance(aggregate, row)
|
|
131
92
|
end
|
|
132
93
|
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# not only the root — a head whose own reference points at another
|
|
136
|
-
# included head rather than the root directly would otherwise match
|
|
137
|
-
# nothing at all, since its reference attribute would be compared
|
|
138
|
-
# against a target (the root) it never names.
|
|
94
|
+
# Matches every already-projected source, not just the root, so a head
|
|
95
|
+
# referencing another included head still matches.
|
|
139
96
|
def select_related(aggregate, projected)
|
|
140
97
|
matches = projected.flat_map do |source|
|
|
141
98
|
references = aggregate.attributes.select do |attribute|
|
|
@@ -150,10 +107,7 @@ module Hecks
|
|
|
150
107
|
end
|
|
151
108
|
return [] if matches.empty?
|
|
152
109
|
|
|
153
|
-
# A reference column holds the
|
|
154
|
-
# `json_extract(col,'$.value') = ? OR col = ?` this replaced was
|
|
155
|
-
# reading both shapes because both existed — one written by the
|
|
156
|
-
# command path, one by older journals. There is one shape now.
|
|
110
|
+
# A reference column holds the id, so it compares directly against itself.
|
|
157
111
|
clauses = matches.map { |attribute, _id| "#{quote_ident(attribute.name)} = ?" }
|
|
158
112
|
bind = matches.map { |_attribute, id| id }
|
|
159
113
|
@db.execute("SELECT * FROM #{quote_ident(aggregate.storage_name)} WHERE #{clauses.join(' OR ')} ORDER BY id", bind)
|
|
@@ -168,37 +122,27 @@ module Hecks
|
|
|
168
122
|
decode_fields(fields_for(aggregate), aggregate, row)
|
|
169
123
|
end
|
|
170
124
|
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# among them (both read back raw — see `decode_fields`).
|
|
125
|
+
# Declared attributes plus the lifecycle field and any unlisted
|
|
126
|
+
# `projects` fields, all read back raw (see `decode_fields`).
|
|
174
127
|
def fields_for(aggregate)
|
|
175
128
|
fields = aggregate.attributes.map { |attribute| [attribute.name, attribute] }
|
|
176
129
|
if (lifecycle = aggregate.lifecycle) && fields.none? { |name, _| name == lifecycle.field }
|
|
177
130
|
fields << [lifecycle.field, nil]
|
|
178
131
|
end
|
|
179
|
-
# `projects` fields (
|
|
180
|
-
# (above) already writes one into its own column via `persisted_fields`
|
|
181
|
-
# (`Codec#persisted_fields`, this class's own superclass module), but
|
|
182
|
-
# this method built its own independent field list that never
|
|
183
|
-
# consulted it — a column `project` populated correctly, silently
|
|
184
|
-
# dropped on every read back out. Same raw-passthrough treatment as
|
|
185
|
-
# the lifecycle field just above: `attribute: nil` down in the loop.
|
|
132
|
+
# `projects` fields (ADR 0025) read back raw too — see `persisted_fields`.
|
|
186
133
|
aggregate.projected_fields.each do |field|
|
|
187
134
|
fields << [field.name, nil] unless fields.any? { |name, _| name == field.name }
|
|
188
135
|
end
|
|
189
136
|
fields
|
|
190
137
|
end
|
|
191
138
|
|
|
192
|
-
# Decoded through the state codec
|
|
193
|
-
#
|
|
194
|
-
# column reads back absent.
|
|
139
|
+
# Decoded through the state codec against the row's own aggregate —
|
|
140
|
+
# see Codec#decode for why a NULL projected-only column reads back absent.
|
|
195
141
|
def decode_fields(fields, aggregate, row)
|
|
196
142
|
state = fields.each_with_object({}) do |(name, attribute), raw_state|
|
|
197
143
|
raw = row[name.to_s]
|
|
198
144
|
# rubocop:disable Lint/DuplicateBranch -- the nil-attribute and
|
|
199
|
-
# reference-id branches both just answer `raw`,
|
|
200
|
-
# two unrelated reasons (see each branch's own comment); merging
|
|
201
|
-
# them would blur that distinction.
|
|
145
|
+
# reference-id branches both just answer `raw`, for unrelated reasons.
|
|
202
146
|
next if attribute.nil? && raw.nil? && aggregate.lifecycle&.field&.to_sym != name.to_sym
|
|
203
147
|
|
|
204
148
|
raw_state[name] =
|