hecks 1.3.0 → 1.5.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/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -16,22 +16,36 @@ module Hecks
|
|
|
16
16
|
class PolicyInterpreter
|
|
17
17
|
attr_reader :registry
|
|
18
18
|
|
|
19
|
+
# @param registry [Runtime::Registry] the booted registry whose loaded
|
|
20
|
+
# bluebooks are scanned for candidate policies
|
|
21
|
+
# @param door [Runtime::Dispatcher] the dispatcher reactions re-enter through,
|
|
22
|
+
# and whose reaction-depth guard is checked before each delivery
|
|
19
23
|
def initialize(registry, door:)
|
|
20
24
|
@registry = registry
|
|
21
25
|
@door = door
|
|
22
26
|
end
|
|
23
27
|
|
|
28
|
+
# Fires every declared policy `event` triggers, recording each outcome on
|
|
29
|
+
# the registry's reaction log.
|
|
30
|
+
#
|
|
24
31
|
# `deliver` returns `nil` for a policy whose `where` did not hold —
|
|
25
|
-
#
|
|
32
|
+
# silently, the same as a policy `policies_for` never selected at all
|
|
26
33
|
# (an `event_qualifier` miss carries no reaction_log entry either) —
|
|
27
|
-
# so nothing is appended for it. A `for_each` policy answers an
|
|
34
|
+
# so nothing is appended for it. A `for_each` policy answers an array
|
|
28
35
|
# (one record per matched row) rather than one record ; `Array(...)`
|
|
29
36
|
# is wrong here (it would explode a plain record Hash into its own
|
|
30
37
|
# key/value pairs), so the two shapes are told apart explicitly.
|
|
31
|
-
# `only:` —
|
|
38
|
+
# `only:` — one `[policy, home_domain]` pair, the outbox relay's
|
|
32
39
|
# way of running exactly the consumer a row names (`Runtime::
|
|
33
40
|
# Outbox::Relay#run_consumer`) instead of every policy that
|
|
34
41
|
# matches the event. Selection is otherwise identical.
|
|
42
|
+
#
|
|
43
|
+
# @param event [Runtime::Event] the just-emitted event to react to
|
|
44
|
+
# @param domain [String, Symbol] the domain `event`'s own aggregate belongs
|
|
45
|
+
# to, the emitting domain's policies fire first
|
|
46
|
+
# @param only [Array(Bluebook::Policy, String), nil] one `[policy, home_domain]`
|
|
47
|
+
# pair to run exactly, instead of scanning every loaded bluebook for candidates
|
|
48
|
+
# @return [void]
|
|
35
49
|
def react(event, domain, only: nil)
|
|
36
50
|
selected = only ? [only] : policies_for(event, domain)
|
|
37
51
|
selected.each do |policy, home_domain|
|
|
@@ -44,13 +58,13 @@ module Hecks
|
|
|
44
58
|
|
|
45
59
|
private
|
|
46
60
|
|
|
47
|
-
#
|
|
48
|
-
# a policy commonly lives in the
|
|
61
|
+
# Scans every loaded bluebook, not just the emitting command's own —
|
|
62
|
+
# a policy commonly lives in the consumer's bluebook, reacting
|
|
49
63
|
# passively to an event a different domain's aggregate emits (the
|
|
50
64
|
# corpus-standard shape : `world/conception/bluebook/domain_cell.bluebook`'s
|
|
51
65
|
# ConceiveOnAbsorption reacts `on "DomainAbsorbed"`, an event
|
|
52
66
|
# `body/organs/bluebook/gut.bluebook`'s Gut.Absorb emits — two
|
|
53
|
-
# different bluebooks, joined only by the event's
|
|
67
|
+
# different bluebooks, joined only by the event's name). Restricting
|
|
54
68
|
# the scan to the emitting domain's own bluebook (the previous
|
|
55
69
|
# shape) silently drops every cross-domain reaction : the policy is
|
|
56
70
|
# never even a candidate, no refusal, no log entry, nothing —
|
|
@@ -58,12 +72,12 @@ module Hecks
|
|
|
58
72
|
# which encode this exact cross-bluebook cascade as their contract.
|
|
59
73
|
#
|
|
60
74
|
# Returns [policy, home_domain] pairs rather than bare policies —
|
|
61
|
-
# `deliver`/`deliver_for_each` fall back to the
|
|
62
|
-
#
|
|
75
|
+
# `deliver`/`deliver_for_each` fall back to the reacting policy's
|
|
76
|
+
# own domain (not the emitting one) when a trigger or for_each route
|
|
63
77
|
# is bare, and that fallback has to travel with each match now that
|
|
64
78
|
# a single event can surface policies from several different homes.
|
|
65
79
|
#
|
|
66
|
-
#
|
|
80
|
+
# The emitting domain's own policies first, in declaration order,
|
|
67
81
|
# then other bluebooks' in load order (C10.2) — the same order the
|
|
68
82
|
# outbox lays its rows in (`Outbox::Fanout.policies`) and the Rust
|
|
69
83
|
# kernel runs (`react_policies`: its own table, then cross-domain).
|
|
@@ -79,7 +93,7 @@ module Hecks
|
|
|
79
93
|
end
|
|
80
94
|
end
|
|
81
95
|
|
|
82
|
-
#
|
|
96
|
+
# The guard, evaluated against the triggering event's own payload —
|
|
83
97
|
# a policy has no aggregate instance of its own to read state from,
|
|
84
98
|
# so `state` is empty and every bare name a `where` resolves comes
|
|
85
99
|
# from `attrs` (Expression::Resolver#fetch checks `attrs` before
|
|
@@ -88,7 +102,7 @@ module Hecks
|
|
|
88
102
|
# policy simply has none of). Called from inside `deliver`'s own
|
|
89
103
|
# rescue-guarded body (both callers, below) — never guarded here —
|
|
90
104
|
# so an EvaluationError (an unresolvable field, a bad comparison) is
|
|
91
|
-
# caught the
|
|
105
|
+
# caught the same way any other reaction defect is, not swallowed as
|
|
92
106
|
# though the policy had merely declined to fire.
|
|
93
107
|
def where_holds?(policy, event)
|
|
94
108
|
return true if policy.where.to_s.empty?
|
|
@@ -97,7 +111,7 @@ module Hecks
|
|
|
97
111
|
end
|
|
98
112
|
|
|
99
113
|
def deliver(policy, event, domain)
|
|
100
|
-
# `record`
|
|
114
|
+
# `record` assigned before any branch that can raise, same reason
|
|
101
115
|
# `deliver_for_each`'s own header gives : both rescue clauses below
|
|
102
116
|
# call `.merge` on it, and a defect raised before it existed would
|
|
103
117
|
# be caught here only to raise a second, different NoMethodError
|
|
@@ -121,7 +135,7 @@ module Hecks
|
|
|
121
135
|
# fatal to the command that emitted the event.
|
|
122
136
|
record.merge(delivered: false, reason: e.message)
|
|
123
137
|
rescue StandardError => e
|
|
124
|
-
# A
|
|
138
|
+
# A defect, not a refusal — a NoMethodError in an interpreter, a
|
|
125
139
|
# NameError from a missing constant, a TypeError from a bad
|
|
126
140
|
# assumption : exactly the class of thing DOMAIN_REFUSALS
|
|
127
141
|
# (errors.rb, see the comment above that constant) deliberately
|
|
@@ -129,19 +143,19 @@ module Hecks
|
|
|
129
143
|
# folding a crash into the same `delivered: false` shape as an
|
|
130
144
|
# ordinary refusal makes a broken runtime read as normal operation
|
|
131
145
|
# in the log. This clause does not reopen that hole: it is a
|
|
132
|
-
#
|
|
146
|
+
# second, narrower rescue, tried only once the first one above has
|
|
133
147
|
# already declined to match, so a legitimate refusal still takes
|
|
134
148
|
# the branch above and a defect always takes this one.
|
|
135
149
|
#
|
|
136
|
-
# Catching it
|
|
150
|
+
# Catching it here is safe for a fact this method's caller cannot
|
|
137
151
|
# see from where it sits: by the time `react` runs, the command
|
|
138
|
-
# that
|
|
152
|
+
# that emitted `event` has already succeeded and persisted —
|
|
139
153
|
# `Dispatcher#dispatch` calls `@policies.react` only after its own
|
|
140
154
|
# `announced` events are already in hand. Letting this exception
|
|
141
155
|
# keep propagating would not undo that command (nothing here is
|
|
142
156
|
# transactional across aggregates) — it would only blow up the
|
|
143
|
-
#
|
|
144
|
-
# a
|
|
157
|
+
# original caller's `dispatch` call for a failure that happened in
|
|
158
|
+
# a different command, one the caller never asked to run and has no
|
|
145
159
|
# way to compensate for. So the defect is recorded, distinguishably
|
|
146
160
|
# (`defect: true`, plus the error's own class — nothing here is
|
|
147
161
|
# allowed to read like an ordinary refusal), warned to STDERR so it
|
|
@@ -152,19 +166,19 @@ module Hecks
|
|
|
152
166
|
record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
|
|
153
167
|
end
|
|
154
168
|
|
|
155
|
-
#
|
|
169
|
+
# **The fan-out** — `policy.for_each` names a query ; this runs it
|
|
156
170
|
# against the triggering event's own payload (the same source
|
|
157
171
|
# `deliver`'s own ordinary path forwards to `trigger` wholesale) and
|
|
158
172
|
# fires `trigger` once per row, merging each row's own id into the
|
|
159
|
-
# forwarded payload under whichever key
|
|
173
|
+
# forwarded payload under whichever key the target command itself
|
|
160
174
|
# expects to be addressed by (`Behaviour::Command#addressing_key_for`
|
|
161
175
|
# — never a guessed, one-size mint: `Account.Freeze`, addressed by
|
|
162
|
-
# `account` because it is declared
|
|
176
|
+
# `account` because it is declared on Account and self-references
|
|
163
177
|
# it, refused every dispatch for as long as this hardcoded
|
|
164
178
|
# `<aggregate>_id` instead — a real bug, found wiring `for_each`
|
|
165
179
|
# into a real domain for the first time, not a hypothetical). A
|
|
166
180
|
# refusal is recorded per row and the fan-out continues ; a crash
|
|
167
|
-
# resolving the
|
|
181
|
+
# resolving the query itself, or the target command's own inability
|
|
168
182
|
# to address this aggregate at all (`addressing_key_for` answering
|
|
169
183
|
# `nil` — a domain-authoring mistake, not a data problem), is a
|
|
170
184
|
# single top-level defect for the policy, the same shape `deliver`'s
|
|
@@ -174,9 +188,9 @@ module Hecks
|
|
|
174
188
|
|
|
175
189
|
query_domain, aggregate_name, query_name = policy.for_each_route(domain)
|
|
176
190
|
aggregate = resolve_query_aggregate(query_domain, aggregate_name, policy.for_each)
|
|
177
|
-
#
|
|
178
|
-
# what the
|
|
179
|
-
# different question (
|
|
191
|
+
# The query reads the event, never the projection — `with:` says
|
|
192
|
+
# what the trigger is given, and the fan-out's query is asking a
|
|
193
|
+
# different question (which rows) in the event's own vocabulary.
|
|
180
194
|
query_args = for_each_query_args(aggregate.query(query_name), event)
|
|
181
195
|
rows = QueryInterpreter.new(@registry).call(query_domain, aggregate, query_name, query_args)
|
|
182
196
|
reference_key = addressing_key_for(target, aggregate_name)
|
|
@@ -192,20 +206,19 @@ module Hecks
|
|
|
192
206
|
record.merge(delivered: false, reason: e.message, defect: true, error_class: e.class.name)
|
|
193
207
|
end
|
|
194
208
|
|
|
195
|
-
#
|
|
196
|
-
# for_each query commonly filters by the
|
|
209
|
+
# The event's own identity is a fact too, not only its payload. A
|
|
210
|
+
# for_each query commonly filters by the emitting record's own
|
|
197
211
|
# identity (`OpenForCustomer`'s own `reference:`, scoping by the
|
|
198
|
-
# very customer who was just suspended) —
|
|
199
|
-
#
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
205
|
-
# customer" the whole reaction exists to catch.
|
|
212
|
+
# very customer who was just suspended) — a fact routing keeps
|
|
213
|
+
# separate from payload (`to:`/`with:`, what the facade's own
|
|
214
|
+
# bang-methods always use), so `event.payload` alone does not carry
|
|
215
|
+
# it. Without this, the query would silently see neither the field
|
|
216
|
+
# it needs nor any error saying why — an empty result read as
|
|
217
|
+
# "nothing to freeze" instead of "the customer" the whole reaction
|
|
218
|
+
# exists to catch.
|
|
206
219
|
#
|
|
207
|
-
# Merged in
|
|
208
|
-
# name
|
|
220
|
+
# Merged in only when the query declares an argument by that exact
|
|
221
|
+
# name and the emitting aggregate's own identity is genuinely what
|
|
209
222
|
# that name means (`construct.identity_heads`) — the same guard
|
|
210
223
|
# `SagaInterpreter::Correlation#self_identified?` uses for the
|
|
211
224
|
# identical shape one call away, so an unrelated aggregate sharing
|
|
@@ -229,22 +242,22 @@ module Hecks
|
|
|
229
242
|
args
|
|
230
243
|
end
|
|
231
244
|
|
|
232
|
-
#
|
|
245
|
+
# **What the trigger is given**. Undeclared, the event's whole payload
|
|
233
246
|
# forwards verbatim — the behaviour every policy had before `with:`
|
|
234
247
|
# existed, and still the right default for a trigger shaped like
|
|
235
248
|
# its event.
|
|
236
249
|
#
|
|
237
250
|
# Declared, it is the same reading a saga's own `dispatch ...,
|
|
238
251
|
# with:` gets (`SagaInterpreter#dispatch_args`): a Symbol names a
|
|
239
|
-
# field on the
|
|
252
|
+
# field on the source below, anything else is a literal the policy
|
|
240
253
|
# supplies itself. A saga additionally resolves against its own
|
|
241
254
|
# memory and correlation key; a policy has neither — it holds
|
|
242
255
|
# nothing between events — so the source is the event, plus:
|
|
243
256
|
#
|
|
244
|
-
# `extra` is a
|
|
257
|
+
# `extra` is a fan-out's row key, merged into the source before the
|
|
245
258
|
# projection rather than after it. That is what lets a `for_each`
|
|
246
259
|
# policy name the row it is acting on — `with: { account: :account }`
|
|
247
|
-
# — and therefore what lets one send the row and
|
|
260
|
+
# — and therefore what lets one send the row and nothing else. A
|
|
248
261
|
# trigger needing only which record to act on is the ordinary case
|
|
249
262
|
# for a fan-out, and before this it could not be written: the whole
|
|
250
263
|
# event rode along, and the target had to declare every field of it
|
|
@@ -261,26 +274,26 @@ module Hecks
|
|
|
261
274
|
label: "#{policy.name}'s trigger"
|
|
262
275
|
)
|
|
263
276
|
|
|
264
|
-
#
|
|
277
|
+
# The raw inputs `args` was resolved from — same additive,
|
|
265
278
|
# Ruby-only shape SagaInterpreter#deliver_saga_dispatch's own
|
|
266
279
|
# saga_dispatch_log gets, for Properties.dispatch_binding_
|
|
267
280
|
# fidelity's own independent re-derivation of Policy#with_spec's
|
|
268
281
|
# 2-branch resolution (Symbol → payload lookup, anything else →
|
|
269
282
|
# literal — a policy holds no correlation and no memory, so
|
|
270
283
|
# `payload` — the merged event-payload-plus-fan-out-row source —
|
|
271
|
-
# is the
|
|
284
|
+
# is the whole source, unlike a saga's own 4-branch one).
|
|
272
285
|
@registry.policy_dispatch_log << { policy: policy.name, on: event.name, payload: payload,
|
|
273
286
|
with_spec: policy.with_spec, args: args }
|
|
274
287
|
args
|
|
275
288
|
end
|
|
276
289
|
|
|
277
|
-
#
|
|
290
|
+
# The emitting record's own identity is a fact a projection may read
|
|
278
291
|
# — the same reasoning `for_each_query_args` gives one method up,
|
|
279
|
-
# extended from the fan-out's
|
|
292
|
+
# extended from the fan-out's query to the trigger's own `with:`.
|
|
280
293
|
# Routing separated from payload (`to:`/`with:`) stopped carrying
|
|
281
294
|
# the emitting aggregate's identity in the payload, which is right
|
|
282
295
|
# for the event (a KnightCaptured is a fact about a knight, not a
|
|
283
|
-
# re-statement of which game) but left a
|
|
296
|
+
# re-statement of which game) but left a cross-aggregate reaction
|
|
284
297
|
# with no way to say which record to address: chess's Graveyard is
|
|
285
298
|
# one-per-game, fed by policy from every piece's own Captured
|
|
286
299
|
# event, and its burials had no way to name the game — the
|
|
@@ -288,10 +301,10 @@ module Hecks
|
|
|
288
301
|
# graveyard's identity and refused every one ("no Graveyard with
|
|
289
302
|
# label.value \"bb\""). Same-aggregate targets were already covered
|
|
290
303
|
# (`ReactionInvocation.source_receiver_for` lifts Event.id), so
|
|
291
|
-
# this is the
|
|
304
|
+
# this is the other aggregate's half of that.
|
|
292
305
|
#
|
|
293
306
|
# Offered under the emitting aggregate's own identity heads, only
|
|
294
|
-
# to an
|
|
307
|
+
# to an explicit projection (a legacy wholesale forward keeps its
|
|
295
308
|
# exact old payload), and never over a value the payload itself
|
|
296
309
|
# carries. `event.id` is the scalar the identity resolves to, so a
|
|
297
310
|
# projection naming it as the target's own identity head routes it
|
|
@@ -309,9 +322,9 @@ module Hecks
|
|
|
309
322
|
construct.identity_heads.to_h { |head| [head.to_sym, event.id] }
|
|
310
323
|
end
|
|
311
324
|
|
|
312
|
-
#
|
|
313
|
-
# `deliver`'s own caller already built) back to its
|
|
314
|
-
# command, then asks
|
|
325
|
+
# Resolves `target` ("Domain::Aggregate.Command", the same shape
|
|
326
|
+
# `deliver`'s own caller already built) back to its own declared
|
|
327
|
+
# command, then asks it how it expects to be addressed by a row of
|
|
315
328
|
# `aggregate_name` — see `Behaviour::Command#addressing_key_for`'s
|
|
316
329
|
# own comment for the two shapes that answers. Raises (caught by
|
|
317
330
|
# `deliver_for_each`'s own outer `rescue StandardError`, the same
|
|
@@ -342,8 +355,8 @@ module Hecks
|
|
|
342
355
|
reason: "reaction depth #{@door.max_reaction_depth} reached")
|
|
343
356
|
end
|
|
344
357
|
|
|
345
|
-
#
|
|
346
|
-
# source a `with:` projection reads
|
|
358
|
+
# Already merged, by `trigger_args` — the row key belongs in the
|
|
359
|
+
# source a `with:` projection reads from, not bolted onto its
|
|
347
360
|
# result, or a projection could never name the row it acts on.
|
|
348
361
|
@door.reenter(target, **reaction_invocation(target, args, policy, event))
|
|
349
362
|
row_record.merge(delivered: true)
|
|
@@ -361,20 +374,20 @@ module Hecks
|
|
|
361
374
|
)
|
|
362
375
|
end
|
|
363
376
|
|
|
364
|
-
# `for_each`'s own
|
|
377
|
+
# `for_each`'s own query route moved onto the Policy itself
|
|
365
378
|
# (Behaviour::Policy#for_each_route) — one reading the interpreter
|
|
366
379
|
# and the fuzzer's fan-out property both call, rather than the
|
|
367
|
-
# same split spelled twice. The reference-
|
|
368
|
-
# uses is `addressing_key_for`, above — a property of the
|
|
369
|
-
#
|
|
380
|
+
# same split spelled twice. The reference-key the dispatch itself
|
|
381
|
+
# uses is `addressing_key_for`, above — a property of the target
|
|
382
|
+
# command, not of the policy, so it lives on `Behaviour::Command`
|
|
370
383
|
# instead.
|
|
371
384
|
|
|
372
385
|
def resolve_query_aggregate(domain, aggregate_name, verb)
|
|
373
386
|
bluebook = @registry.bluebook(domain) ||
|
|
374
|
-
raise(UnknownVerb, RefusalWording.
|
|
387
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_domain", domain: domain, verb: verb))
|
|
375
388
|
bluebook.aggregate(aggregate_name) ||
|
|
376
|
-
raise(UnknownVerb, RefusalWording.
|
|
377
|
-
|
|
389
|
+
raise(UnknownVerb, RefusalWording.render_site("UnknownVerb", "no_aggregate",
|
|
390
|
+
domain: domain, aggregate: aggregate_name))
|
|
378
391
|
end
|
|
379
392
|
end
|
|
380
393
|
end
|
|
@@ -19,20 +19,43 @@ module Hecks
|
|
|
19
19
|
include Interpreting
|
|
20
20
|
include CommandInterpreter::ArgumentGate
|
|
21
21
|
|
|
22
|
-
Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result)
|
|
22
|
+
Context = Struct.new(:domain, :aggregate, :operation, :args, :route, :instance, :result, :invocation)
|
|
23
23
|
|
|
24
24
|
DISPATCH_ORDER = %i[
|
|
25
25
|
refuse_unknown_arguments refuse_absent_arguments normalize_args resolve_references resolve_route emit
|
|
26
26
|
].freeze
|
|
27
27
|
|
|
28
|
+
# @param registry [Runtime::Registry] the booted registry this interpreter dispatches
|
|
29
|
+
# against
|
|
30
|
+
# @param rules [Runtime::CommandRules] the shared command-rule checks (references) this
|
|
31
|
+
# interpreter's steps call
|
|
28
32
|
def initialize(registry, rules:)
|
|
29
33
|
@registry = registry
|
|
30
34
|
@rules = rules
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
# `invocation` — the `Runtime::Invocation` `Dispatcher` built;
|
|
38
|
+
# `ctx.args` is its `to_args`, `ctx.route` its `target`.
|
|
39
|
+
#
|
|
40
|
+
# @param domain [String] the domain the aggregate belongs to
|
|
41
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate the port operation belongs to
|
|
42
|
+
# @param operation [Bluebook::PortOperation] the port operation to dispatch
|
|
43
|
+
# @param invocation [Runtime::Invocation] the invocation `Dispatcher` built for this call
|
|
44
|
+
# @return [Array<Runtime::Event>] the events recorded: the operation's own `emits` for
|
|
45
|
+
# an inbound operation, or a single `answers`/`refuses` event for an outbound one
|
|
46
|
+
# (an adapter failure is recorded as a `refuses` event, not raised)
|
|
47
|
+
# @raise [Runtime::UnknownArgument] if `invocation` offers an argument the operation
|
|
48
|
+
# does not declare
|
|
49
|
+
# @raise [Runtime::AbsentArgument] if `invocation` omits a non-optional declared
|
|
50
|
+
# argument
|
|
51
|
+
# @raise [Runtime::TypeMismatch] if an offered argument does not coerce to its declared
|
|
52
|
+
# type
|
|
53
|
+
# @raise [Runtime::NotFound] if an offered reference or the operation's own receiving
|
|
54
|
+
# record cannot be found
|
|
55
|
+
def call(domain, aggregate, operation, invocation)
|
|
56
|
+
ctx = Context.new(domain, aggregate, operation, invocation.to_args)
|
|
57
|
+
ctx.invocation = invocation
|
|
58
|
+
ctx.route = invocation.target
|
|
36
59
|
run_dispatch_order(DISPATCH_ORDER, ctx)
|
|
37
60
|
ctx.result
|
|
38
61
|
end
|
|
@@ -66,13 +89,13 @@ module Hecks
|
|
|
66
89
|
ctx.result = step(:emit) { ctx.operation.outbound? ? ask(ctx) : emit(ctx) }
|
|
67
90
|
end
|
|
68
91
|
|
|
69
|
-
#
|
|
92
|
+
# The domain calling out, and both endings recorded.
|
|
70
93
|
#
|
|
71
94
|
# The adapter is found the same way every other port's is — by name,
|
|
72
95
|
# across whatever adapters this boot loaded — so an `asks` is bound by
|
|
73
96
|
# an adapter declaring `port "IssueTracker"` and nothing new to learn.
|
|
74
97
|
#
|
|
75
|
-
#
|
|
98
|
+
# Every failure is an answer. A raise from the far side of a boundary is
|
|
76
99
|
# not an exception in this domain's terms, it is the outside saying no,
|
|
77
100
|
# and the chapter already named the word for that. So the rescue is
|
|
78
101
|
# deliberately wide: a timeout, a bad credential, an adapter that does
|
|
@@ -80,22 +103,22 @@ module Hecks
|
|
|
80
103
|
# `refuses` event, carrying what was said. A policy reacts to it, a
|
|
81
104
|
# retry counter reads it, and nothing has to catch anything.
|
|
82
105
|
#
|
|
83
|
-
#
|
|
106
|
+
# An ask is handed the record it is about.
|
|
84
107
|
#
|
|
85
|
-
# An
|
|
108
|
+
# An inbound operation deliberately cannot read state — it is the
|
|
86
109
|
# anti-corruption boundary, translating a fact from outside, and letting
|
|
87
110
|
# it read the aggregate would make it a second place rules live. That
|
|
88
111
|
# rule was written for that direction and does not survive the crossing.
|
|
89
112
|
#
|
|
90
113
|
# An outbound one almost always needs the record. `asks "File"` names
|
|
91
114
|
# `reference_to Ticket` and the adapter needs the ticket's repository,
|
|
92
|
-
# title and body — which are
|
|
115
|
+
# title and body — which are on the ticket, and which the policy that
|
|
93
116
|
# triggered this cannot supply because a command's event payload is its
|
|
94
|
-
#
|
|
117
|
+
# arguments, not its state. Without this, every ask would have to have
|
|
95
118
|
# its data re-passed through the command that fired it, so the same text
|
|
96
119
|
# would live in two places and could differ.
|
|
97
120
|
#
|
|
98
|
-
#
|
|
121
|
+
# Arguments win over state, because an argument is what this call said
|
|
99
122
|
# and state is what the record happens to hold.
|
|
100
123
|
def ask(ctx)
|
|
101
124
|
payload = held_state(ctx).merge(materialise(ctx.args))
|
|
@@ -105,13 +128,13 @@ module Hecks
|
|
|
105
128
|
announce(ctx, ctx.operation.refuses, ctx.args.merge(refusal: { value: "#{e.class}: #{e.message}" }))
|
|
106
129
|
end
|
|
107
130
|
|
|
108
|
-
#
|
|
109
|
-
# close. A policy re-enters its target with the event payload
|
|
131
|
+
# The answer is spread, not nested — and that is what makes the loop
|
|
132
|
+
# close. A policy re-enters its target with the event payload verbatim;
|
|
110
133
|
# it cannot reach inside a key. So an answer tucked under `answered:`
|
|
111
134
|
# can be read by a human and by nothing else, and the command that
|
|
112
135
|
# should record the issue number never gets one.
|
|
113
136
|
#
|
|
114
|
-
# Spread, the adapter's own keys
|
|
137
|
+
# Spread, the adapter's own keys are the arguments of whatever command
|
|
115
138
|
# reacts to the answering event. Which is a real contract on the adapter
|
|
116
139
|
# — it must return what that command takes, in the shape the runtime
|
|
117
140
|
# coerces (`{ number: { value: 43 } }`, not `43`) — and naming it here
|
|
@@ -134,7 +157,7 @@ module Hecks
|
|
|
134
157
|
end
|
|
135
158
|
end
|
|
136
159
|
|
|
137
|
-
#
|
|
160
|
+
# The record, if there is one. A record that does not exist yet is not
|
|
138
161
|
# an error here — the ask still goes, carrying only its arguments, and
|
|
139
162
|
# whatever the adapter makes of that is its own business. Refusing
|
|
140
163
|
# would put a second existence check behind the one `resolve_references`
|
|
@@ -145,10 +168,10 @@ module Hecks
|
|
|
145
168
|
{}
|
|
146
169
|
end
|
|
147
170
|
|
|
148
|
-
#
|
|
171
|
+
# The port this operation belongs to, found by asking the aggregate
|
|
149
172
|
# rather than threading it through the call — the dispatcher already
|
|
150
173
|
# resolved it once to get here, and a second parameter carried purely so
|
|
151
|
-
# this method can read it would be a parameter every
|
|
174
|
+
# this method can read it would be a parameter every other step ignores.
|
|
152
175
|
def port_name_for(ctx)
|
|
153
176
|
owning = ctx.aggregate.ports.find { |port| port.operations.any? { |op| op.equal?(ctx.operation) } }
|
|
154
177
|
owning&.name or raise WiringError,
|
|
@@ -185,9 +208,9 @@ module Hecks
|
|
|
185
208
|
[event]
|
|
186
209
|
end
|
|
187
210
|
|
|
188
|
-
#
|
|
211
|
+
# The one place this differs from CommandRules::Emission — there is no
|
|
189
212
|
# mutated instance to read an id off, because nothing was hydrated or
|
|
190
|
-
# saved. The record this event is
|
|
213
|
+
# saved. The record this event is about is named by whichever attribute
|
|
191
214
|
# is a reference to the owning aggregate (PortOperationBuilder#build
|
|
192
215
|
# already refused to build an operation with none), so its coerced
|
|
193
216
|
# value — already a plain id, never an object, per
|