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
|
@@ -13,10 +13,8 @@ require_relative "rebuild_sweep"
|
|
|
13
13
|
|
|
14
14
|
module Hecks
|
|
15
15
|
module Runtime
|
|
16
|
-
# The dispatch pipeline for a command on an aggregate head
|
|
17
|
-
# gate
|
|
18
|
-
# in command_interpreter/mutation_applier.rb; what stays here is the
|
|
19
|
-
# order of the steps and how a record is addressed.
|
|
16
|
+
# The dispatch pipeline for a command on an aggregate head; the argument
|
|
17
|
+
# gate and mutation walk are mixed in from their own files.
|
|
20
18
|
class CommandInterpreter
|
|
21
19
|
include Interpreting
|
|
22
20
|
include ArgumentGate
|
|
@@ -24,27 +22,17 @@ module Hecks
|
|
|
24
22
|
|
|
25
23
|
attr_reader :registry
|
|
26
24
|
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# conformance_spec.rb holds every step to a real `step_<name>` handler,
|
|
31
|
-
# both directions.
|
|
25
|
+
# Read off the generated table (lib/hecks/vocabulary.rb), not typed here.
|
|
26
|
+
# spec/vocabulary_conformance_spec.rb holds every step to a real
|
|
27
|
+
# `step_<name>` handler, both directions.
|
|
32
28
|
DISPATCH_ORDER = Hecks::Vocabulary.symbols("AggregateDispatchOrder")
|
|
33
29
|
|
|
34
|
-
# A
|
|
35
|
-
#
|
|
36
|
-
# against one aggregate resolve through exactly one retry in the
|
|
37
|
-
# ordinary case (the loser's retried hydrate reads the winner's now-
|
|
38
|
-
# committed state and its own `given` refuses for real, raising
|
|
39
|
-
# `GivenNotMet`, not `StaleWrite`) — this cap exists for pathological
|
|
40
|
-
# contention (many concurrent writers on one hot aggregate), not the
|
|
41
|
-
# two-writer case.
|
|
30
|
+
# A cap for pathological contention (many concurrent writers on one hot
|
|
31
|
+
# aggregate) — the ordinary two-writer race resolves in a single retry.
|
|
42
32
|
MAX_STALE_WRITE_RETRIES = 5
|
|
43
33
|
|
|
44
|
-
# Every cross-step local held in one place
|
|
45
|
-
#
|
|
46
|
-
# `result` and `transition`/`old_state` default to nil until the step
|
|
47
|
-
# that sets them runs, the same as unset locals would be before that point.
|
|
34
|
+
# Every cross-step local held in one place; fields default to nil until
|
|
35
|
+
# the step that sets them runs.
|
|
48
36
|
Context = Struct.new(:domain, :aggregate, :command, :args, :repository, :instance, :transition, :old_state,
|
|
49
37
|
:result, :correlation, :route, :plan, :strategy, :persistence_outcome, :pending_delegation,
|
|
50
38
|
:dry_run, :correction_bindings, :outbox_rows, :invocation)
|
|
@@ -57,42 +45,18 @@ module Hecks
|
|
|
57
45
|
@rules = rules
|
|
58
46
|
end
|
|
59
47
|
|
|
60
|
-
# `
|
|
61
|
-
#
|
|
62
|
-
# dispatch would (givens checked, mutations applied to `ctx.instance`
|
|
63
|
-
# in memory); `step_save`/`step_emit` are the only two that read this
|
|
64
|
-
# flag, each skipping its own real work — see their own comments.
|
|
65
|
-
# Retries the whole method body on `StaleWrite` — a fresh `ctx`, a
|
|
66
|
-
# fresh `step_hydrate` re-reading current state, so `enforce_givens`
|
|
67
|
-
# re-evaluates against reality rather than the snapshot that just
|
|
68
|
-
# went stale. See `MAX_STALE_WRITE_RETRIES`/`Runtime::StaleWrite`
|
|
69
|
-
# for why exhaustion is a pathological-contention signal, not the
|
|
70
|
-
# expected shape of a two-writer race.
|
|
71
|
-
#
|
|
72
|
-
# `invocation` — the `Runtime::Invocation` `Dispatcher` built for this
|
|
73
|
-
# call. `ctx.args` is `invocation.to_args` (the same Hash routing
|
|
74
|
-
# always handed this method), `ctx.route` its `target`.
|
|
48
|
+
# Dispatches `command` against `aggregate` through the declared step
|
|
49
|
+
# order, retrying once per concurrent-write conflict.
|
|
75
50
|
#
|
|
76
51
|
# @param domain [String, Symbol] the domain `aggregate` belongs to
|
|
77
52
|
# @param aggregate [Bluebook::Aggregate] the aggregate the command acts on
|
|
78
53
|
# @param command [Class] the command class (`Bluebook::Command` subclass) to dispatch
|
|
79
|
-
# @param invocation [Runtime::Invocation] the invocation
|
|
80
|
-
#
|
|
81
|
-
# @param
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
# @
|
|
85
|
-
# saving, emitting or enqueueing
|
|
86
|
-
# @return [Array(Runtime::Instance, Array<Runtime::Event>,
|
|
87
|
-
# Runtime::DependencyPlanning::Plan, Ports::Persistence::Execution,
|
|
88
|
-
# Array<Runtime::Outbox::Row>)] the settled instance, emitted events,
|
|
89
|
-
# execution plan, persistence outcome and outbox rows — the last
|
|
90
|
-
# three nil on a dry run, which skips save/emit/outbox
|
|
91
|
-
# @raise [StandardError] any class in `Runtime::DOMAIN_REFUSALS` when a
|
|
92
|
-
# given/ensures/invariant/authorization/admissibility rule refuses
|
|
93
|
-
# @raise [Runtime::StaleWrite] if concurrent writers beat this one through every
|
|
94
|
-
# retry (`MAX_STALE_WRITE_RETRIES`)
|
|
95
|
-
# @raise [Runtime::WiringError] if the aggregate's repository cannot be resolved
|
|
54
|
+
# @param invocation [Runtime::Invocation] the invocation built for this call
|
|
55
|
+
# @param correlation [Hash, nil] saga correlation stamped on emitted events, if any
|
|
56
|
+
# @param dry_run [Boolean] validate every step without saving, emitting or enqueueing
|
|
57
|
+
# @return [Array] the settled instance, events, plan, persistence outcome, and outbox rows
|
|
58
|
+
# @raise [StandardError] a domain refusal (Runtime::DOMAIN_REFUSALS) when a rule refuses
|
|
59
|
+
# @raise [Runtime::StaleWrite, Runtime::WiringError] every retry loses, or resolve fails
|
|
96
60
|
def call(domain, aggregate, command, invocation, correlation = nil, dry_run: false)
|
|
97
61
|
args = invocation.to_args
|
|
98
62
|
route = invocation.target
|
|
@@ -104,11 +68,8 @@ module Hecks
|
|
|
104
68
|
ctx.route = route
|
|
105
69
|
ctx.dry_run = dry_run
|
|
106
70
|
ctx.plan = DependencyPlanning::Analyzer.call(aggregate: aggregate, command: command)
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
# (no second fetch there any more) always name the same instance;
|
|
110
|
-
# the isolation decision below (lock vs. CAS+retry) needs the
|
|
111
|
-
# repository's capabilities before a single step runs.
|
|
71
|
+
# Resolved once here, before hydration: `Registry#repository` memoizes,
|
|
72
|
+
# and the isolation decision below needs its capabilities up front.
|
|
112
73
|
ctx.repository = @registry.repository(domain, aggregate)
|
|
113
74
|
lock_id = Identity.best_effort(aggregate, args, route, reference_key: reference_key(command))
|
|
114
75
|
run_dispatch_order_with_isolation(DISPATCH_ORDER, ctx, lock_key_id: lock_id)
|
|
@@ -122,14 +83,9 @@ module Hecks
|
|
|
122
83
|
|
|
123
84
|
private
|
|
124
85
|
|
|
125
|
-
#
|
|
126
|
-
#
|
|
127
|
-
#
|
|
128
|
-
# nothing left to decode here yet; like a conditional step that does
|
|
129
|
-
# not fire, it records nothing in `trace`. Declared so the Rust
|
|
130
|
-
# kernel's generated step enum carries the step its generated decoder
|
|
131
|
-
# will move into (roadmap D2), and so a typed Invocation (I2) has a
|
|
132
|
-
# step to be consumed at.
|
|
86
|
+
# No-op: `Routing` has already handed `call` a decoded argument hash,
|
|
87
|
+
# so there is nothing left to decode here yet. Kept as a step so the
|
|
88
|
+
# generated step enum has a slot to move the real decoder into later.
|
|
133
89
|
def step_decode_arguments(_ctx); end
|
|
134
90
|
|
|
135
91
|
def step_refuse_unknown_arguments(ctx)
|
|
@@ -154,7 +110,7 @@ module Hecks
|
|
|
154
110
|
|
|
155
111
|
def step_hydrate(ctx)
|
|
156
112
|
# `ctx.repository` is resolved once, in `#call`, before the
|
|
157
|
-
# isolation decision (lock vs. CAS+retry) — not here
|
|
113
|
+
# isolation decision (lock vs. CAS+retry) — not here.
|
|
158
114
|
ctx.strategy = ctx.plan.strategy_for(capabilities: ctx.repository.capabilities)
|
|
159
115
|
ctx.instance = step(:hydrate) do
|
|
160
116
|
if ctx.plan.complete_state? && ctx.plan.state_independent?
|
|
@@ -171,14 +127,9 @@ module Hecks
|
|
|
171
127
|
|
|
172
128
|
def step_enforce_givens(ctx)
|
|
173
129
|
step(:enforce_givens) do
|
|
174
|
-
# Structural
|
|
175
|
-
# NotFound/AlreadyExists
|
|
176
|
-
#
|
|
177
|
-
# domain rule an author wrote, it is a precondition for the
|
|
178
|
-
# domain rules to mean anything at all. Also locates the
|
|
179
|
-
# correction target itself, if `as:` named one — carried on
|
|
180
|
-
# `ctx` so `step_enforce_ensures` (the settled-record half)
|
|
181
|
-
# can bind the same name too, not just this pre-mutation half.
|
|
130
|
+
# Structural check before the declared givens (same ordering as
|
|
131
|
+
# NotFound/AlreadyExists at hydration). Also locates the correction
|
|
132
|
+
# target, if `as:` named one, so `step_enforce_ensures` can reuse it.
|
|
182
133
|
ctx.correction_bindings = @rules.enforce_correction_target(ctx.instance, ctx.aggregate, ctx.command, domain: ctx.domain)
|
|
183
134
|
@rules.enforce_givens(ctx.instance, ctx.command, ctx.args, domain: ctx.domain,
|
|
184
135
|
declaring: ctx.aggregate, parent: ctx.instance, correction: ctx.correction_bindings)
|
|
@@ -220,61 +171,20 @@ module Hecks
|
|
|
220
171
|
step(:advance_lifecycle) { ctx.instance[ctx.aggregate.lifecycle.field] = ctx.transition.target }
|
|
221
172
|
end
|
|
222
173
|
|
|
223
|
-
# The synchronous cousin of a policy's own `trigger`
|
|
224
|
-
# `CommandBuilder#delegates_to`
|
|
225
|
-
#
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
#
|
|
230
|
-
# this command's own state, the target element's — has been saved
|
|
231
|
-
# yet. `ctx.instance` is the same in-memory record `step_hydrate`
|
|
232
|
-
# loaded and `step_save` will persist; `EntityElement.locate_chain`
|
|
233
|
-
# mutates it in place exactly the way `EntityInterpreter`'s own
|
|
234
|
-
# `step_locate_element`/`step_apply_mutations` mutate their own
|
|
235
|
-
# freshly-loaded copy — the only difference is which already-in-
|
|
236
|
-
# memory record gets handed in.
|
|
237
|
-
#
|
|
238
|
-
# Reimplements the entity command pipeline's own order (givens,
|
|
239
|
-
# transition, mutations, ensures, emit) inline, deliberately — see
|
|
240
|
-
# the comment above on why this must run strictly between this
|
|
241
|
-
# command's own mutations and its ensures/invariants/save. Splitting
|
|
242
|
-
# it would scatter that exact ordering across method boundaries and
|
|
243
|
-
# force threading element/view/transition/old_element/settled
|
|
244
|
-
# through as parameters — the `with:` bug documented just below is
|
|
245
|
-
# exactly the kind of ordering/plumbing mistake that shape invites.
|
|
246
|
-
# rubocop:disable-next Metrics/AbcSize
|
|
174
|
+
# The synchronous cousin of a policy's own `trigger` (see
|
|
175
|
+
# `CommandBuilder#delegates_to`). Runs between this command's own
|
|
176
|
+
# mutations and its ensures/invariants/save, so a refusal here leaves
|
|
177
|
+
# nothing committed on either side. Reimplements the entity pipeline
|
|
178
|
+
# (givens, transition, mutations, ensures, emit) inline, to keep that
|
|
179
|
+
# exact ordering in one place rather than threading state through
|
|
180
|
+
# method boundaries as parameters.
|
|
247
181
|
def step_delegate_to_entity(ctx)
|
|
248
182
|
delegation = ctx.command.mutations.find { |mutation| mutation.op == :delegate }
|
|
249
183
|
return unless delegation
|
|
250
184
|
|
|
251
185
|
step(:delegate_to_entity) do
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
|
|
255
|
-
"#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
|
|
256
|
-
"entity named #{entity_name.inspect}")
|
|
257
|
-
target_command = entity.command(command_name) ||
|
|
258
|
-
raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
|
|
259
|
-
"#{entity_name}.#{command_name}, which " \
|
|
260
|
-
"#{entity_name} declares no such command")
|
|
261
|
-
|
|
262
|
-
# `with:` remaps, it does not enumerate — starting from a copy
|
|
263
|
-
# of this command's own already-resolved args (`ctx.args`) and
|
|
264
|
-
# overlaying the explicit mapping on top means ambient context
|
|
265
|
-
# the caller never had to think about (the aggregate's own
|
|
266
|
-
# identity, addressed the ordinary way to reach `MoveKnight` at
|
|
267
|
-
# all) flows through to the target untouched, the same as it
|
|
268
|
-
# always would for a caller dispatching the entity command
|
|
269
|
-
# directly. Confirmed necessary, not a defensive guess: a real
|
|
270
|
-
# downstream domain's own AdvancePly-on-Moved policy silently
|
|
271
|
-
# failed to re-locate its own aggregate (`reaction_log`: "no
|
|
272
|
-
# Game with label.value ..."), because the emitted event's
|
|
273
|
-
# payload — built from `target_args` alone — never carried
|
|
274
|
-
# `label` at all when `with:` named only `id`/`to`.
|
|
275
|
-
target_args = ctx.args.merge(
|
|
276
|
-
delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
|
|
277
|
-
)
|
|
186
|
+
entity, target_command, command_name = resolve_delegation_target(ctx, delegation)
|
|
187
|
+
target_args = mapped_and_gated_delegation_args(ctx, delegation, entity, target_command)
|
|
278
188
|
|
|
279
189
|
element = EntityElement.locate_chain(ctx.aggregate, [entity], ctx.instance, target_args, command_name)
|
|
280
190
|
view = Instance.new(aggregate: entity, id: EntityElement.element_identity(entity, element).to_s, state: element)
|
|
@@ -292,18 +202,52 @@ module Hecks
|
|
|
292
202
|
settled = Instance.new(aggregate: entity, id: view.id, state: element)
|
|
293
203
|
@rules.enforce_ensures(settled, target_command, target_args, old: old_element, domain: ctx.domain, parent: ctx.instance)
|
|
294
204
|
|
|
295
|
-
# Not emitted here — C7.2
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
299
|
-
# `step_emit`, after the parent committed — where every other
|
|
300
|
-
# command's events are emitted too. (Without parking it here, the
|
|
301
|
-
# entity leg's events would be on the event log and in the adapter
|
|
302
|
-
# before the parent could refuse.)
|
|
205
|
+
# Not emitted here — C7.2: a refused command records nothing, and
|
|
206
|
+
# the parent's own ensures/invariants/save steps still run after
|
|
207
|
+
# this one. Parked and emitted by `step_emit`, after the parent
|
|
208
|
+
# commits, alongside every other command's events.
|
|
303
209
|
ctx.pending_delegation = [target_command, target_args]
|
|
304
210
|
end
|
|
305
211
|
end
|
|
306
212
|
|
|
213
|
+
# The entity and command a `delegates_to` mutation names, resolved once so
|
|
214
|
+
# `step_delegate_to_entity` can read them as plain locals.
|
|
215
|
+
def resolve_delegation_target(ctx, delegation)
|
|
216
|
+
entity_name, _dot, command_name = delegation.target.to_s.rpartition(".")
|
|
217
|
+
entity = ctx.aggregate.entities.find { |e| e.hecks_name == entity_name } ||
|
|
218
|
+
raise(WiringError, "#{ctx.command.hecks_name} delegates_to #{entity_name}." \
|
|
219
|
+
"#{command_name}, but #{ctx.aggregate.hecks_name} has no " \
|
|
220
|
+
"entity named #{entity_name.inspect}")
|
|
221
|
+
target_command = entity.command(command_name) ||
|
|
222
|
+
raise(WiringError, "#{ctx.command.hecks_name} delegates_to " \
|
|
223
|
+
"#{entity_name}.#{command_name}, which " \
|
|
224
|
+
"#{entity_name} declares no such command")
|
|
225
|
+
[entity, target_command, command_name]
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Builds the target command's own args, then runs the same argument gate
|
|
229
|
+
# `EntityInterpreter` runs before a direct dispatch of that command — evaluated
|
|
230
|
+
# against the target command's own declared attributes and the already
|
|
231
|
+
# `with:`-mapped args, not the delegating command's. Without this, a `with:`
|
|
232
|
+
# mapping that omits one of the target's required attributes would silently
|
|
233
|
+
# mutate and persist state that was never validated at all.
|
|
234
|
+
# `extra_identity_heads:` exempts the entity's own identity field (e.g. `id`),
|
|
235
|
+
# which locates the element but is never a declared attribute.
|
|
236
|
+
def mapped_and_gated_delegation_args(ctx, delegation, entity, target_command)
|
|
237
|
+
# `with:` remaps, it does not enumerate — starting from a copy of this command's
|
|
238
|
+
# own resolved args and overlaying the explicit mapping means ambient context the
|
|
239
|
+
# caller never named (the aggregate's own identity) still flows through to the
|
|
240
|
+
# target, the same as a direct dispatch of the entity command would get.
|
|
241
|
+
target_args = ctx.args.merge(
|
|
242
|
+
delegation.source.to_h { |target_key, source_key| [target_key.to_sym, ctx.args[source_key]] }
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
refuse_unknown_arguments(ctx.domain, ctx.aggregate, target_command, target_args,
|
|
246
|
+
extra_identity_heads: entity.identity_heads)
|
|
247
|
+
refuse_absent_arguments(target_command, target_args)
|
|
248
|
+
normalize_args(ctx.aggregate, target_command, target_args)
|
|
249
|
+
end
|
|
250
|
+
|
|
307
251
|
def step_enforce_ensures(ctx)
|
|
308
252
|
step(:enforce_ensures) do
|
|
309
253
|
@rules.enforce_ensures(ctx.instance, ctx.command, ctx.args, old: ctx.old_state,
|
|
@@ -315,33 +259,10 @@ module Hecks
|
|
|
315
259
|
step(:enforce_invariants) { @rules.enforce_invariants(ctx.instance, ctx.aggregate, domain: ctx.domain) }
|
|
316
260
|
end
|
|
317
261
|
|
|
318
|
-
# `dry_run:` skips
|
|
319
|
-
#
|
|
320
|
-
#
|
|
321
|
-
#
|
|
322
|
-
# actually exists, the same question a real dispatch answers before
|
|
323
|
-
# it ever writes anything. Skipping it under `dry_run:` made
|
|
324
|
-
# `dry_run?` disagree with what a real dispatch immediately after it
|
|
325
|
-
# would do: it answered `true` ("would succeed") for a command whose
|
|
326
|
-
# real dispatch refuses with NotFound. Only the persist/raise-on-
|
|
327
|
-
# conflict tail is genuinely persistence-only and stays behind the
|
|
328
|
-
# early return.
|
|
329
|
-
#
|
|
330
|
-
# BUG#132 — a second validation-not-persistence check hid in that
|
|
331
|
-
# same persist/raise-on-conflict tail: `persist_instance`'s own
|
|
332
|
-
# ATOMIC_PUT branch (below) is where `hydrate_complete_state`'s
|
|
333
|
-
# comment says a `creates?` command's duplicate check is
|
|
334
|
-
# deliberately deferred to, for a strategy this fast — a read
|
|
335
|
-
# (`repository.find`) any earlier, before the real write, would be
|
|
336
|
-
# exactly the extra read `insert_only:` exists to avoid paying on
|
|
337
|
-
# every real dispatch. But that means the check never ran at all under
|
|
338
|
-
# `dry_run:` — not eagerly (hydration skips it for this exact
|
|
339
|
-
# strategy, on purpose) and not deferred (this whole branch returns
|
|
340
|
-
# first). `Roster::Roster.Open` against an already-open name is
|
|
341
|
-
# this shape: `dry_run?` answered `true` for a real dispatch
|
|
342
|
-
# immediately after it that refuses `AlreadyExists`. Under
|
|
343
|
-
# `dry_run:` there is no write to race, so paying for that one read
|
|
344
|
-
# is safe here — and only here.
|
|
262
|
+
# `dry_run:` skips persistence, but not `resolve_state_references` or
|
|
263
|
+
# the ATOMIC_PUT duplicate check (`check_dry_run_creates_duplicate`) —
|
|
264
|
+
# both are validation, not persistence, and a real dispatch would
|
|
265
|
+
# refuse on either before ever writing.
|
|
345
266
|
def step_save(ctx)
|
|
346
267
|
step(:save) { @rules.resolve_state_references(ctx.domain, ctx.aggregate, ctx.instance.state) }
|
|
347
268
|
|
|
@@ -357,11 +278,9 @@ module Hecks
|
|
|
357
278
|
end
|
|
358
279
|
end
|
|
359
280
|
|
|
360
|
-
#
|
|
361
|
-
#
|
|
362
|
-
#
|
|
363
|
-
# state` deliberately skips this exact case (its own comment), and
|
|
364
|
-
# a dry run never reaches `persist_instance`'s matching branch.
|
|
281
|
+
# The only path, real or dry, that catches a `creates?` command reusing
|
|
282
|
+
# an occupied identity under ATOMIC_PUT — `hydrate_complete_state`
|
|
283
|
+
# deliberately defers this exact check to here.
|
|
365
284
|
def check_dry_run_creates_duplicate(ctx)
|
|
366
285
|
return unless ctx.strategy == DependencyPlanning::ATOMIC_PUT && ctx.command.creates?
|
|
367
286
|
return unless ctx.repository.find(ctx.instance.id)
|
|
@@ -374,23 +293,15 @@ module Hecks
|
|
|
374
293
|
|
|
375
294
|
def persist_instance(ctx)
|
|
376
295
|
if ctx.strategy == DependencyPlanning::ATOMIC_PUT
|
|
377
|
-
#
|
|
378
|
-
#
|
|
379
|
-
#
|
|
380
|
-
#
|
|
381
|
-
# adapter to decide and refuse atomically
|
|
382
|
-
# (never writing a `creates?` command over an
|
|
383
|
-
# identity that already names a record) rather
|
|
384
|
-
# than this interpreter reading the record
|
|
385
|
-
# first to check — a `repository.find` before
|
|
386
|
-
# every atomic_put would be exactly the read
|
|
387
|
-
# this strategy exists to skip.
|
|
296
|
+
# `insert_only:` asks the adapter to refuse atomically rather than
|
|
297
|
+
# this interpreter reading the record first to check — a
|
|
298
|
+
# `repository.find` before every atomic_put would be exactly the
|
|
299
|
+
# read this strategy exists to skip.
|
|
388
300
|
ctx.repository.atomic_put(ctx.instance, insert_only: ctx.command.creates?)
|
|
389
301
|
else
|
|
390
|
-
# `expected_version:` is
|
|
391
|
-
#
|
|
392
|
-
#
|
|
393
|
-
# through to a plain, unconditional save inside `AppendOnly#save`.
|
|
302
|
+
# `expected_version:` is nil for a brand-new record or a
|
|
303
|
+
# non-CAS repository, either of which falls through to a
|
|
304
|
+
# plain, unconditional save inside `AppendOnly#save`.
|
|
394
305
|
ctx.repository.save(ctx.instance, expected_version: ctx.instance.version)
|
|
395
306
|
end
|
|
396
307
|
end
|
|
@@ -402,10 +313,9 @@ module Hecks
|
|
|
402
313
|
identity: identity_reading(ctx.aggregate),
|
|
403
314
|
offered: Rendering.describe(ctx.instance.id)))
|
|
404
315
|
elsif ctx.persistence_outcome.status == :stale
|
|
405
|
-
# Not a
|
|
406
|
-
#
|
|
407
|
-
#
|
|
408
|
-
# retry loop, re-raised only once retries are exhausted.
|
|
316
|
+
# Not a declared vocabulary refusal, just a plain, informative
|
|
317
|
+
# message — caught by `#call`'s retry loop, re-raised only once
|
|
318
|
+
# retries are exhausted.
|
|
409
319
|
raise(StaleWrite,
|
|
410
320
|
"#{ctx.command.hecks_name} on #{ctx.aggregate.hecks_name} " \
|
|
411
321
|
"(#{identity_reading(ctx.aggregate)}: #{Rendering.describe(ctx.instance.id)}) lost a race — " \
|
|
@@ -413,28 +323,12 @@ module Hecks
|
|
|
413
323
|
end
|
|
414
324
|
end
|
|
415
325
|
|
|
416
|
-
# The one-time, synchronous half of `projects` (
|
|
417
|
-
#
|
|
418
|
-
#
|
|
419
|
-
#
|
|
420
|
-
#
|
|
421
|
-
#
|
|
422
|
-
# some synchronous population, a projected field never gets an
|
|
423
|
-
# initial value at all until an operator remembers to run a
|
|
424
|
-
# sweep by hand — every acting command reading it (`given
|
|
425
|
-
# ("customer is active") { customer_status == "active" }`, say)
|
|
426
|
-
# would refuse a freshly created, genuinely active record for no
|
|
427
|
-
# real reason, which is not the eventual-consistency tradeoff the
|
|
428
|
-
# ADR accepts, just a bug. So: every time a record with `projects`
|
|
429
|
-
# fields is about to save — creating or acting, either can be the
|
|
430
|
-
# first time a referenced record resolves — read each one once,
|
|
431
|
-
# here, using the exact same `RebuildSweep.remote_value` a sweep
|
|
432
|
-
# itself would compute. This is still eventually consistent in
|
|
433
|
-
# the sense the ADR means: a change on the target side after this
|
|
434
|
-
# save still needs a sweep to reach here. It is only ever
|
|
435
|
-
# synchronous with this record's own write, never a live read
|
|
436
|
-
# triggered by a `given`/`ensures`/`invariant` mid-dispatch — the
|
|
437
|
-
# boundary rule those enforce holds exactly as before.
|
|
326
|
+
# The one-time, synchronous half of `projects` (ADR 0025); `RebuildSweep`
|
|
327
|
+
# is what keeps a projected field current afterward. Without seeding it
|
|
328
|
+
# here, a freshly created record would read a nil projected field until
|
|
329
|
+
# an operator ran a sweep, refusing commands that depend on it for no
|
|
330
|
+
# real reason. Uses the same `RebuildSweep.remote_value` a sweep
|
|
331
|
+
# computes, and runs only at save time, never as a live read mid-dispatch.
|
|
438
332
|
def seed_projected_fields(ctx)
|
|
439
333
|
return if ctx.aggregate.projected_fields.empty?
|
|
440
334
|
|
|
@@ -446,16 +340,11 @@ module Hecks
|
|
|
446
340
|
end
|
|
447
341
|
end
|
|
448
342
|
|
|
449
|
-
# A delegating command emits nothing of its own
|
|
450
|
-
#
|
|
451
|
-
#
|
|
452
|
-
# `
|
|
453
|
-
#
|
|
454
|
-
# announced events at all.
|
|
455
|
-
#
|
|
456
|
-
# `dry_run:` skips this too, same reasoning as `step_save` — nothing
|
|
457
|
-
# was committed, so `ctx.result` stays nil and `Dispatcher#dry_run?`
|
|
458
|
-
# never reads it (its own return value is just whether this raised).
|
|
343
|
+
# A delegating command emits nothing of its own; its result is the
|
|
344
|
+
# target entity command's own `emits`, parked by
|
|
345
|
+
# `step_delegate_to_entity` and emitted here, after save (C7.2).
|
|
346
|
+
# `dry_run:` skips this too — nothing was committed, so `ctx.result`
|
|
347
|
+
# stays nil.
|
|
459
348
|
def step_emit(ctx)
|
|
460
349
|
return if ctx.dry_run
|
|
461
350
|
|
|
@@ -500,61 +389,12 @@ module Hecks
|
|
|
500
389
|
found.dup
|
|
501
390
|
end
|
|
502
391
|
|
|
503
|
-
#
|
|
504
|
-
#
|
|
505
|
-
#
|
|
506
|
-
#
|
|
507
|
-
#
|
|
508
|
-
#
|
|
509
|
-
# pizzas, chess, compliance, roster) found and fixed 4 real
|
|
510
|
-
# authoring bugs (`Statement.Generate`, `CreatePizza`, `Chess::
|
|
511
|
-
# Game.Start`, `Roster.Open` — each declared an attribute and
|
|
512
|
-
# never `sets` it, so the field silently stayed nil on every
|
|
513
|
-
# created record regardless of what a caller sent), plus a real
|
|
514
|
-
# `DependencyPlanning::Analyzer` bug for entity-owned commands
|
|
515
|
-
# (`root_aggregate:`, that class's own header) that surfaced one
|
|
516
|
-
# more live corpus bug of its own (`payment_cards.bluebook`'s own
|
|
517
|
-
# `Withdrawal.Dispute`, which never actually checked whether the
|
|
518
|
-
# card was retired) — all real, all kept.
|
|
519
|
-
#
|
|
520
|
-
# Deleting this method on that basis turned out to be wrong,
|
|
521
|
-
# caught by running the full suite rather than trusting the
|
|
522
|
-
# audit's own scope: the 5 example domains are nowhere near the
|
|
523
|
-
# whole inventory of `creates?`-true commands this fallback
|
|
524
|
-
# actually carries. Dozens of separate, purpose-built spec
|
|
525
|
-
# fixtures across the suite (`spec/fixtures/*.bluebook`, and
|
|
526
|
-
# inline `Hecks.bluebook` blocks declared directly inside
|
|
527
|
-
# individual spec files — governance, mutation ops, ports,
|
|
528
|
-
# routing, tenant isolation, sagas, and more) declare their own
|
|
529
|
-
# small `creates?`-true commands the same incomplete way, and
|
|
530
|
-
# rely on this exact fallback to create anything at all. Deleting
|
|
531
|
-
# it produced 218 failures across specs with nothing to do with
|
|
532
|
-
# Wave 8's own subject — confirmed, not guessed, by actually
|
|
533
|
-
# running `bundle exec rspec` after the deletion, not merely by
|
|
534
|
-
# extrapolating from the one audited inventory. `spec/fixtures/
|
|
535
|
-
# till.bluebook`'s own `Till.OpenTill` (missing `sets :number`,
|
|
536
|
-
# the identical bug shape as the 4 fixed above) was fixed
|
|
537
|
-
# alongside the four real corpus ones as one instance of this — but
|
|
538
|
-
# it was one of many, not the last one, and finding the rest is
|
|
539
|
-
# real, separate, much larger follow-up work this pass does not
|
|
540
|
-
# attempt: a suite-wide audit of every declared bluebook fixture,
|
|
541
|
-
# not just the 5 example domains the plan's own text named.
|
|
542
|
-
#
|
|
543
|
-
# Transitional compatibility for live source that has not yet
|
|
544
|
-
# acquired explicit effects, same as it always was — deliberately
|
|
545
|
-
# isolated from the normal routing and planning path so
|
|
546
|
-
# `reference_to` no longer chooses how a migrated command hydrates
|
|
547
|
-
# or persists. A real, future Wave 8 removes this once that wider
|
|
548
|
-
# inventory is empty, not before.
|
|
549
|
-
#
|
|
550
|
-
# `ctx.plan.complete_state?` already claimed every command whose
|
|
551
|
-
# plan is fully resolved in the two branches above `step_hydrate`
|
|
552
|
-
# tries first, so reaching this check already means the plan is
|
|
553
|
-
# incomplete — an un-migrated command still routes here on
|
|
554
|
-
# `creates?` alone, the same as the old `hydrate` did, regardless
|
|
555
|
-
# of whether it happens to have any mutations (`write_set`).
|
|
556
|
-
# Requiring an empty write_set here refused every un-migrated
|
|
557
|
-
# creating command that sets even one field.
|
|
392
|
+
# Transitional compatibility for a command that has not yet acquired
|
|
393
|
+
# explicit effects — isolated from the normal routing/planning path so
|
|
394
|
+
# `reference_to` does not choose how a migrated command hydrates. An
|
|
395
|
+
# un-migrated command routes here on `creates?` alone, regardless of
|
|
396
|
+
# whether it has any mutations; requiring an empty write_set here would
|
|
397
|
+
# wrongly exclude one that sets fields.
|
|
558
398
|
def legacy_implicit_creation?(ctx)
|
|
559
399
|
ctx.route.nil? && ctx.command.creates?
|
|
560
400
|
end
|
|
@@ -587,19 +427,10 @@ module Hecks
|
|
|
587
427
|
aggregate: aggregate.hecks_name,
|
|
588
428
|
identity: identity_reading(aggregate)))
|
|
589
429
|
|
|
590
|
-
#
|
|
591
|
-
#
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
# dependent case, below. Only when `strategy` will not be ATOMIC_PUT:
|
|
595
|
-
# an atomic-put-capable adapter enforces this itself, atomically,
|
|
596
|
-
# via `insert_only:` in step_save (no read here, no race with the
|
|
597
|
-
# write) — but `strategy_for` already fell back to a plain `save`
|
|
598
|
-
# for an adapter with no atomic_put capability at all (Heki, today),
|
|
599
|
-
# and a plain `save` never refuses an overwrite on its own. Skipping
|
|
600
|
-
# this check for that case silently dropped the refusal instead of
|
|
601
|
-
# deferring it — the very "no such call — no such check" gap Wave
|
|
602
|
-
# 8 exists to close everywhere else.
|
|
430
|
+
# `creates?` on an occupied identity refuses (`AlreadyExists`), except
|
|
431
|
+
# under ATOMIC_PUT: that adapter enforces the same refusal itself,
|
|
432
|
+
# atomically, via `insert_only:` in `step_save` — reading here too
|
|
433
|
+
# would race the write for nothing.
|
|
603
434
|
if command.creates? && strategy != DependencyPlanning::ATOMIC_PUT && repository.find(id)
|
|
604
435
|
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
605
436
|
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
@@ -610,11 +441,9 @@ module Hecks
|
|
|
610
441
|
Instance.new(aggregate: aggregate, id: id, args: args)
|
|
611
442
|
end
|
|
612
443
|
|
|
613
|
-
# A complete command may still depend on prior state
|
|
614
|
-
# are the common case
|
|
615
|
-
# its real state
|
|
616
|
-
# are the prior state. Completeness, not a reference marker, proves that
|
|
617
|
-
# initializing that missing record is safe.
|
|
444
|
+
# A complete command may still depend on prior state (lifecycle guards
|
|
445
|
+
# are the common case) — read once, so an existing record is checked
|
|
446
|
+
# against its real state rather than assumed defaults.
|
|
618
447
|
def hydrate_prior_or_initial(repository, aggregate, command, args, route)
|
|
619
448
|
derived = identity_of(aggregate, args)
|
|
620
449
|
if route && derived && route.aggregate.to_s != derived.to_s
|
|
@@ -629,20 +458,10 @@ module Hecks
|
|
|
629
458
|
identity: identity_reading(aggregate)))
|
|
630
459
|
found = repository.find(id)
|
|
631
460
|
|
|
632
|
-
#
|
|
633
|
-
#
|
|
634
|
-
# a
|
|
635
|
-
#
|
|
636
|
-
# of hydrate_complete_state). Gated on `command.creates?`: a
|
|
637
|
-
# non-creating command's own complete payload legitimately means
|
|
638
|
-
# "act on whatever this identity already holds" (`found` is the
|
|
639
|
-
# prior state this branch exists to supply), so only a genuine
|
|
640
|
-
# creation reusing an already-occupied identity is a duplicate.
|
|
641
|
-
# `SafeDepositBox.Rent` is exactly this shape — `given("box is
|
|
642
|
-
# vacant")` makes it state-dependent, so without this check a
|
|
643
|
-
# second Rent would silently hydrate the existing box as "prior
|
|
644
|
-
# state" and refuse for the wrong reason (not vacant) instead of
|
|
645
|
-
# the right one (already exists).
|
|
461
|
+
# Same refusal as `hydrate_complete_state`, for a complete-but-state-
|
|
462
|
+
# dependent command (one with a `given` on its own prior state).
|
|
463
|
+
# Gated on `creates?`: a non-creating command's complete payload just
|
|
464
|
+
# means "act on whatever this identity already holds".
|
|
646
465
|
if found && command.creates?
|
|
647
466
|
raise(AlreadyExists, RefusalWording.render_site("AlreadyExists", "creating_duplicate",
|
|
648
467
|
command: command.hecks_name, aggregate: aggregate.hecks_name,
|
|
@@ -653,10 +472,9 @@ module Hecks
|
|
|
653
472
|
found ? found.dup : Instance.new(aggregate: aggregate, id: id, args: args)
|
|
654
473
|
end
|
|
655
474
|
|
|
656
|
-
#
|
|
657
|
-
#
|
|
658
|
-
#
|
|
659
|
-
# here, at the old names, purely so nothing below has to change.
|
|
475
|
+
# Shared with `EntityInterpreter`, in `Runtime::Identity`, rather than
|
|
476
|
+
# kept as two copies that could only ever drift; these three stay here,
|
|
477
|
+
# under their old names, so nothing below has to change.
|
|
660
478
|
def identity_of(aggregate, args) = Identity.of(aggregate, args)
|
|
661
479
|
def identity_from(aggregate, args, key) = Identity.from(aggregate, args, key)
|
|
662
480
|
def identity_reading(construct) = Identity.reading(construct)
|