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
|
@@ -3,13 +3,16 @@ require_relative "traits"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Bluebook
|
|
5
5
|
module Behaviour
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# **What a command does**. Extended, not included — a command is a
|
|
7
|
+
# class, one per declared verb.
|
|
8
8
|
module Command
|
|
9
9
|
include Indexed
|
|
10
10
|
|
|
11
11
|
# Indexed once — attributes are final once absorbed, and every
|
|
12
12
|
# dispatch asks this finder by name.
|
|
13
|
+
#
|
|
14
|
+
# @return [Class] self — the command class, once its attributes are
|
|
15
|
+
# indexed
|
|
13
16
|
def settle
|
|
14
17
|
index_attributes(@attributes)
|
|
15
18
|
self
|
|
@@ -17,8 +20,8 @@ module Hecks
|
|
|
17
20
|
|
|
18
21
|
# The construct this verb acts upon — the construct itself, not its name.
|
|
19
22
|
#
|
|
20
|
-
# A verb declared on an
|
|
21
|
-
# self-references, because an element is addressed
|
|
23
|
+
# A verb declared on an entity always acts on that piece. It never
|
|
24
|
+
# self-references, because an element is addressed through its parent —
|
|
22
25
|
# which means `creates?` answers true for every one of them, and reading
|
|
23
26
|
# `acts_on` off `creates?` alone would report that `LedgerEntry.Amend`
|
|
24
27
|
# brings a ledger entry into being. Three of banking's commands were
|
|
@@ -26,10 +29,15 @@ module Hecks
|
|
|
26
29
|
#
|
|
27
30
|
# On an aggregate, a creating command acts on no existing root, so nil is
|
|
28
31
|
# the truth: there is nothing there yet.
|
|
32
|
+
#
|
|
33
|
+
# @return [Class, Bluebook::Aggregate, nil] the entity class (a
|
|
34
|
+
# `Bluebook::Entity` subclass) this verb acts on when declared on an
|
|
35
|
+
# entity, the aggregate instance when declared on an aggregate and
|
|
36
|
+
# non-creating, or `nil` for a creating command
|
|
29
37
|
def acts_on
|
|
30
|
-
#
|
|
38
|
+
# Fully qualified, and it has to be: inside `module Behaviour`
|
|
31
39
|
# the bare name `Entity` resolves to Behaviour::Entity — this
|
|
32
|
-
# module's
|
|
40
|
+
# module's sibling — not to the construct. Comparing a Class to
|
|
33
41
|
# a Module answers nil, so the guard silently fell through and
|
|
34
42
|
# every entity verb reported acting on nothing.
|
|
35
43
|
return hecks_owner if hecks_owner.is_a?(Class) && hecks_owner < Bluebook::Entity
|
|
@@ -37,20 +45,27 @@ module Hecks
|
|
|
37
45
|
creates? ? nil : hecks_owner
|
|
38
46
|
end
|
|
39
47
|
|
|
48
|
+
# Whether this command creates a new root rather than acting on one.
|
|
49
|
+
#
|
|
50
|
+
# @return [Boolean] whether this command creates a new root — true when
|
|
51
|
+
# it declares no `reference_to` back to its own owner
|
|
40
52
|
def creates? = @references.nil?
|
|
41
53
|
|
|
42
|
-
#
|
|
43
|
-
# its givens
|
|
54
|
+
# Every reason this verb can refuse on a rule — the descriptions of
|
|
55
|
+
# its givens and its ensures, the exact text the runtime quotes
|
|
44
56
|
# after "refused — " when a guard is not met (see
|
|
45
57
|
# command_rules/admissibility.rb's GivenNotMet/EnsuresNotMet). A
|
|
46
58
|
# property that asks "did the runtime only ever refuse for a rule
|
|
47
59
|
# the language wrote" reads this rather than re-deriving the two
|
|
48
60
|
# collections; `compact` because a rule's description is optional
|
|
49
61
|
# (behavior.bluebook's Rule) and an unnamed one quotes nothing.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<String>] every named given's and ensure's own
|
|
64
|
+
# description text, skipping unnamed rules
|
|
50
65
|
def guard_descriptions = (@givens + @ensures).map(&:description).compact
|
|
51
66
|
|
|
52
|
-
#
|
|
53
|
-
# for
|
|
67
|
+
# The argument name that addresses an instance of `aggregate_name`
|
|
68
|
+
# for this command — the one fact `PolicyInterpreter`'s own
|
|
54
69
|
# `for_each` fan-out needs and, until this reading existed, had
|
|
55
70
|
# to guess at (see git blame: `Behaviour::Policy
|
|
56
71
|
# #fan_out_reference_key`, which hardcoded `<aggregate>_id`
|
|
@@ -58,32 +73,37 @@ module Hecks
|
|
|
58
73
|
# addressing command as a result — `Account.Freeze`, addressed
|
|
59
74
|
# by `number`/`account`, not `account_id`).
|
|
60
75
|
#
|
|
61
|
-
#
|
|
76
|
+
# Two shapes, the same two `CommandBuilder#reference_to` already
|
|
62
77
|
# tells apart at declare time (command_builder.rb's own comment
|
|
63
|
-
# on `cross_reference` — "`as:`
|
|
64
|
-
# command can point at another instance of its
|
|
78
|
+
# on `cross_reference` — "`as:` means a named attribute... a
|
|
79
|
+
# command can point at another instance of its own kind"):
|
|
65
80
|
#
|
|
66
|
-
#
|
|
67
|
-
# is declared
|
|
81
|
+
# self-addressing — `references == aggregate_name` (this verb
|
|
82
|
+
# is declared on the very aggregate it acts on, `reference_to
|
|
68
83
|
# Account` on a command Account itself owns). No attribute was
|
|
69
|
-
# minted for it at all; the
|
|
84
|
+
# minted for it at all; the same bare key
|
|
70
85
|
# `CommandInterpreter::ArgumentGate#reference_key` already
|
|
71
86
|
# accepts as "addressing, not describing" is reused here
|
|
72
87
|
# rather than re-derived — one door, not two.
|
|
73
88
|
#
|
|
74
|
-
#
|
|
75
|
-
# attribute whose
|
|
76
|
-
# on `Account.Open`, or whatever `as:` named it). Its
|
|
89
|
+
# cross-referencing — a real, declared reference-typed
|
|
90
|
+
# attribute whose own target is `aggregate_name` (`customer_id`
|
|
91
|
+
# on `Account.Open`, or whatever `as:` named it). Its name is
|
|
77
92
|
# the key, exactly as declared — never re-derived from the
|
|
78
93
|
# target's name, because an `as:` reference's name and its
|
|
79
94
|
# target's snake case can legitimately differ (`Transfer`'s own
|
|
80
95
|
# `source`/`destination`, both `Reference<Account>`).
|
|
81
96
|
#
|
|
82
|
-
# `nil` when neither shape matches — a
|
|
97
|
+
# `nil` when neither shape matches — a creating command (nothing
|
|
83
98
|
# to address yet) or one that simply never references this
|
|
84
99
|
# aggregate at all. A caller minting a fan-out dispatch is
|
|
85
100
|
# expected to treat `nil` as "this command cannot be addressed by
|
|
86
101
|
# a row of this aggregate," not to fall back on a guess.
|
|
102
|
+
#
|
|
103
|
+
# @param aggregate_name [String, Symbol] the aggregate a fan-out dispatch is
|
|
104
|
+
# addressing an instance of
|
|
105
|
+
# @return [String, nil] the argument name that addresses it, or nil if this command
|
|
106
|
+
# cannot be addressed by a row of that aggregate
|
|
87
107
|
def addressing_key_for(aggregate_name)
|
|
88
108
|
return Naming.reference_key(aggregate_name) if references.to_s == aggregate_name.to_s
|
|
89
109
|
|
|
@@ -92,15 +112,25 @@ module Hecks
|
|
|
92
112
|
end
|
|
93
113
|
end
|
|
94
114
|
|
|
95
|
-
# A
|
|
115
|
+
# **A mutation's own readings**. Included (not extended) — Mutation is
|
|
96
116
|
# a Struct, so these are instance methods.
|
|
97
117
|
module Mutation
|
|
98
|
-
# An
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
118
|
+
# An append binds several fields at once, each from either a command
|
|
119
|
+
# argument (a Symbol) or a literal. Spelled through `Literal.render`,
|
|
120
|
+
# the same self-describing form a where-clause's own value already
|
|
121
|
+
# uses, rather than a bare Symbol paired with an inspected literal —
|
|
122
|
+
# see Hecks::Literal.
|
|
123
|
+
#
|
|
124
|
+
# @return [Hash{Symbol => String}] the append's field bindings, each
|
|
125
|
+
# value rendered through `Literal.render`
|
|
102
126
|
def appended_fields = source.transform_values { |value| Literal.render(value) }
|
|
103
127
|
|
|
128
|
+
# Classifies this mutation's source for the wire, the counterpart
|
|
129
|
+
# `Assembly::Marks#classified` reads back.
|
|
130
|
+
#
|
|
131
|
+
# @return [Hash{Symbol => Object}] `{kind: "argument", name:}` for a
|
|
132
|
+
# command argument, `{kind: "state", name:}` for a state
|
|
133
|
+
# self-reference, or `{kind: "literal", value:}` for a literal value
|
|
104
134
|
def classified_source
|
|
105
135
|
if source.is_a?(Symbol)
|
|
106
136
|
{ kind: "argument", name: source.to_s }
|
|
@@ -3,17 +3,19 @@ require_relative "traits"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Bluebook
|
|
5
5
|
module Behaviour
|
|
6
|
-
#
|
|
6
|
+
# **What one port operation does**.
|
|
7
7
|
module PortOperation
|
|
8
8
|
include Indexed
|
|
9
9
|
|
|
10
|
-
# An operation declares no reference of its own — the
|
|
10
|
+
# An operation declares no reference of its own — the owner is
|
|
11
11
|
# what it acts for, and `identity_attribute` is how that is found.
|
|
12
|
+
#
|
|
13
|
+
# @return [nil] always `nil`
|
|
12
14
|
def references = nil
|
|
13
15
|
|
|
14
|
-
#
|
|
16
|
+
# Never a creating command — a port operation always acts on an
|
|
15
17
|
# aggregate that already exists (`operation.to`/`identity_attribute`
|
|
16
|
-
# both name where its
|
|
18
|
+
# both name where its receiver comes from, never a birth). Answered
|
|
17
19
|
# explicitly, not derived from `references` the way `Command
|
|
18
20
|
# #creates?` is (`references.nil?` would read every operation as
|
|
19
21
|
# creating, since `references` above is unconditionally nil) —
|
|
@@ -21,27 +23,45 @@ module Hecks
|
|
|
21
23
|
# `target.command.creates?` on a port operation the same way it
|
|
22
24
|
# already does on an ordinary command, and correctly lift a
|
|
23
25
|
# same-aggregate policy's own Event.id as the operation's receiver.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean] always `false`
|
|
24
28
|
def creates? = false
|
|
25
29
|
|
|
30
|
+
# Finds the reference-typed attribute through which this operation addresses
|
|
31
|
+
# its owning aggregate.
|
|
32
|
+
#
|
|
33
|
+
# @param owner_name [String, Symbol] the owning aggregate's name
|
|
34
|
+
# @return [Bluebook::Attribute, nil] the reference-typed attribute targeting
|
|
35
|
+
# `owner_name`, or `nil` if this operation declares none
|
|
26
36
|
def identity_attribute(owner_name)
|
|
27
37
|
@attributes.find { |attribute| attribute.reference? && attribute.type.target_name == owner_name.to_s }
|
|
28
38
|
end
|
|
29
39
|
|
|
30
|
-
#
|
|
40
|
+
# The same reading `Command#addressing_key_for` gives, minus its
|
|
31
41
|
# self-addressing branch — a port operation's `references` is
|
|
32
42
|
# unconditionally nil (above), so it never means "this verb is
|
|
33
|
-
# declared
|
|
43
|
+
# declared on the very aggregate it acts on" the way a command's
|
|
34
44
|
# does; a port operation's only path back to its owner is a real,
|
|
35
45
|
# declared reference-typed attribute, which is exactly what
|
|
36
46
|
# `identity_attribute` already finds. Needed for the identical
|
|
37
47
|
# reason `Command#addressing_key_for` is: `ReactionInvocation
|
|
38
48
|
# #aggregate_aliases` calls it on whatever `target.command` holds,
|
|
39
49
|
# a `PortOperation` now included since a policy can trigger one.
|
|
50
|
+
#
|
|
51
|
+
# @param aggregate_name [String, Symbol] the name of the aggregate a row of which
|
|
52
|
+
# would address this operation
|
|
53
|
+
# @return [Symbol, nil] the reference-typed attribute's name a caller passes to
|
|
54
|
+
# address that row, or `nil` if this operation cannot be addressed by one
|
|
40
55
|
def addressing_key_for(aggregate_name) = identity_attribute(aggregate_name)&.name
|
|
41
56
|
end
|
|
42
57
|
|
|
43
|
-
#
|
|
58
|
+
# **What a port does** — one finder over its declared operations.
|
|
44
59
|
module DomainPort
|
|
60
|
+
# Finds a declared operation by name.
|
|
61
|
+
#
|
|
62
|
+
# @param named [String, Symbol] the operation's declared name
|
|
63
|
+
# @return [Bluebook::PortOperation, nil] the operation, or `nil` if none is
|
|
64
|
+
# declared by that name
|
|
45
65
|
def operation(named) = @operations.find { |op| op.hecks_name == named.to_s }
|
|
46
66
|
end
|
|
47
67
|
end
|
|
@@ -3,8 +3,8 @@ require_relative "traits"
|
|
|
3
3
|
module Hecks
|
|
4
4
|
module Bluebook
|
|
5
5
|
module Behaviour
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# **What an entity does**. Extended, not included — an entity is a
|
|
7
|
+
# class, so this is singleton behaviour.
|
|
8
8
|
#
|
|
9
9
|
# `settle` is the same seam an Aggregate has, reached from `absorb`
|
|
10
10
|
# rather than from a constructor because a declared entity is built
|
|
@@ -17,41 +17,53 @@ module Hecks
|
|
|
17
17
|
include Indexed
|
|
18
18
|
include Owns
|
|
19
19
|
|
|
20
|
+
# The hook `absorb` calls once every declared field is assigned.
|
|
21
|
+
#
|
|
22
|
+
# @return [Class] this entity's own class (a `Bluebook::Entity` subclass), self,
|
|
23
|
+
# once identity and indexes are derived
|
|
20
24
|
def settle
|
|
21
25
|
derive_identity
|
|
22
26
|
index_declarations
|
|
23
27
|
self
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
# Builds every by-name index this entity answers finders through.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
26
33
|
def index_declarations
|
|
27
34
|
index_attributes(@attributes)
|
|
28
35
|
@commands_by_name = index_by_hecks_name(@commands)
|
|
29
36
|
@queries_by_name = index_by_hecks_name(@queries)
|
|
30
37
|
end
|
|
31
38
|
|
|
32
|
-
# S17, ADR 0026 — `@entities`, now genuinely
|
|
39
|
+
# S17, ADR 0026 — `@entities`, now genuinely nested entities
|
|
33
40
|
# (Dispatch, inside Handler) rather than always `[]`. Kept as a
|
|
34
41
|
# real reader rather than a hardcoded empty list for two
|
|
35
42
|
# reasons at once: `Value::Coercion#for_attribute` calls
|
|
36
|
-
# `.entities` on
|
|
37
|
-
# an entity's own — the moment it meets
|
|
43
|
+
# `.entities` on whatever owner it is handed — an aggregate's or
|
|
44
|
+
# an entity's own — the moment it meets any `list_of(...)`
|
|
38
45
|
# attribute (entity-typed or not — `hydrate_entity_list`'s own
|
|
39
46
|
# fallback, `return value unless entity`, only runs once
|
|
40
47
|
# `.entities` has already answered) ; and `EntityInterpreter`
|
|
41
|
-
# now walks a
|
|
48
|
+
# now walks a dotted chain of entities one level at a time
|
|
42
49
|
# (`walk_entity_chain`) exactly the way an aggregate's own
|
|
43
50
|
# `.entities` is walked for its direct children. Entity's own
|
|
44
51
|
# header comment already promises it stays "structurally
|
|
45
52
|
# interchangeable with an aggregate" for exactly this reason.
|
|
53
|
+
#
|
|
54
|
+
# @return [Array<Class>] this entity's own nested entities (each a `Bluebook::Entity`
|
|
55
|
+
# subclass), or `[]` if it declares none
|
|
46
56
|
def entities = @entities || []
|
|
47
57
|
|
|
48
|
-
# A piece
|
|
58
|
+
# A piece owns the verbs declared on it, so they can state an
|
|
49
59
|
# identity — `Banking::Account.Ledger.Deposit` rather than a
|
|
50
60
|
# command that cannot say what it belongs to. Separate from
|
|
51
|
-
# `settle` because `declare` stamps
|
|
61
|
+
# `settle` because `declare` stamps after absorbing, once the
|
|
52
62
|
# subclass that will own them exists. `@entities` too now
|
|
53
63
|
# (S17, ADR 0026) — a nested entity states its own owner chain
|
|
54
64
|
# exactly the way a nested command does.
|
|
65
|
+
#
|
|
66
|
+
# @return [void]
|
|
55
67
|
def stamp_children = stamp(@commands, @queries, @entities)
|
|
56
68
|
end
|
|
57
69
|
end
|
|
@@ -1,18 +1,32 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module Behaviour
|
|
4
|
-
#
|
|
4
|
+
# **What a hecksagon does** — the lookups over its declared binds.
|
|
5
5
|
module Hecksagon
|
|
6
6
|
# Aggregate-specific bind wins when one was declared; otherwise
|
|
7
7
|
# falls back to a domain-level default (`b.aggregate.nil?` — see
|
|
8
8
|
# `HecksagonBuilder#method_missing`). Checked directly rather than
|
|
9
9
|
# via `aggregate_name`/`Naming.demodulise`, which would need its own
|
|
10
10
|
# nil-handling for the default row.
|
|
11
|
+
#
|
|
12
|
+
# @param aggregate_name [String, Symbol] the aggregate to find a bind for
|
|
13
|
+
# @param verb [String, Symbol] the port verb name to find a bind for
|
|
14
|
+
# @return [Bluebook::Bind, nil] the aggregate-specific bind when one is
|
|
15
|
+
# declared, the domain-level default bind for `verb` otherwise, or
|
|
16
|
+
# `nil` when neither exists
|
|
11
17
|
def bind_for(aggregate_name, verb)
|
|
12
18
|
@binds.find { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s } ||
|
|
13
19
|
@binds.find { |b| b.aggregate.nil? && b.verb.to_s == verb.to_s }
|
|
14
20
|
end
|
|
15
21
|
|
|
22
|
+
# Every bind for `verb`, aggregate-specific ones winning as a group over
|
|
23
|
+
# the domain-level default.
|
|
24
|
+
#
|
|
25
|
+
# @param aggregate_name [String, Symbol] the aggregate to find binds for
|
|
26
|
+
# @param verb [String, Symbol] the port verb name to find binds for
|
|
27
|
+
# @return [Array<Bluebook::Bind>] every aggregate-specific bind for
|
|
28
|
+
# `verb` when any are declared, otherwise every domain-level default
|
|
29
|
+
# bind for `verb`
|
|
16
30
|
def binds_for(aggregate_name, verb)
|
|
17
31
|
specific = @binds.select { |b| b.aggregate_name == aggregate_name.to_s && b.verb.to_s == verb.to_s }
|
|
18
32
|
return specific if specific.any?
|
|
@@ -21,22 +35,34 @@ module Hecks
|
|
|
21
35
|
end
|
|
22
36
|
end
|
|
23
37
|
|
|
24
|
-
#
|
|
38
|
+
# **What a world does** — settings lookup, with the adapter-specific
|
|
25
39
|
# entry falling back to the verb's own.
|
|
26
40
|
module World
|
|
41
|
+
# Finds the generic settings declared for a port verb.
|
|
42
|
+
#
|
|
43
|
+
# @param verb [String, Symbol] the port verb name, such as `"persistence"`
|
|
44
|
+
# @return [Hash{Symbol => Object}] the settings declared for `verb`, or
|
|
45
|
+
# `{}` when none are declared
|
|
27
46
|
def for_verb(verb) = @settings.fetch(verb.to_s, {})
|
|
28
47
|
|
|
29
48
|
# The generic `verb` entry (`persisted_by("Heki") do dir :default
|
|
30
49
|
# end`) only answers for the adapter it actually names — falling
|
|
31
50
|
# back to it unconditionally applies one adapter's settings to an
|
|
32
51
|
# unrelated one. Real, corpus-caught bug: a hecksagon binding two
|
|
33
|
-
# aggregates to two different adapters under the
|
|
52
|
+
# aggregates to two different adapters under the same verb (one to
|
|
34
53
|
# Heki, one to Memory) sent Memory's lookup down Heki's generic
|
|
35
54
|
# entry, then failed `check_settings` with "Memory does not
|
|
36
55
|
# declare :dir" — the generic entry's own `settings[:adapter]`
|
|
37
56
|
# names Heki, not Memory, so the fallback was never actually for
|
|
38
57
|
# this bind. `{}` is exactly right when nothing was configured for
|
|
39
|
-
#
|
|
58
|
+
# this adapter — Memory, which takes no values at all.
|
|
59
|
+
#
|
|
60
|
+
# @param verb [String, Symbol] the port verb name to find settings for
|
|
61
|
+
# @param adapter [String, Symbol] the adapter name, such as `"Heki"`, to
|
|
62
|
+
# find settings for
|
|
63
|
+
# @return [Hash{Symbol => Object}] the adapter-qualified settings when
|
|
64
|
+
# declared, the generic verb settings when they name this same
|
|
65
|
+
# adapter, or `{}` otherwise
|
|
40
66
|
def for_binding(verb, adapter)
|
|
41
67
|
qualified = @settings["#{verb}:#{adapter.to_s.downcase}"]
|
|
42
68
|
return qualified if qualified
|
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module Behaviour
|
|
4
|
-
#
|
|
5
|
-
# state and a transition list. Everything here
|
|
4
|
+
# **What a lifecycle does**. Its declared half is a field, a starting
|
|
5
|
+
# state and a transition list. Everything here reads that — which
|
|
6
6
|
# states exist, which transition a command takes, and how one
|
|
7
7
|
# declared transition expands into the several rows the emission
|
|
8
8
|
# carries when `from` names more than one source state.
|
|
9
9
|
module Lifecycle
|
|
10
|
+
# Lists every state this lifecycle can be in.
|
|
11
|
+
#
|
|
12
|
+
# @return [Array<String>] every state this lifecycle can be in — the
|
|
13
|
+
# default plus each declared transition's own target, deduplicated
|
|
10
14
|
def states
|
|
11
15
|
([default] + transitions.map { |_command, t| t.target }).uniq
|
|
12
16
|
end
|
|
13
17
|
|
|
18
|
+
# Finds every transition a command declares.
|
|
19
|
+
#
|
|
20
|
+
# @param command [String, Symbol] the command name to find declared
|
|
21
|
+
# transitions for
|
|
22
|
+
# @return [Array<Bluebook::StateTransition>] every transition declared
|
|
23
|
+
# for `command`
|
|
14
24
|
def transitions_for(command)
|
|
15
25
|
transitions.select { |name, _| name == command.to_s }.map { |_, t| t }
|
|
16
26
|
end
|
|
17
27
|
|
|
28
|
+
# Finds the state a command moves the record to.
|
|
29
|
+
#
|
|
30
|
+
# @param command [String, Symbol] the command name to find the target
|
|
31
|
+
# state for
|
|
32
|
+
# @param current_state [String, Symbol, nil] the record's current
|
|
33
|
+
# state, to pick the one declared transition that admits it; `nil`
|
|
34
|
+
# to skip that check and take the first declared transition
|
|
35
|
+
# @return [String, nil] the state `command` moves the record to, or
|
|
36
|
+
# `nil` when `command` declares no transition at all
|
|
37
|
+
# @raise [Runtime::WiringError] if `command` declares transitions but
|
|
38
|
+
# none of them admits `current_state`
|
|
18
39
|
def target_for(command, current_state = nil)
|
|
19
40
|
match_transition(command, current_state)&.target
|
|
20
41
|
end
|
|
21
42
|
|
|
22
43
|
private
|
|
23
44
|
|
|
24
|
-
#
|
|
45
|
+
# One declared transition is several rows when `from` names more
|
|
25
46
|
# than one source state — the emission carries them flat, so the
|
|
26
47
|
# fan-out happens here rather than in whatever reads it.
|
|
27
48
|
def expand(command, transition)
|
|
@@ -37,10 +58,10 @@ module Hecks
|
|
|
37
58
|
return nil if matches.empty?
|
|
38
59
|
return matches.first unless current_state
|
|
39
60
|
|
|
40
|
-
#
|
|
41
|
-
#
|
|
61
|
+
# Not `|| matches.first` — that would silently hand back an
|
|
62
|
+
# arbitrary declared transition for `command` whenever none of
|
|
42
63
|
# them actually admitted `current_state`, picking a `target`
|
|
43
|
-
# that command dispatch would in fact
|
|
64
|
+
# that command dispatch would in fact refuse (that
|
|
44
65
|
# refusal is `CommandRules::Admissibility#admissible_transition`'s
|
|
45
66
|
# own job, which raises `LifecycleRefused` for exactly this
|
|
46
67
|
# case rather than guessing — this module has no state subject
|
|
@@ -1,34 +1,53 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module Behaviour
|
|
4
|
-
#
|
|
4
|
+
# **What a policy does**. Its declared half is four plain fields; these
|
|
5
5
|
# are the readings taken off them.
|
|
6
6
|
module Policy
|
|
7
|
-
# The
|
|
7
|
+
# The bluebook's name for this construct, asked the same way of a class
|
|
8
8
|
# that has crossed over and of an IR object that has not. Collapses into
|
|
9
9
|
# Construct when this one crosses.
|
|
10
|
+
#
|
|
11
|
+
# @return [String] this policy's declared name
|
|
10
12
|
def hecks_name = @name
|
|
11
13
|
|
|
14
|
+
# The domain-and-aggregate prefix of the triggering event's name.
|
|
15
|
+
#
|
|
16
|
+
# @return [String, nil] the part of `on_event` before its first `.`
|
|
17
|
+
# (the `"Domain::Aggregate"` prefix), or `nil` when `on_event`
|
|
18
|
+
# carries no dot
|
|
12
19
|
def event_qualifier = Naming.qualifier(@on_event)
|
|
13
20
|
|
|
21
|
+
# The bare name of the triggering event, without its domain/aggregate
|
|
22
|
+
# prefix.
|
|
23
|
+
#
|
|
24
|
+
# @return [String] the part of `on_event` after its first `.` (the
|
|
25
|
+
# bare event name), or the whole string when it carries no dot
|
|
14
26
|
def event_name = Naming.unqualified(@on_event)
|
|
15
27
|
|
|
16
|
-
#
|
|
28
|
+
# Whether this policy fans out — `for_each` names a query, and a
|
|
17
29
|
# non-empty one turns a single reaction into one dispatch per row
|
|
18
30
|
# the query answers. Read by the interpreter that runs the fan-out
|
|
19
31
|
# and by the property that checks it dispatched once per row.
|
|
32
|
+
#
|
|
33
|
+
# @return [Boolean] whether `for_each` names a query
|
|
20
34
|
def fans_out? = !@for_each.to_s.empty?
|
|
21
35
|
|
|
22
|
-
#
|
|
36
|
+
# Whether this policy is guarded — a non-empty `where` decides
|
|
23
37
|
# whether the policy fires at all, read against the triggering
|
|
24
38
|
# event's own payload.
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean] whether `where` is declared and non-empty
|
|
25
41
|
def guarded? = !@where.to_s.empty?
|
|
26
42
|
|
|
27
|
-
#
|
|
43
|
+
# The structured form of `where`, derived once — the same tree
|
|
28
44
|
# `AstJson.emit_predicate` spells for every rule row, memoized
|
|
29
45
|
# here because a policy is consulted once per event, not once per
|
|
30
46
|
# boot. Nil when there is no `where`, exactly as the wire carries
|
|
31
47
|
# it.
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash, nil] the JSON-shaped boolean AST for `where`, or
|
|
50
|
+
# `nil` when this policy is not guarded
|
|
32
51
|
def where_ast
|
|
33
52
|
if defined?(@where_ast)
|
|
34
53
|
@where_ast
|
|
@@ -40,30 +59,36 @@ module Hecks
|
|
|
40
59
|
# The rule-shaped reading of the guard, for `Evaluator.call_rule`
|
|
41
60
|
# — a policy's `where` has no description (nothing refuses with
|
|
42
61
|
# it; an unmet where is a silent skip).
|
|
62
|
+
#
|
|
63
|
+
# @return [Bluebook::Given] the guard as a rule, with no description
|
|
64
|
+
# and its `ast` already resolved via `where_ast`
|
|
43
65
|
def where_rule = @where_rule ||= Given.new(description: nil, canonical: @where, ast: where_ast)
|
|
44
66
|
|
|
45
|
-
#
|
|
67
|
+
# The fan-out query's route, split the way the runtime runs it:
|
|
46
68
|
# `[query_domain, aggregate_name, query_name]`. The query runs
|
|
47
|
-
# against the triggering event's
|
|
69
|
+
# against the triggering event's own domain unless `for_each`
|
|
48
70
|
# names one ("Domain::Aggregate.query"). Deliberately independent
|
|
49
71
|
# of `across`/`target_domain`, which name where `trigger` fires,
|
|
50
72
|
# not where the fan-out's own query runs.
|
|
73
|
+
#
|
|
74
|
+
# @param default_domain [String, Symbol] the triggering event's own
|
|
75
|
+
# domain, used when `for_each` names no domain of its own
|
|
76
|
+
# @return [Array(String, String, String)] `[domain, aggregate_name,
|
|
77
|
+
# query_name]` the fan-out query resolves against
|
|
51
78
|
def for_each_route(default_domain)
|
|
52
79
|
path, query_name = @for_each.to_s.split(".", 2)
|
|
53
80
|
domain, aggregate = path.to_s.include?("::") ? path.split("::", 2) : [default_domain.to_s, path]
|
|
54
81
|
[domain, aggregate, query_name]
|
|
55
82
|
end
|
|
56
83
|
|
|
57
|
-
#
|
|
58
|
-
# under
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# lives on `Behaviour::Command#addressing_key_for`, asked of the
|
|
66
|
-
# resolved target command by `PolicyInterpreter#addressing_key_for`.
|
|
84
|
+
# The argument name a fan-out dispatch mints each matched row's id
|
|
85
|
+
# under is not minted here — it depends on the target command's own
|
|
86
|
+
# declared shape (a target command declared on the very aggregate it
|
|
87
|
+
# self-references, such as `Account.Freeze`, is addressed by its own
|
|
88
|
+
# identity field's name, not a synthetic foreign key), not on the
|
|
89
|
+
# aggregate name alone, so it lives on
|
|
90
|
+
# `Behaviour::Command#addressing_key_for`, asked of the resolved
|
|
91
|
+
# target command by `PolicyInterpreter#addressing_key_for`.
|
|
67
92
|
end
|
|
68
93
|
end
|
|
69
94
|
end
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module Behaviour
|
|
4
|
-
#
|
|
4
|
+
# **What a process manager does**. Its declared half is the trigger,
|
|
5
5
|
# the states and the handlers; the compensation half — `saga` — is
|
|
6
|
-
#
|
|
6
|
+
# derived from the handler that answers a refusal, not declared.
|
|
7
7
|
module ProcessManager
|
|
8
|
+
# The bluebook's name for this construct.
|
|
9
|
+
#
|
|
10
|
+
# @return [String, nil] the process manager's declared name, or `nil` before it
|
|
11
|
+
# is set
|
|
8
12
|
def hecks_name = @name
|
|
9
13
|
|
|
10
|
-
#
|
|
14
|
+
# The leg that answers — selected by (event, current state), C10.3
|
|
11
15
|
# (docs/semantics/bluebook-semantics.md). Two legs may answer the
|
|
12
16
|
# same event from different states; which one runs is decided by
|
|
13
17
|
# the state the instance is in, never by declaration order. With
|
|
@@ -17,39 +21,63 @@ module Hecks
|
|
|
17
21
|
# selection). Build refuses two legs on one (event, state) pair
|
|
18
22
|
# (`ProcessManagerBuilder#validate!`), so a stated lookup is
|
|
19
23
|
# unambiguous by construction.
|
|
24
|
+
#
|
|
25
|
+
# @param event [String, Symbol] the triggering event's name
|
|
26
|
+
# @param state [String, Symbol, nil] the instance's current state; `nil` asks
|
|
27
|
+
# declaratively, ignoring state
|
|
28
|
+
# @return [Bluebook::ProcessManagerHandler, nil] the matching handler row, or `nil`
|
|
29
|
+
# if no declared leg answers this (event, state) pair
|
|
20
30
|
def handler_for(event, state = nil)
|
|
21
31
|
@handlers.find { |h| h.event_type == event.to_s && (state.nil? || h.from_state == state.to_s) }
|
|
22
32
|
end
|
|
23
33
|
|
|
34
|
+
# Finds every declared leg for an event, regardless of source state.
|
|
35
|
+
#
|
|
36
|
+
# @param event [String, Symbol] the triggering event's name
|
|
37
|
+
# @return [Array<Bluebook::ProcessManagerHandler>] every handler row declared for
|
|
38
|
+
# `event`; `[]` if none is declared
|
|
24
39
|
def handlers_for(event) = @handlers.select { |h| h.event_type == event.to_s }
|
|
25
40
|
|
|
41
|
+
# Says whether any declared leg answers an event.
|
|
42
|
+
#
|
|
43
|
+
# @param event [String, Symbol] the triggering event's name
|
|
44
|
+
# @return [Boolean] whether any handler row is declared for `event`
|
|
26
45
|
def handles?(event) = @handlers.any? { |h| h.event_type == event.to_s }
|
|
27
46
|
|
|
28
|
-
#
|
|
47
|
+
# Whether a state is one this procedure declares — asked of a value
|
|
29
48
|
# a real run left a saga instance holding (its live or rehydrated
|
|
30
49
|
# state), the way `Lifecycle#states` is asked of an aggregate's
|
|
31
50
|
# resting field. A rehydrated instance in a state no handler could
|
|
32
51
|
# have reached is corruption the durable round-trip introduced.
|
|
52
|
+
#
|
|
53
|
+
# @param state [String, Symbol] the state to check
|
|
54
|
+
# @return [Boolean] whether `state` is one of this procedure's declared states
|
|
33
55
|
def declares_state?(state) = @states.map(&:to_s).include?(state.to_s)
|
|
34
56
|
|
|
57
|
+
# Names the payload field a triggering event's instances are correlated by.
|
|
58
|
+
#
|
|
59
|
+
# @return [Symbol] the head of `correlates_by` — the part before the first `.`
|
|
35
60
|
def correlation_head = @correlates_by.to_s.split(".").first.to_sym
|
|
36
61
|
|
|
37
62
|
# The compensation half of a procedure, read off the handler that
|
|
38
|
-
# answers REFUSED
|
|
63
|
+
# answers `REFUSED`.
|
|
39
64
|
#
|
|
40
65
|
# nil for a procedure with no answer to a refusal, which is a legitimate
|
|
41
66
|
# thing to be — a hiring pipeline cannot un-interview anybody.
|
|
67
|
+
#
|
|
68
|
+
# @return [Bluebook::Saga, nil] the derived saga, or `nil` if no handler answers
|
|
69
|
+
# `REFUSED`
|
|
42
70
|
def saga
|
|
43
71
|
leg = handler_for(Bluebook::ProcessManager::REFUSED)
|
|
44
72
|
return nil unless leg
|
|
45
73
|
|
|
46
|
-
# `compensations` — a
|
|
74
|
+
# `compensations` — a static preview, declaration order, not
|
|
47
75
|
# one instance's own runtime history (which legs a given
|
|
48
76
|
# instance actually completed is per-instance state,
|
|
49
77
|
# `SagaInterpreter`'s own `completed_compensations`, not a
|
|
50
78
|
# fact `Saga` — a pure declaration reading — could ever hold).
|
|
51
|
-
# Every `compensates`
|
|
52
|
-
# forward declaration order,
|
|
79
|
+
# Every `compensates` any handler's own dispatch declares,
|
|
80
|
+
# forward declaration order, then whatever this leg's own
|
|
53
81
|
# hand-written body still lists — coexistence, not replacement
|
|
54
82
|
# (`ProcessManagerBuilder::HandlerBuilder#dispatch_impl`'s own
|
|
55
83
|
# comment): a saga can derive some of its compensation and
|
|
@@ -60,6 +88,9 @@ module Hecks
|
|
|
60
88
|
to_state: leg.to_state, compensations: derived + leg.dispatches)
|
|
61
89
|
end
|
|
62
90
|
|
|
91
|
+
# Says whether this procedure declares a handler for `REFUSED`.
|
|
92
|
+
#
|
|
93
|
+
# @return [Boolean] whether this procedure has a derivable saga
|
|
63
94
|
def saga? = !saga.nil?
|
|
64
95
|
end
|
|
65
96
|
end
|