hecks 2.8.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/exe/hecks +4 -6
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +17 -98
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +31 -141
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +9 -90
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +150 -678
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +2 -15
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +99 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +141 -568
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +26 -81
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +3 -2
|
@@ -7,31 +7,12 @@ require_relative "../../ports/query/in_memory"
|
|
|
7
7
|
module Hecks
|
|
8
8
|
module Runtime
|
|
9
9
|
class CommandRules
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# `reference_to Customer` is the one guarantee an aggregate reference is
|
|
13
|
-
# for, and it was declared 14 times across banking and enforced nowhere :
|
|
14
|
-
# an Account could belong to a customer who was never registered, and
|
|
15
|
-
# every gate stayed green because
|
|
16
|
-
# no corpus step ever passed a dangling reference.
|
|
10
|
+
# Refuses a `reference_to` whose target identity does not exist.
|
|
17
11
|
module References
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# cross-domain target may legitimately not be loaded, which is the same
|
|
21
|
-
# reading `across` policies already get.
|
|
12
|
+
# Checks reference-typed command arguments against their target repositories.
|
|
13
|
+
# Cross-domain targets are skipped: they may legitimately not be loaded.
|
|
22
14
|
#
|
|
23
|
-
#
|
|
24
|
-
# can declare a reference-typed attribute the same way an aggregate
|
|
25
|
-
# command can, even though nothing in the real corpus does yet.
|
|
26
|
-
#
|
|
27
|
-
# @param domain [String, Symbol] the domain the referenced aggregate is
|
|
28
|
-
# resolved in
|
|
29
|
-
# @param command [Class] the command or port-operation class (`Bluebook::Command`
|
|
30
|
-
# or `PortOperation` subclass) whose reference-typed attributes are checked
|
|
31
|
-
# @param args [Hash{Symbol => Object}] the offered, already-coerced argument values
|
|
32
|
-
# @return [void]
|
|
33
|
-
# @raise [Runtime::NotFound] if a reference-typed argument names an identity that
|
|
34
|
-
# does not exist in its target aggregate's repository
|
|
15
|
+
# @raise [Runtime::NotFound] if an argument names an identity that does not exist
|
|
35
16
|
def resolve_references(domain, command, args)
|
|
36
17
|
command.attributes.each do |attribute|
|
|
37
18
|
next unless attribute.reference?
|
|
@@ -47,23 +28,11 @@ module Hecks
|
|
|
47
28
|
end
|
|
48
29
|
end
|
|
49
30
|
|
|
50
|
-
#
|
|
51
|
-
# This is what makes a `has_many` declared on an aggregate honest even
|
|
52
|
-
# when a command supplies its list through an ordinary typed argument.
|
|
31
|
+
# Re-checks references, cardinality and tenant agreement against settled state.
|
|
53
32
|
#
|
|
54
|
-
# @
|
|
55
|
-
#
|
|
56
|
-
# @
|
|
57
|
-
# whose reference-typed attributes are checked against `state`
|
|
58
|
-
# @param state [Hash{Symbol => Object}] the settled, post-mutation state to
|
|
59
|
-
# check every reference and relationship cardinality against
|
|
60
|
-
# @return [void]
|
|
61
|
-
# @raise [Runtime::TypeMismatch] if a required `has_one`/`belongs_to`
|
|
62
|
-
# relationship holds nil
|
|
63
|
-
# @raise [Runtime::NotFound] if a reference-typed field names an identity
|
|
64
|
-
# that does not exist in its target aggregate's repository
|
|
65
|
-
# @raise [Runtime::Unauthorized] if a referenced record belongs to a
|
|
66
|
-
# different tenant than `construct`'s own record
|
|
33
|
+
# @raise [Runtime::TypeMismatch] if a required `has_one`/`belongs_to` holds nil
|
|
34
|
+
# @raise [Runtime::NotFound] if a reference names an identity that does not exist
|
|
35
|
+
# @raise [Runtime::Unauthorized] if a referenced record belongs to another tenant
|
|
67
36
|
def resolve_state_references(domain, construct, state)
|
|
68
37
|
own_tenant_field = tenant_field_for(construct)
|
|
69
38
|
|
|
@@ -90,20 +59,8 @@ module Hecks
|
|
|
90
59
|
end
|
|
91
60
|
end
|
|
92
61
|
|
|
93
|
-
# `has_one` and `belongs_to`
|
|
94
|
-
#
|
|
95
|
-
# boundary because a command may leave an aggregate field untouched;
|
|
96
|
-
# checking only command arguments would let a required relationship be
|
|
97
|
-
# persisted as nil. `has_many` admits zero members, so its empty list is
|
|
98
|
-
# already a valid cardinality and needs no presence refusal.
|
|
99
|
-
#
|
|
100
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
101
|
-
# `attribute` is declared on, named in a refusal
|
|
102
|
-
# @param attribute [Bluebook::Attribute] the reference-typed attribute to check
|
|
103
|
-
# @param held [Object, nil] the field's settled value
|
|
104
|
-
# @return [void]
|
|
105
|
-
# @raise [Runtime::TypeMismatch] if `attribute` declares a required (non-optional,
|
|
106
|
-
# non-list) `has_one`/`belongs_to` relationship and `held` is nil
|
|
62
|
+
# `has_one` and `belongs_to` require a target unless optional. Checked on state,
|
|
63
|
+
# since a command may leave the field untouched.
|
|
107
64
|
def validate_relationship_cardinality(construct, attribute, held)
|
|
108
65
|
return if attribute.relationship.nil? || attribute.list?
|
|
109
66
|
return unless held.nil? && !attribute.optional?
|
|
@@ -114,32 +71,14 @@ module Hecks
|
|
|
114
71
|
"#{attribute.type.target_name} identity, got nil"
|
|
115
72
|
end
|
|
116
73
|
|
|
117
|
-
#
|
|
118
|
-
# bluebook's declared heads are the index, rather than regexing the
|
|
119
|
-
# target's name out of "Reference<Customer>" and searching
|
|
120
|
-
# `registry.bluebook(domain).aggregates` for it, or reaching the
|
|
121
|
-
# target through Ruby's constant tree, a class thrown away for its
|
|
122
|
-
# `.ir` the moment it was found.
|
|
74
|
+
# Resolves the attribute's target through the chapter IR.
|
|
123
75
|
#
|
|
124
|
-
# @
|
|
125
|
-
# @return [Bluebook::Aggregate, nil] the target aggregate the reference names,
|
|
126
|
-
# or nil if it cannot be resolved
|
|
127
|
-
# @raise [Bluebook::DSL::Malformed] if the reference does not know which
|
|
128
|
-
# aggregate declares it
|
|
76
|
+
# @raise [Bluebook::DSL::Malformed] if the reference does not know its declaring aggregate
|
|
129
77
|
def referenced_aggregate(attribute)
|
|
130
78
|
attribute.type.resolve
|
|
131
79
|
end
|
|
132
80
|
|
|
133
81
|
# Refuses a reference-typed value whose target identity does not exist.
|
|
134
|
-
#
|
|
135
|
-
# @param domain [String, Symbol] the domain `target` is resolved in
|
|
136
|
-
# @param target [Bluebook::Aggregate] the referenced aggregate
|
|
137
|
-
# @param held [Object, Array<Object>] the offered value: a single reference,
|
|
138
|
-
# or, when `list:` is true, an Array of them
|
|
139
|
-
# @param list [Boolean] whether `held` is list-shaped
|
|
140
|
-
# @return [void]
|
|
141
|
-
# @raise [Runtime::NotFound] if any referenced identity does not exist in
|
|
142
|
-
# `target`'s repository
|
|
143
82
|
def validate_reference_values(domain, target, held, list:)
|
|
144
83
|
values = list ? Array(held) : [held]
|
|
145
84
|
values.each do |value|
|
|
@@ -154,47 +93,8 @@ module Hecks
|
|
|
154
93
|
end
|
|
155
94
|
end
|
|
156
95
|
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
# module turns a declared `authorize policy, tenant: :field` into a
|
|
160
|
-
# synthetic where-clause checked against the caller's own supplied
|
|
161
|
-
# tenant argument; there is no caller-identity/session system this
|
|
162
|
-
# runtime has to check a write's caller against (TenantScope's own
|
|
163
|
-
# header names that as a separate, still-open gap), so this checks
|
|
164
|
-
# the one thing that is available without one: whether the record
|
|
165
|
-
# being written and the record it references agree about which
|
|
166
|
-
# tenant they belong to. `lib/hecks/fuzzing/properties/guards.rb`'s
|
|
167
|
-
# `commands_respect_tenant_scope` states the identical claim,
|
|
168
|
-
# read off `history[:instances]` after the fact — this is what
|
|
169
|
-
# makes that claim hold by construction (a refused write is never
|
|
170
|
-
# stored) rather than merely checked for regression.
|
|
171
|
-
#
|
|
172
|
-
# Hooked into `resolve_state_references` rather than a new
|
|
173
|
-
# DISPATCH_ORDER step deliberately: that method already walks
|
|
174
|
-
# every `reference_to`-typed attribute against the settled,
|
|
175
|
-
# post-mutation state (the same moment `commands_respect_tenant_
|
|
176
|
-
# scope` itself inspects), already resolves the referenced record
|
|
177
|
-
# through the repository right above, and already runs from both
|
|
178
|
-
# `CommandInterpreter#step_save` and `EntityInterpreter#step_save`
|
|
179
|
-
# — one change, both interpreters covered, no new vocabulary step
|
|
180
|
-
# to keep in sync with `Vocabulary::AggregateDispatchOrder`/
|
|
181
|
-
# `EntityDispatchOrder`.
|
|
182
|
-
#
|
|
183
|
-
# @param domain [String, Symbol] the domain `target` is resolved in
|
|
184
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
185
|
-
# `attribute` is declared on, named in a refusal
|
|
186
|
-
# @param attribute [Bluebook::Attribute] the reference-typed attribute being
|
|
187
|
-
# checked
|
|
188
|
-
# @param target [Bluebook::Aggregate] the referenced aggregate
|
|
189
|
-
# @param held [Object, Array<Object>] the offered value: a single reference,
|
|
190
|
-
# or, when `attribute.list?` is true, an Array of them
|
|
191
|
-
# @param state [Hash{Symbol => Object}] `construct`'s own settled state, read
|
|
192
|
-
# for its own tenant field's value
|
|
193
|
-
# @param own_tenant_field [Symbol, nil] `construct`'s own declared tenant field;
|
|
194
|
-
# a no-op if nil or absent from `state`
|
|
195
|
-
# @return [void]
|
|
196
|
-
# @raise [Runtime::Unauthorized] if a referenced record's own tenant field
|
|
197
|
-
# disagrees with `construct`'s
|
|
96
|
+
# Refuses a write whose record and referenced record disagree about their tenant.
|
|
97
|
+
# Hooked into `resolve_state_references` so both interpreters are covered.
|
|
198
98
|
def enforce_tenant_boundary(domain, construct, attribute, target, held, state, own_tenant_field)
|
|
199
99
|
return unless own_tenant_field && state.key?(own_tenant_field)
|
|
200
100
|
|
|
@@ -224,54 +124,16 @@ module Hecks
|
|
|
224
124
|
end
|
|
225
125
|
end
|
|
226
126
|
|
|
227
|
-
# The field
|
|
228
|
-
# exact same lookup `Fuzzing::Properties::Guards#tenant_field_for`
|
|
229
|
-
# already established for the property that found this gap, reused
|
|
230
|
-
# here rather than reinvented: an aggregate's own declared tenant
|
|
231
|
-
# field is whichever field one of its own queries names in
|
|
232
|
-
# `authorize policy, tenant: :field`. `nil` for a construct that
|
|
233
|
-
# declares no such query — not every aggregate is tenant-scoped,
|
|
234
|
-
# and an entity never declares a query of its own at all today
|
|
235
|
-
# (`Entity.queries` is always empty in the real corpus), so this
|
|
236
|
-
# answers `nil` for every entity without needing to special-case
|
|
237
|
-
# one.
|
|
238
|
-
#
|
|
239
|
-
# @param construct [Bluebook::Aggregate, Bluebook::Entity] the construct
|
|
240
|
-
# whose declared queries are searched
|
|
241
|
-
# @return [Symbol, nil] the tenant-scoping field name, or nil if `construct`
|
|
242
|
-
# declares no query with `authorize policy, tenant: :field`
|
|
127
|
+
# The field a query names in `authorize policy, tenant: :field`, or nil.
|
|
243
128
|
def tenant_field_for(construct)
|
|
244
129
|
authorization = construct.queries.filter_map(&:authorization).find(&:tenant)
|
|
245
130
|
authorization&.tenant&.to_sym
|
|
246
131
|
end
|
|
247
132
|
|
|
248
|
-
# Renders a reference
|
|
249
|
-
# record is looked up by.
|
|
250
|
-
#
|
|
251
|
-
# `value` is the referenced record's own id, exactly as `Identity.of`
|
|
252
|
-
# would build it for that record — a bare scalar for a single-field
|
|
253
|
-
# identity (the overwhelming common case; Banking's own plain
|
|
254
|
-
# `reference_to Customer` holds one already, so `Value.
|
|
255
|
-
# materialize_unwrapped` is a no-op passthrough here), or a
|
|
256
|
-
# `Naming.identity`-joined string for a compound one
|
|
257
|
-
# (`belongs_to Translation, as: :translation_ref` — Translation's
|
|
258
|
-
# own `identified_by :domain, :from, :to`, three fields). Without
|
|
259
|
-
# `materialize_unwrapped`, plain `value.to_s` on that compound case's
|
|
260
|
-
# own coerced Value would hit Ruby's default `Object#to_s` (a raw,
|
|
261
|
-
# run-to-run-random memory address) instead of joining the record's
|
|
262
|
-
# real id — found live via bin/fuzz on the self-hosted "translation"
|
|
263
|
-
# domain (replay_is_deterministic), the same class of gap `Identity.from`
|
|
264
|
-
# already had for a compound `identified_by`'s own bare (undotted)
|
|
265
|
-
# attribute paths. `materialize_unwrapped` recurses a multi-
|
|
266
|
-
# attribute value object to a plain Hash keyed by attribute name,
|
|
267
|
-
# in declaration order — `Naming.identity` on `.values` reproduces
|
|
268
|
-
# the identical join `Identity.of` itself would produce for the
|
|
269
|
-
# same fields.
|
|
133
|
+
# Renders a reference value as the string key its target record is looked up by.
|
|
270
134
|
#
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
# @return [String] the key to look the referenced record up by; empty when
|
|
274
|
-
# `value` names no id
|
|
135
|
+
# A compound identity arrives as a Value; `materialize_unwrapped` is needed so it joins
|
|
136
|
+
# like `Identity.of` instead of rendering `Object#to_s`.
|
|
275
137
|
def reference_key(value)
|
|
276
138
|
unwrapped = Value.materialize_unwrapped(value)
|
|
277
139
|
return Naming.identity(unwrapped.values).to_s if unwrapped.is_a?(Hash)
|
|
@@ -279,56 +141,15 @@ module Hecks
|
|
|
279
141
|
unwrapped.to_s
|
|
280
142
|
end
|
|
281
143
|
|
|
282
|
-
#
|
|
283
|
-
# from `given`/`ensures` — `disputed_by.status`, say, `CardPayment
|
|
284
|
-
# .Dispute`'s own fresh `Reference<Customer>` argument — without
|
|
285
|
-
# teaching the pure expression evaluator anything about
|
|
286
|
-
# repositories. The lookup happens here, once, before evaluation;
|
|
287
|
-
# `Resolver#lookup` just digs into a plain Hash exactly as it
|
|
288
|
-
# always has.
|
|
289
|
-
#
|
|
290
|
-
# `owner` narrowed to `command` only (S12, ADR 0025 — "rules
|
|
291
|
-
# confined to their own aggregate boundary"): dereferencing the
|
|
292
|
-
# declaring aggregate/entity's own stored `reference_to` is not part
|
|
293
|
-
# of this method's job — a cross-aggregate fact a rule needs has to
|
|
294
|
-
# be a `projects`-maintained local field (`AggregateBuilder#
|
|
295
|
-
# projects_impl`'s own comment), already present in `subject`'s own
|
|
296
|
-
# state, no hydration needed, rather than a live query against
|
|
297
|
-
# another aggregate's own repository on every `given`/`ensures`/
|
|
298
|
-
# `invariant` run. A reference-typed command argument stays in
|
|
299
|
-
# bounds, though — the ADR's own boundary list names "its command
|
|
300
|
-
# arguments" as readable, and nothing is stored yet for a fresh
|
|
301
|
-
# argument to project from; resolving it once here, synchronous
|
|
302
|
-
# with this command's own admission, is a different shape from a
|
|
303
|
-
# live query against an already-persisted reference. `enforce_givens`/
|
|
304
|
-
# `enforce_ensures` are this method's only two remaining callers,
|
|
305
|
-
# both passing `command`/`args`, never a `subject`'s own
|
|
306
|
-
# aggregate — verified by grep, not assumed.
|
|
307
|
-
#
|
|
308
|
-
# Recurses into what it finds, so a chain deeper than one hop
|
|
309
|
-
# still resolves in one pass. Depth-bounded rather than cycle-
|
|
310
|
-
# detected — nothing in this corpus dots more than two hops on a
|
|
311
|
-
# fresh argument, and a bound is simpler than tracking visited
|
|
312
|
-
# (type, id) pairs for a cycle nothing here declares.
|
|
144
|
+
# Recursion bound; simpler than cycle detection for a cycle no corpus rule declares.
|
|
313
145
|
DEREFERENCE_DEPTH = 4
|
|
314
146
|
private_constant :DEREFERENCE_DEPTH
|
|
315
147
|
|
|
316
|
-
# Hydrates
|
|
148
|
+
# Hydrates reference-typed attributes into the records they name, so `given`/`ensures`
|
|
149
|
+
# can dot into them. Only command arguments are dereferenced (ADR 0025); a stored
|
|
150
|
+
# reference must be a `projects` field.
|
|
317
151
|
#
|
|
318
|
-
# @
|
|
319
|
-
# resolved in
|
|
320
|
-
# @param owner [Class, Bluebook::Aggregate, Bluebook::Entity, nil] the
|
|
321
|
-
# construct whose reference-typed attributes are dereferenced; nil (or a
|
|
322
|
-
# depth of zero) short-circuits to an empty Hash
|
|
323
|
-
# @param source [Hash{Symbol => Object}] the offered payload to read each
|
|
324
|
-
# reference-typed attribute's raw id from
|
|
325
|
-
# @param depth [Integer] how many more hops to recurse into a resolved
|
|
326
|
-
# reference's own reference-typed fields
|
|
327
|
-
# @return [Hash{Symbol => Object}] one entry per resolved reference-typed
|
|
328
|
-
# attribute, keyed by the attribute name with a trailing `_id` stripped,
|
|
329
|
-
# valued at the referenced record's state merged with its own dereferenced
|
|
330
|
-
# references; an attribute with no id, an unresolvable target, or no
|
|
331
|
-
# matching record contributes nothing
|
|
152
|
+
# @return [Hash{Symbol => Object}] keyed by attribute name with `_id` stripped
|
|
332
153
|
def dereference(domain, owner, source, depth: DEREFERENCE_DEPTH)
|
|
333
154
|
return {} if depth <= 0 || owner.nil?
|
|
334
155
|
|
|
@@ -2,8 +2,7 @@ require_relative "../bluebook/expression"
|
|
|
2
2
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Runtime
|
|
5
|
-
# Persistence-neutral facts derived from a command's
|
|
6
|
-
# This module deliberately does not execute a plan or choose a repository.
|
|
5
|
+
# Persistence-neutral facts derived from a command's semantic IR; it plans, never executes.
|
|
7
6
|
module DependencyPlanning
|
|
8
7
|
ATOMIC_PUT = :atomic_put
|
|
9
8
|
TRANSACTIONAL_FALLBACK = :load_apply_validate_store
|
|
@@ -17,23 +16,18 @@ module Hecks
|
|
|
17
16
|
:unresolved_dependencies,
|
|
18
17
|
keyword_init: true
|
|
19
18
|
) do
|
|
20
|
-
#
|
|
21
|
-
# deterministic write.
|
|
19
|
+
# Whether every owner field this command could touch is a known, deterministic write.
|
|
22
20
|
#
|
|
23
21
|
# @return [Boolean] `complete_state`
|
|
24
22
|
def complete_state? = complete_state
|
|
25
23
|
|
|
26
|
-
#
|
|
24
|
+
# Whether the command needs no prior state at all (implies `complete_state?`).
|
|
27
25
|
#
|
|
28
26
|
# @return [Boolean] `state_independent`
|
|
29
27
|
def state_independent? = state_independent
|
|
30
28
|
|
|
31
|
-
# Chooses the dispatch strategy
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
# Capability negotiation is correctness-first: an optimization is
|
|
35
|
-
# selected only when both the semantic proof and adapter capability
|
|
36
|
-
# are present. This is planning data only; no runtime path calls it yet.
|
|
29
|
+
# Chooses the dispatch strategy the plan's proof and the adapter's capabilities allow.
|
|
30
|
+
# An optimization is selected only when both are present.
|
|
37
31
|
#
|
|
38
32
|
# @param capabilities [Array<String, Symbol>] the repository's declared capabilities
|
|
39
33
|
# @return [Symbol] `DependencyPlanning::ATOMIC_PUT` when the plan is complete,
|
|
@@ -47,34 +41,22 @@ module Hecks
|
|
|
47
41
|
end
|
|
48
42
|
end
|
|
49
43
|
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
# dotted paths it reads — used by Analyzer to classify a
|
|
53
|
-
# given/ensures/invariant rule's dependencies without evaluating it.
|
|
44
|
+
# Collects the dotted paths a canonical expression reads, without evaluating it.
|
|
45
|
+
# Used by Analyzer to classify rule dependencies.
|
|
54
46
|
module ExpressionReads
|
|
55
47
|
module_function
|
|
56
48
|
|
|
57
|
-
# Finds every dotted path a canonical expression reads
|
|
49
|
+
# Finds every dotted path a canonical expression reads.
|
|
58
50
|
#
|
|
59
|
-
#
|
|
60
|
-
# A generic Struct walk keeps this additive when the expression grammar
|
|
61
|
-
# gains a composed node; only Lookup nodes carry domain dependencies.
|
|
51
|
+
# Walks the parsed nodes the evaluator uses; only Lookup nodes carry dependencies.
|
|
62
52
|
#
|
|
63
|
-
# @param canonical [String] the canonical expression text
|
|
53
|
+
# @param canonical [String] the canonical expression text
|
|
64
54
|
# @return [Array<String>] the dotted paths the expression reads
|
|
65
55
|
def paths(canonical)
|
|
66
56
|
collect(Bluebook::Expression::Evaluator.parse(canonical), Set.new)
|
|
67
57
|
end
|
|
68
58
|
|
|
69
|
-
# Walks one parsed
|
|
70
|
-
#
|
|
71
|
-
# @param node [Object] a node from `Bluebook::Expression::Evaluator.parse` — a
|
|
72
|
-
# `Bluebook::Expression::Resolver::Lookup`, a `Bluebook::Expression::Resolver::
|
|
73
|
-
# BlockPredicate`, a `Struct` composed of further nodes, an `Array` of them, or any
|
|
74
|
-
# other value (a literal), which contributes no paths
|
|
75
|
-
# @param bound_names [Set<String>] names locally bound by an enclosing block
|
|
76
|
-
# predicate's own parameter, excluded from the result rather than reported as reads
|
|
77
|
-
# @return [Array<String>] the dotted paths read under `node`, excluding `bound_names`
|
|
59
|
+
# Walks one parsed node, skipping names bound by an enclosing block predicate.
|
|
78
60
|
def collect(node, bound_names)
|
|
79
61
|
case node
|
|
80
62
|
when Bluebook::Expression::Resolver::Lookup
|
|
@@ -93,30 +75,13 @@ module Hecks
|
|
|
93
75
|
end
|
|
94
76
|
end
|
|
95
77
|
|
|
96
|
-
# Static
|
|
97
|
-
#
|
|
98
|
-
# invariant rules to derive a Plan (read_set/write_set/
|
|
99
|
-
# complete_state?/state_independent?) describing what the command
|
|
100
|
-
# touches without executing it. `Analyzer.call` is what
|
|
101
|
-
# CommandInterpreter and EntityInterpreter both consult before
|
|
102
|
-
# choosing a dispatch strategy.
|
|
78
|
+
# Static dependency analysis for one command: derives a Plan without executing it.
|
|
79
|
+
# CommandInterpreter and EntityInterpreter consult it before choosing a dispatch strategy.
|
|
103
80
|
class Analyzer
|
|
104
81
|
STATEFUL_MUTATIONS = %i[append increment decrement multiply clamp remove].freeze
|
|
105
82
|
|
|
106
|
-
# `root_aggregate:`
|
|
107
|
-
#
|
|
108
|
-
# `EntityInterpreter` calls this with `aggregate:` set to the
|
|
109
|
-
# entity itself (`element_interpreter.rb`'s own `Analyzer.call
|
|
110
|
-
# (aggregate: entity, command:)`), so `owner_fields` was always
|
|
111
|
-
# the entity's own attribute set. A `given`/`ensures` reading
|
|
112
|
-
# `parent.X` legitimately means the root aggregate's own field —
|
|
113
|
-
# a genuinely different owner — but `classify_path`'s `:parent`
|
|
114
|
-
# branch checked that read against `owner_fields` (the entity's),
|
|
115
|
-
# which can never contain a root-level field, so every entity
|
|
116
|
-
# command with a real, legitimate `parent.*` read was refused as
|
|
117
|
-
# unresolved regardless of correctness. Defaults to `aggregate`
|
|
118
|
-
# (a no-op) for the plain-aggregate case — `CommandInterpreter`'s
|
|
119
|
-
# own call site never needed to change.
|
|
83
|
+
# `root_aggregate:` is the owner a `parent.*` read resolves against. An entity-owned
|
|
84
|
+
# command passes `aggregate:` as the entity, whose fields never include root-level ones.
|
|
120
85
|
#
|
|
121
86
|
# @param aggregate [Bluebook::Aggregate, Bluebook::Entity] the construct the command
|
|
122
87
|
# is dispatched against; an entity for an entity-owned command
|
|
@@ -138,22 +103,9 @@ module Hecks
|
|
|
138
103
|
@owner_fields << aggregate.lifecycle.field.to_sym if aggregate.lifecycle
|
|
139
104
|
@root_owner_fields = root_aggregate.attributes.to_set(&:name)
|
|
140
105
|
@root_owner_fields << root_aggregate.lifecycle.field.to_sym if root_aggregate.lifecycle
|
|
141
|
-
# `projects` fields
|
|
142
|
-
# `
|
|
143
|
-
#
|
|
144
|
-
# as any attribute, even though nothing here writes it via a
|
|
145
|
-
# declared mutation (`CommandInterpreter#seed_projected_fields`
|
|
146
|
-
# populates it outside this analysis entirely). Left out of
|
|
147
|
-
# `known_writes` deliberately: `add_preservation_reads` then
|
|
148
|
-
# correctly treats it as a prior-state read that must survive
|
|
149
|
-
# a partial mutation, which is exactly right — a projected
|
|
150
|
-
# field's freshness comes from the interpreter reseeding it on
|
|
151
|
-
# save, not from anything a caller-supplied write set carries.
|
|
152
|
-
# Applies to both `owner_fields` and `root_owner_fields` — an
|
|
153
|
-
# entity's own `parent.*` read can name the root aggregate's
|
|
154
|
-
# projected field just as easily as one of its real attributes
|
|
155
|
-
# (`Banking::Withdrawal.Dispute`'s own `parent.account_customer_
|
|
156
|
-
# status`, ATMCard's projected field, is a real, live example).
|
|
106
|
+
# `projects` fields are owner state: a rule reading one reads this record's stored
|
|
107
|
+
# field. Left out of `known_writes` so `add_preservation_reads` keeps it as a read;
|
|
108
|
+
# the interpreter reseeds it on save. `parent.*` reads may name the root's too.
|
|
157
109
|
aggregate.projected_fields.each { |field| @owner_fields << field.name } if aggregate.respond_to?(:projected_fields)
|
|
158
110
|
if root_aggregate.respond_to?(:projected_fields)
|
|
159
111
|
root_aggregate.projected_fields.each do |field|
|
|
@@ -198,10 +150,8 @@ module Hecks
|
|
|
198
150
|
attr_reader :aggregate, :command, :owner_fields, :root_owner_fields, :payload_fields,
|
|
199
151
|
:state_reads, :payload_reads, :writes, :known_writes, :unresolved
|
|
200
152
|
|
|
201
|
-
# A fresh Instance supplies these values without
|
|
202
|
-
#
|
|
203
|
-
# establish completeness but are not command mutations, so they do not
|
|
204
|
-
# appear in write_set.
|
|
153
|
+
# A fresh Instance supplies these values without a stored record; keep aligned with
|
|
154
|
+
# Instance.defaults/default_for. They are not command mutations, so not in write_set.
|
|
205
155
|
def analyze_initial_state
|
|
206
156
|
aggregate.attributes.each do |attribute|
|
|
207
157
|
known_writes << attribute.name if deterministic_initial_value?(attribute)
|
|
@@ -239,8 +189,7 @@ module Hecks
|
|
|
239
189
|
end
|
|
240
190
|
end
|
|
241
191
|
|
|
242
|
-
#
|
|
243
|
-
# state. Hash sources are the canonical append binding shape.
|
|
192
|
+
# True only when the source is known without prior state. Hash sources are append bindings.
|
|
244
193
|
def analyze_source?(source)
|
|
245
194
|
case source
|
|
246
195
|
when Symbol
|
|
@@ -282,34 +231,16 @@ module Hecks
|
|
|
282
231
|
end
|
|
283
232
|
end
|
|
284
233
|
|
|
285
|
-
#
|
|
286
|
-
#
|
|
287
|
-
#
|
|
288
|
-
# `:parent` are — a given/ensures referencing it falls through to
|
|
289
|
-
# `unresolved` below (its own field lookup finds no owner/payload
|
|
290
|
-
# match), same net effect as any other not-yet-optimized command:
|
|
291
|
-
# `complete_state?` comes back false, so dispatch takes the safe
|
|
292
|
-
# `hydrate_existing` path instead of the `ATOMIC_PUT` fast path.
|
|
293
|
-
# Not a correctness bug — `as:`'s runtime binding (a plain `attrs`
|
|
294
|
-
# merge, exactly like `old:`'s) resolves and evaluates correctly
|
|
295
|
-
# regardless of what this static analysis concludes — just a real,
|
|
296
|
-
# deliberately-left optimization gap: closing it would mean
|
|
297
|
-
# threading "which names this command declares as correction
|
|
298
|
-
# bindings" into the Analyzer, which doesn't have that per-command
|
|
299
|
-
# context today. Worth doing alongside `:old`/`:parent`'s own
|
|
300
|
-
# handling someday, not attempted here.
|
|
234
|
+
# Gap: the `as:` name bound by `corrects` is not special-cased like `:old`/`:parent`, so a
|
|
235
|
+
# rule reading it lands in `unresolved` and dispatch takes the safe `hydrate_existing`
|
|
236
|
+
# path. Runtime binding is unaffected.
|
|
301
237
|
def classify_path(path, phase)
|
|
302
238
|
head, nested = path.split(".", 2)
|
|
303
239
|
name = head.to_sym
|
|
304
240
|
|
|
305
241
|
if name == :parent
|
|
306
|
-
# `
|
|
307
|
-
#
|
|
308
|
-
# `parent.X` always means the root aggregate's own field, a
|
|
309
|
-
# genuinely different owner (`root_aggregate:`'s own header,
|
|
310
|
-
# above, has the full bug this fixes). Identical for a plain
|
|
311
|
-
# aggregate command, where root_aggregate defaults to aggregate
|
|
312
|
-
# itself and the two sets are the same set.
|
|
242
|
+
# `parent.X` names the root aggregate's field, not the entity's `owner_fields`;
|
|
243
|
+
# the two sets are identical for a plain aggregate command.
|
|
313
244
|
resolve_nested_state_read!(path, nested, root_owner_fields, "does not name parent aggregate state")
|
|
314
245
|
elsif name == :old
|
|
315
246
|
resolve_nested_state_read!(path, nested, owner_fields, "does not name prior aggregate state")
|
|
@@ -322,10 +253,8 @@ module Hecks
|
|
|
322
253
|
end
|
|
323
254
|
end
|
|
324
255
|
|
|
325
|
-
# Shared
|
|
326
|
-
#
|
|
327
|
-
# (deliberately different sets for `parent`/`old` — see the caller),
|
|
328
|
-
# and either record it as a state read or refuse with `message`.
|
|
256
|
+
# Shared by the `:parent`/`:old` branches: checks the nested field against `field_set`
|
|
257
|
+
# and records a state read, or refuses with `message`.
|
|
329
258
|
def resolve_nested_state_read!(path, nested, field_set, message)
|
|
330
259
|
field = nested.to_s.split(".", 2).first
|
|
331
260
|
if field.empty? || !field_set.include?(field.to_sym)
|
|
@@ -335,9 +264,8 @@ module Hecks
|
|
|
335
264
|
end
|
|
336
265
|
end
|
|
337
266
|
|
|
338
|
-
# A partial mutation must preserve every untouched field
|
|
339
|
-
#
|
|
340
|
-
# names them. A deterministic write needs no preservation read.
|
|
267
|
+
# A partial mutation must preserve every untouched field, so those prior values are
|
|
268
|
+
# reads even when no rule names them.
|
|
341
269
|
def add_preservation_reads
|
|
342
270
|
state_reads.merge(owner_fields - known_writes)
|
|
343
271
|
end
|