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
|
@@ -2,37 +2,68 @@ require_relative "word_gate"
|
|
|
2
2
|
module Hecks
|
|
3
3
|
module Bluebook
|
|
4
4
|
module DSL
|
|
5
|
-
# Parses a
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
5
|
+
# Parses a `verb "x"; signal :effect` port body into a `Port` — the
|
|
6
|
+
# adapter-facing shape (one verb, one signal, an optional method
|
|
7
|
+
# contract in `answers`) a domain calls out through, as opposed to
|
|
8
|
+
# `DomainPortBuilder`'s own inbound/outbound operations.
|
|
9
|
+
#
|
|
10
|
+
# `Hecks.port` (lib/hecks.rb) parses a top-level `.port` file through
|
|
11
|
+
# `DomainPortBuilder`, whose bare-verb branch builds the identical
|
|
12
|
+
# `Port`; this builder is the reference shape `spec/dsl_spec.rb` holds
|
|
13
|
+
# that branch to.
|
|
10
14
|
class PortBuilder
|
|
11
15
|
GRAMMAR_CONTEXT = "Port".freeze
|
|
12
16
|
|
|
13
17
|
include WordGate
|
|
14
18
|
|
|
19
|
+
# @param name [String] the port's name, as written after `Hecks.port`
|
|
15
20
|
def initialize(name)
|
|
16
21
|
@name = name
|
|
17
22
|
@signal = :reply
|
|
18
23
|
@answers = []
|
|
19
24
|
end
|
|
20
25
|
|
|
26
|
+
# Names the verb a domain's aggregates call this port by when a hecksagon binds it.
|
|
27
|
+
#
|
|
28
|
+
# @param value [String, Symbol] the verb, such as `"asked_by"` or `"persisted_by"`
|
|
29
|
+
# @return [String] the verb as stored
|
|
21
30
|
def verb(value) = @verb = value.to_s
|
|
31
|
+
|
|
32
|
+
# Sets whether the domain waits for a value back; a port left unset signals `:reply`.
|
|
33
|
+
#
|
|
34
|
+
# @param value [Symbol, String] `:reply` when the adapter answers with a value,
|
|
35
|
+
# `:effect` when it is called only for its effect
|
|
36
|
+
# @return [Symbol] the signal as stored
|
|
22
37
|
def signal(value) = @signal = value.to_sym
|
|
23
38
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
39
|
+
# Declares one method an adapter bound to this port must respond to.
|
|
40
|
+
#
|
|
41
|
+
# **The method contract** — the fact a `.port` file's `verb`/`signal`
|
|
42
|
+
# never carried: what an adapter must actually respond to for a
|
|
26
43
|
# dispatch to reach it without a bare `NoMethodError`. Declared the
|
|
27
44
|
# same repeatable way `AdapterBuilder#field`/`#secret` already are,
|
|
28
45
|
# so `verify!` can check it with `respond_to?` at boot instead of
|
|
29
46
|
# the runtime discovering it live.
|
|
47
|
+
#
|
|
48
|
+
# @param name [Symbol, String] the method name, such as `:ask`
|
|
49
|
+
# @return [Array<Symbol>] every method declared so far, this one last
|
|
30
50
|
def answers(name) = @answers << name.to_sym
|
|
31
51
|
|
|
52
|
+
# Assembles the collected verb, signal and method contract, judged by the port language.
|
|
53
|
+
#
|
|
54
|
+
# @return [Bluebook::Port] the port, returned once the language accepts it
|
|
55
|
+
# @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration
|
|
32
56
|
def build
|
|
33
57
|
MetaValidator.call_port(Port.new(name: @name, verb: @verb, signal: @signal, answers: @answers))
|
|
34
58
|
end
|
|
35
59
|
|
|
60
|
+
# Evaluates a port block against a fresh builder and returns what it built.
|
|
61
|
+
#
|
|
62
|
+
# @param name [String] the port's name
|
|
63
|
+
# @yield the port body, evaluated with the builder as `self`; may be omitted
|
|
64
|
+
# @return [Bluebook::Port] the judged port
|
|
65
|
+
# @raise [Bluebook::DSL::Malformed] if the port language refuses the declaration, or
|
|
66
|
+
# the block uses a word the `Port` grammar does not admit
|
|
36
67
|
def self.build(name, &block)
|
|
37
68
|
builder = new(name)
|
|
38
69
|
builder.instance_eval(&block) if block
|
|
@@ -14,31 +14,34 @@ module Hecks
|
|
|
14
14
|
include AttributeCollector
|
|
15
15
|
include WordGate
|
|
16
16
|
|
|
17
|
-
# `to:` —
|
|
18
|
-
# operation body
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
# (domain_port.bluebook) for operation/tells/asks to receive it —
|
|
23
|
-
# confirmed by grep across every lib/hecks/language file, not
|
|
24
|
-
# assumed.
|
|
17
|
+
# `to:` — the sanctioned replacement for `reference_to` inside an
|
|
18
|
+
# operation body: `reference_to_impl`'s own refusal message tells
|
|
19
|
+
# authors to "pass the receiving aggregate in to:", and this is
|
|
20
|
+
# the argument DomainPort's own grammar (domain_port.bluebook)
|
|
21
|
+
# gives operation/tells/asks to receive it.
|
|
25
22
|
#
|
|
26
|
-
#
|
|
23
|
+
# **Genuine routing metadata, not an attribute** — matching
|
|
27
24
|
# rust/parser/src/parse/domain_port.rs's own header comment ("The
|
|
28
25
|
# receiving aggregate is routing metadata supplied by to:, not an
|
|
29
26
|
# operation attribute"), which anticipated this shape before either
|
|
30
27
|
# side actually built it. Stored separately (below, threaded to
|
|
31
28
|
# PortOperation as `to:`) rather than reusing reference_to_impl's
|
|
32
29
|
# own attribute-adding path — Dispatcher#port_invocation
|
|
33
|
-
# (lib/hecks/runtime/dispatcher.rb) is the
|
|
30
|
+
# (lib/hecks/runtime/dispatcher.rb) is the one place that resolves
|
|
34
31
|
# routing at dispatch time; it gained a second, purely additive
|
|
35
|
-
# branch for this (falls back to a plain attribute
|
|
32
|
+
# branch for this (falls back to a plain attribute named for the
|
|
36
33
|
# owning aggregate's own identified_by field, the same "declare
|
|
37
34
|
# only external facts with attribute" the refusal message
|
|
38
35
|
# describes) rather than folding `to:` into identity_attribute's
|
|
39
36
|
# existing Reference-attribute scan, which every operation already
|
|
40
37
|
# in the corpus (Banking, pizzas, lifeadelics' own vendored
|
|
41
38
|
# PaymentGateway) still relies on unchanged.
|
|
39
|
+
#
|
|
40
|
+
# @param name [String] the operation's name
|
|
41
|
+
# @param to [Symbol, String, Module, nil] the aggregate the operation routes to; stored
|
|
42
|
+
# demodulised, and nil when the operation names none
|
|
43
|
+
# @param owner [String, nil] name of the aggregate the enclosing port is declared on
|
|
44
|
+
# @param direction [Symbol] `:inbound` for `operation`/`tells`, `:outbound` for `asks`
|
|
42
45
|
def initialize(name, to: nil, owner: nil, direction: :inbound)
|
|
43
46
|
@name = name
|
|
44
47
|
@to = to && Naming.demodulise(to)
|
|
@@ -47,23 +50,33 @@ module Hecks
|
|
|
47
50
|
@emits = []
|
|
48
51
|
end
|
|
49
52
|
|
|
50
|
-
#
|
|
53
|
+
# Refuses `reference_to` inside an operation body, except while shadow-parsing, where it
|
|
54
|
+
# adds the reference attribute frozen era text declared.
|
|
55
|
+
#
|
|
56
|
+
# Always an attribute, never the self-reference `CommandBuilder`
|
|
51
57
|
# spells — a port operation has no `creates?`/`acts_on` distinction
|
|
52
58
|
# to protect, so there is nothing for the self-reference branch to be
|
|
53
|
-
#
|
|
59
|
+
# for here. `reference_to Payment, as: :payment_id` reads the same
|
|
54
60
|
# even though the target happens to equal the owning aggregate.
|
|
55
|
-
#
|
|
56
|
-
# dispatch (slice 4b).
|
|
57
|
-
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
61
|
+
# Answers the `reference_to` word through the table's `calls:`
|
|
62
|
+
# column — item #13's full metaprogrammed dispatch (slice 4b).
|
|
63
|
+
# Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
58
64
|
#
|
|
59
|
-
#
|
|
65
|
+
# Disabled, #335 — kept only for MetaValidator's own shadow-parsing
|
|
60
66
|
# pass (the self-hosted grammar's own KeywordSeed/ArgumentSeed rows
|
|
61
67
|
# for "reference_to" in this context are themselves declared using
|
|
62
68
|
# this construct, one level up — deleting the Ruby method would
|
|
63
69
|
# break the language describing itself, not just old domain
|
|
64
|
-
# authors). Every
|
|
70
|
+
# authors). Every real domain author reaches `to:` instead, above —
|
|
65
71
|
# a genuinely different mechanism now, not a relocated spelling of
|
|
66
72
|
# this one (see `to:`'s own comment).
|
|
73
|
+
#
|
|
74
|
+
# @param type [Symbol, String, Module] the referenced aggregate, written as a bare constant
|
|
75
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from the target, as in
|
|
76
|
+
# `:payment_id` under shadow-parsing
|
|
77
|
+
# @return [nil] nothing useful; the reference lands in `attributes`
|
|
78
|
+
# @raise [Bluebook::DSL::Malformed] always outside shadow-parsing, since routing belongs
|
|
79
|
+
# in `to:`; under shadow-parsing, if the attribute name is already declared
|
|
67
80
|
def reference_to_impl(type, as: nil)
|
|
68
81
|
unless MetaValidator.shadow_parsing?
|
|
69
82
|
raise Malformed,
|
|
@@ -74,9 +87,13 @@ module Hecks
|
|
|
74
87
|
add_reference!(type, as: as)
|
|
75
88
|
end
|
|
76
89
|
|
|
90
|
+
# Names one event an inbound operation records once the external fact has arrived.
|
|
91
|
+
#
|
|
92
|
+
# @param event_name [String, Symbol] the event's name, such as `"PaymentSettled"`
|
|
93
|
+
# @return [Array<String>] every event named so far, this one last
|
|
77
94
|
def emits(event_name) = @emits << event_name.to_s
|
|
78
95
|
|
|
79
|
-
#
|
|
96
|
+
# The two halves of an `asks`. An outbound call has exactly two
|
|
80
97
|
# endings and the chapter names both — `answers` for what came back,
|
|
81
98
|
# `refuses` for what the outside said instead. They are separate words
|
|
82
99
|
# rather than two `emits` because a reader has to be able to tell them
|
|
@@ -87,6 +104,12 @@ module Hecks
|
|
|
87
104
|
# kind-driven coerce-and-assign with nothing else, now executed
|
|
88
105
|
# by `GenericDispatch`.
|
|
89
106
|
|
|
107
|
+
# Assembles the operation, refusing words that belong to the other direction.
|
|
108
|
+
#
|
|
109
|
+
# @return [Bluebook::PortOperation] the operation with its attributes, events and routing
|
|
110
|
+
# @raise [Bluebook::DSL::Malformed] if an inbound operation declares no `emits` or
|
|
111
|
+
# declares `answers`/`refuses`, or an outbound one declares `emits` or lacks either
|
|
112
|
+
# `answers` or `refuses`
|
|
90
113
|
def build
|
|
91
114
|
outbound = @direction == :outbound
|
|
92
115
|
refuse_wrong_words!(outbound)
|
|
@@ -96,7 +119,7 @@ module Hecks
|
|
|
96
119
|
direction: @direction, answers: @answers, refuses: @refuses, to: @to
|
|
97
120
|
)
|
|
98
121
|
|
|
99
|
-
#
|
|
122
|
+
# An inbound operation still has to say something. Only inbound: an
|
|
100
123
|
# `asks` says it with `answers`/`refuses` instead, and
|
|
101
124
|
# `refuse_wrong_words!` above has already insisted on both.
|
|
102
125
|
if !outbound && @emits.empty?
|
|
@@ -110,7 +133,7 @@ module Hecks
|
|
|
110
133
|
|
|
111
134
|
private
|
|
112
135
|
|
|
113
|
-
#
|
|
136
|
+
# The one place a reference attribute actually gets added — both
|
|
114
137
|
# `to:` (initialize, above) and reference_to_impl's own shadow-
|
|
115
138
|
# parsing branch call this, so there is exactly one real
|
|
116
139
|
# implementation of "carry a Reference-typed external fact,"
|
|
@@ -120,7 +143,7 @@ module Hecks
|
|
|
120
143
|
attribute_impl(as || default_reference_name(target), Reference.new(target))
|
|
121
144
|
end
|
|
122
145
|
|
|
123
|
-
#
|
|
146
|
+
# **Each direction refuses the other's words**. `emits` on an `asks` looks
|
|
124
147
|
# right and is not: it would name one ending and leave the other
|
|
125
148
|
# nowhere. `answers` on a `tells` is worse — there is no channel back
|
|
126
149
|
# to an inbound caller at all, so it would read as a promise the
|
|
@@ -147,10 +170,21 @@ module Hecks
|
|
|
147
170
|
end
|
|
148
171
|
end
|
|
149
172
|
|
|
173
|
+
# Evaluates one operation block against a fresh builder and returns what it built.
|
|
174
|
+
#
|
|
150
175
|
# `private` above (scoping the instance methods between it and here)
|
|
151
176
|
# doesn't reach a singleton method — correctly so: `.build` is this
|
|
152
177
|
# builder's real public entry point (DomainPortBuilder calls it),
|
|
153
178
|
# never meant to be private.
|
|
179
|
+
#
|
|
180
|
+
# @param name [String] the operation's name
|
|
181
|
+
# @param to [Symbol, String, Module, nil] the aggregate the operation routes to, or nil
|
|
182
|
+
# @param owner [String, nil] name of the aggregate the enclosing port is declared on
|
|
183
|
+
# @param direction [Symbol] `:inbound` for `operation`/`tells`, `:outbound` for `asks`
|
|
184
|
+
# @yield the operation body, evaluated with the builder as `self`; may be omitted
|
|
185
|
+
# @return [Bluebook::PortOperation] the built operation
|
|
186
|
+
# @raise [Bluebook::DSL::Malformed] if the body uses the other direction's words, omits
|
|
187
|
+
# the ones its own direction requires, or uses a word the grammar does not admit
|
|
154
188
|
# rubocop:disable-next Lint/IneffectiveAccessModifier
|
|
155
189
|
def self.build(name, to: nil, owner: nil, direction: :inbound, &block)
|
|
156
190
|
builder = new(name, to: to, owner: owner, direction: direction)
|
|
@@ -7,7 +7,7 @@ module Hecks
|
|
|
7
7
|
# correlates its instances (`correlates_by`), and the `transition`-
|
|
8
8
|
# declared state machine whose `dispatch`es (each optionally paired
|
|
9
9
|
# with its own per-dispatch `compensates`) become its handlers. States
|
|
10
|
-
# are
|
|
10
|
+
# are derived from the transitions rather than declared separately
|
|
11
11
|
# (S7, ADR 0025).
|
|
12
12
|
class ProcessManagerBuilder
|
|
13
13
|
GRAMMAR_CONTEXT = "ProcessManager".freeze
|
|
@@ -16,26 +16,39 @@ module Hecks
|
|
|
16
16
|
|
|
17
17
|
include WordGate
|
|
18
18
|
|
|
19
|
+
# @param name [String] the process manager's own name, as written after `process_manager`
|
|
19
20
|
def initialize(name)
|
|
20
21
|
@name = name
|
|
21
22
|
@handlers = []
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
#
|
|
25
|
-
#
|
|
25
|
+
# Records the event that begins a fresh instance of this process manager.
|
|
26
|
+
#
|
|
27
|
+
# `starts_on Transfer::TransferRequested` — bare constant
|
|
28
|
+
# accepted (ADR 0025, S6 — "events first-class"), resolved
|
|
26
29
|
# through `ConstShim` the same way `on_impl`/`transition_impl`
|
|
27
30
|
# resolve an event reference — but through `Naming.
|
|
28
|
-
# event_name_ref`,
|
|
31
|
+
# event_name_ref`, not `Naming.event_ref` (that method's own
|
|
29
32
|
# header has the full account: `SagaInterpreter` matches
|
|
30
|
-
# `pm.starts_on`/`pm.ends_on` against a
|
|
33
|
+
# `pm.starts_on`/`pm.ends_on` against a bare `event.name`, never
|
|
31
34
|
# a "." qualified one). A plain String still passes through
|
|
32
35
|
# unchanged, both for `shadow_parse` and for any corpus site a
|
|
33
36
|
# future pass hasn't migrated yet.
|
|
37
|
+
#
|
|
38
|
+
# @param event_ref [Symbol, String, Module] the event, as a bare constant (a
|
|
39
|
+
# `ScopedConstant` module `ConstShim` resolves) or quoted text
|
|
40
|
+
# @return [void]
|
|
34
41
|
def starts_on_impl(event_ref)
|
|
35
42
|
@starts_on = Naming.event_name_ref(event_ref)
|
|
36
43
|
end
|
|
37
44
|
|
|
45
|
+
# Records the event this process manager expects to close out its instance on.
|
|
46
|
+
#
|
|
38
47
|
# `ends_on` — same reasoning as `starts_on_impl`, above.
|
|
48
|
+
#
|
|
49
|
+
# @param event_ref [Symbol, String, Module] the event, as a bare constant (a
|
|
50
|
+
# `ScopedConstant` module `ConstShim` resolves) or quoted text
|
|
51
|
+
# @return [void]
|
|
39
52
|
def ends_on_impl(event_ref)
|
|
40
53
|
@ends_on = Naming.event_name_ref(event_ref)
|
|
41
54
|
end
|
|
@@ -45,34 +58,37 @@ module Hecks
|
|
|
45
58
|
# kind-driven coerce-and-assign with nothing else, executed by
|
|
46
59
|
# `GenericDispatch`.
|
|
47
60
|
#
|
|
48
|
-
# `starts_on`/`ends_on`
|
|
49
|
-
#
|
|
50
|
-
# ArgumentSeed row (`starts_on
|
|
51
|
-
# `GenericDispatch::COERCE_BY_KIND`
|
|
52
|
-
# `"symbol"`, and `shape_for` refuses to pick a
|
|
53
|
-
# shape at all once two Argument rows share a
|
|
54
|
-
# (`arguments.size == 1` below), so both words are
|
|
55
|
-
#
|
|
56
|
-
|
|
57
|
-
#
|
|
58
|
-
#
|
|
61
|
+
# `starts_on`/`ends_on` are hand-written rather than table-driven
|
|
62
|
+
# the same way, because ADR 0025 S6 gives each a second,
|
|
63
|
+
# `kind: "constant"` ArgumentSeed row (`starts_on
|
|
64
|
+
# Transfer::TransferRequested`) — `GenericDispatch::COERCE_BY_KIND`
|
|
65
|
+
# only knows `"text"`/`"symbol"`, and `shape_for` refuses to pick a
|
|
66
|
+
# `single_fill` shape at all once two Argument rows share a
|
|
67
|
+
# `fills:` target (`arguments.size == 1` below), so both words are
|
|
68
|
+
# `calls:`-routed like `transition` already is.
|
|
69
|
+
|
|
70
|
+
# Records one leg of this process manager's state machine: the event that takes it, the
|
|
71
|
+
# state(s) it applies from, and the dispatches (and optional compensation) it fires.
|
|
72
|
+
#
|
|
73
|
+
# One state-machine vocabulary (S7, ADR 0025 — "events and
|
|
74
|
+
# reactions"): the same word `Lifecycle#transition` already
|
|
59
75
|
# carries, one level over — `transition "AccountDebited" =>
|
|
60
76
|
# "awaiting_credit", from: "requested" do ... end` replaces `on
|
|
61
77
|
# "AccountDebited", transition: { "requested" => "awaiting_
|
|
62
78
|
# credit" } do ... end`. Same bare rocket-pair argument shape
|
|
63
|
-
# (not a
|
|
79
|
+
# (not a named `transition:` kwarg wrapping a second Hash), same
|
|
64
80
|
# `from:` — including the array form Lifecycle's own commands
|
|
65
81
|
# could already take and a process manager's own events could
|
|
66
|
-
# not — and the states a procedure runs on are
|
|
82
|
+
# not — and the states a procedure runs on are derived from the
|
|
67
83
|
# transitions that name them, the same way `Behaviour::Lifecycle
|
|
68
84
|
# #states` already derives an aggregate's ; `state "x"` lines
|
|
69
85
|
# duplicated exactly what the transition list already said,
|
|
70
86
|
# and could drift from it (`validate!`'s own "undeclared state"
|
|
71
87
|
# check existed only because they could).
|
|
72
88
|
#
|
|
73
|
-
# `starts_on`/`ends_on` are
|
|
89
|
+
# `starts_on`/`ends_on` are not unified into this — verified
|
|
74
90
|
# against the real corpus rather than assumed: Settlement's own
|
|
75
|
-
# `ends_on "TransferSettled"` names an event
|
|
91
|
+
# `ends_on "TransferSettled"` names an event none of its own
|
|
76
92
|
# transitions ever handle (`Transfer.Settle`'s own emission, a
|
|
77
93
|
# full step downstream of the transition that dispatches it),
|
|
78
94
|
# so "the terminal state's own event" is not a fact the
|
|
@@ -81,25 +97,33 @@ module Hecks
|
|
|
81
97
|
# cover the case, which is not less vocabulary than keeping the
|
|
82
98
|
# one that already says it correctly.
|
|
83
99
|
#
|
|
84
|
-
#
|
|
100
|
+
# Expands immediately, unlike `Lifecycle#transition` (which
|
|
85
101
|
# defers to `Behaviour::Lifecycle#expand`, called at emission
|
|
86
102
|
# time) — `ProcessManager`'s own IR constructor takes `states:`/
|
|
87
103
|
# `handlers:` exactly as it always has, so the runtime
|
|
88
104
|
# (`Behaviour::ProcessManager`, `SagaInterpreter`, saga
|
|
89
105
|
# persistence/rehydration) needs no change at all: what changed
|
|
90
|
-
# is how the
|
|
106
|
+
# is how the declaration reaches that same shape, not the shape
|
|
91
107
|
# a real run ever sees or persists.
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
108
|
+
# Reached through `calls: "transition_impl"`. Not exercised during boot itself — checked
|
|
109
|
+
# directly, no core/attached chapter declares a `ProcessManager` of its own — though the
|
|
110
|
+
# (context, word) pair is still carried in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`
|
|
111
|
+
# unconditionally, along with every other `calls:`-routed row.
|
|
112
|
+
#
|
|
113
|
+
# @param mapping [Hash] one `event => target_state` pair plus a required `from:` key
|
|
114
|
+
# naming the source state(s) (`String`, `Symbol`, or `Array<String, Symbol>`) this
|
|
115
|
+
# transition applies from
|
|
116
|
+
# @yield the transition's dispatch body, `instance_eval`'d against a fresh `HandlerBuilder`
|
|
117
|
+
# @return [void]
|
|
118
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `mapping` names
|
|
119
|
+
# no `from:`
|
|
96
120
|
def transition_impl(mapping, &block)
|
|
97
121
|
mapping = mapping.dup
|
|
98
122
|
from = mapping.delete(:from)
|
|
99
123
|
|
|
100
|
-
#
|
|
124
|
+
# Always required, unlike `Lifecycle#transition`'s own `from:`
|
|
101
125
|
# — an aggregate's unconstrained transition is admitted from
|
|
102
|
-
#
|
|
126
|
+
# any current state (`Behaviour::Lifecycle#applies_from?`
|
|
103
127
|
# returns true for a nil `from`), a reading `SagaInterpreter#
|
|
104
128
|
# advance_saga`'s own admission check does not share: it tests
|
|
105
129
|
# `instance[:state] == handler.from_state` by plain equality,
|
|
@@ -120,14 +144,14 @@ module Hecks
|
|
|
120
144
|
handler.instance_eval(&block) if block
|
|
121
145
|
|
|
122
146
|
mapping.each do |event_type, target|
|
|
123
|
-
#
|
|
147
|
+
# Bare constant accepted (ADR 0025, S6 — "events first-
|
|
124
148
|
# class"), `transition Account::AccountDebited => "state"` —
|
|
125
|
-
# `Naming.event_name_ref`,
|
|
149
|
+
# `Naming.event_name_ref`, not the dotted `Naming.event_ref`
|
|
126
150
|
# transform `PolicyBuilder#on_impl` uses (that method's own
|
|
127
151
|
# header has the full account, found live wiring a real
|
|
128
152
|
# migrated corpus site into `bin/model_check` for the first
|
|
129
153
|
# time: `SagaInterpreter#advance_saga` matches `handler.
|
|
130
|
-
# event_type` against a
|
|
154
|
+
# event_type` against a bare `event.name`, never a "."
|
|
131
155
|
# qualified one — a policy's own cross-aggregate match
|
|
132
156
|
# works differently, splitting the qualifier apart from the
|
|
133
157
|
# name rather than comparing the whole string). Writing the
|
|
@@ -142,6 +166,14 @@ module Hecks
|
|
|
142
166
|
end
|
|
143
167
|
end
|
|
144
168
|
|
|
169
|
+
# Assembles the declared transitions into a `ProcessManager`, after validating them.
|
|
170
|
+
#
|
|
171
|
+
# @return [Bluebook::ProcessManager] the built process manager, with its own `states`
|
|
172
|
+
# derived from the declared transitions
|
|
173
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `correlates_by`
|
|
174
|
+
# is undeclared or names a whole field rather than one scalar, if `starts_on` is
|
|
175
|
+
# undeclared, if no transition was declared, or if two transitions answer the same event
|
|
176
|
+
# from the same source state
|
|
145
177
|
def build
|
|
146
178
|
validate!
|
|
147
179
|
|
|
@@ -155,6 +187,13 @@ module Hecks
|
|
|
155
187
|
)
|
|
156
188
|
end
|
|
157
189
|
|
|
190
|
+
# Evaluates a `process_manager` block against a fresh builder and returns what it built.
|
|
191
|
+
#
|
|
192
|
+
# @param name [String] the process manager's own name
|
|
193
|
+
# @yield the process manager's body, `instance_eval`'d against a new builder; may be
|
|
194
|
+
# omitted
|
|
195
|
+
# @return [Bluebook::ProcessManager] the built process manager
|
|
196
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] see `#build`
|
|
158
197
|
def self.build(name, &block)
|
|
159
198
|
builder = new(name)
|
|
160
199
|
builder.instance_eval(&block) if block
|
|
@@ -163,12 +202,12 @@ module Hecks
|
|
|
163
202
|
|
|
164
203
|
private
|
|
165
204
|
|
|
166
|
-
#
|
|
205
|
+
# One declared transition is several rows when `from` names more
|
|
167
206
|
# than one source state — `Behaviour::Lifecycle#expand`'s own
|
|
168
207
|
# comment, the identical fan-out, one level over: a
|
|
169
208
|
# `ProcessManagerHandler` only ever carries a single `from_state`,
|
|
170
209
|
# so a `from: [...]` transition mints one row per source, each
|
|
171
|
-
# carrying the
|
|
210
|
+
# carrying the same dispatches.
|
|
172
211
|
def expand(event_type, transition, dispatches)
|
|
173
212
|
sources = transition.from.nil? ? [nil] : Array(transition.from)
|
|
174
213
|
|
|
@@ -182,12 +221,12 @@ module Hecks
|
|
|
182
221
|
end
|
|
183
222
|
end
|
|
184
223
|
|
|
185
|
-
#
|
|
224
|
+
# Derived, not declared (S7) — every state this procedure ever
|
|
186
225
|
# runs on is already named by some transition's own `from_state`
|
|
187
226
|
# or `to_state`; a state nothing transitions into or out of is
|
|
188
227
|
# not a state this procedure has, the same reading
|
|
189
228
|
# `Behaviour::Lifecycle#states` already gives an aggregate's own
|
|
190
|
-
# field.
|
|
229
|
+
# field. First-seen order, walking declaration order — `begin_
|
|
191
230
|
# saga`'s own `pm.states.first` is what a fresh instance starts
|
|
192
231
|
# in, so the order has to survive the derivation, not just the
|
|
193
232
|
# membership.
|
|
@@ -201,15 +240,15 @@ module Hecks
|
|
|
201
240
|
"nothing would tie its events to one instance"
|
|
202
241
|
end
|
|
203
242
|
|
|
204
|
-
#
|
|
243
|
+
# **The field, named** — never the value object that carries it. A bare
|
|
205
244
|
# `correlates_by :end_to_end` reads whatever the payload holds under
|
|
206
|
-
# that key
|
|
245
|
+
# that key as the correlation key, and what a non-scalar key even
|
|
207
246
|
# is stays open (the object itself? its serialised text?).
|
|
208
247
|
# Requiring the dotted spelling —
|
|
209
248
|
# `:"end_to_end.value"` — makes every correlates_by name a scalar
|
|
210
249
|
# by construction, the same discipline `identified_by` already
|
|
211
250
|
# holds a head to. This is a syntactic check, not a type check: it
|
|
212
|
-
# does not know or care whether the field
|
|
251
|
+
# does not know or care whether the field is a value object, only
|
|
213
252
|
# that the declaration cannot leave that question open.
|
|
214
253
|
unless @correlates_by.to_s.include?(".")
|
|
215
254
|
raise InvalidProcessManager, "#{@name} correlates_by #{@correlates_by.inspect}, which names a whole " \
|
|
@@ -231,7 +270,7 @@ module Hecks
|
|
|
231
270
|
end
|
|
232
271
|
|
|
233
272
|
# C10.3 — a leg is selected by (event, current state), so two
|
|
234
|
-
# legs answering the
|
|
273
|
+
# legs answering the same event from the same state would leave
|
|
235
274
|
# the runtime to pick by declaration order, silently. Refused
|
|
236
275
|
# here, where the declaration can still be read whole. (`from:
|
|
237
276
|
# [...]` fan-out counts: `transition E => "a", from: ["x", "y"]`
|
|
@@ -253,7 +292,7 @@ module Hecks
|
|
|
253
292
|
end
|
|
254
293
|
end
|
|
255
294
|
|
|
256
|
-
#
|
|
295
|
+
# The body of one `transition ... do ... end` block — collects the
|
|
257
296
|
# `dispatch` calls (each optionally opening its own `compensates`
|
|
258
297
|
# via the nested `DispatchBuilder`) that fire when this transition
|
|
259
298
|
# is taken.
|
|
@@ -264,19 +303,22 @@ module Hecks
|
|
|
264
303
|
|
|
265
304
|
include WordGate
|
|
266
305
|
|
|
306
|
+
# Starts this handler's own dispatch list empty.
|
|
267
307
|
def initialize = @dispatches = []
|
|
268
308
|
|
|
269
|
-
#
|
|
309
|
+
# Records one command this transition dispatches, and, if given a block, the
|
|
310
|
+
# compensation that reverses it.
|
|
311
|
+
#
|
|
312
|
+
# The command itself (ADR 0025, "events and reactions" — command
|
|
270
313
|
# references become first-class), same shape and same reasons
|
|
271
314
|
# as `PolicyBuilder#trigger`'s own header — bare constant live,
|
|
272
315
|
# quoted text only under shadow-parsing (S0a's bridge; frozen
|
|
273
316
|
# era text still writes `dispatch "Banking::Account.Debit"`).
|
|
274
317
|
#
|
|
275
|
-
#
|
|
276
|
-
# dispatch (slice 4), same reasoning as trigger_impl above.
|
|
318
|
+
# Reached through `calls: "dispatch_impl"`.
|
|
277
319
|
#
|
|
278
|
-
#
|
|
279
|
-
#
|
|
320
|
+
# An optional block opens `compensates` on this dispatch
|
|
321
|
+
# specifically — per-dispatch saga compensation, replacing a
|
|
280
322
|
# hand-written list at the saga's own `on :refused` leg. Real,
|
|
281
323
|
# live bug this closes: `examples/banking/bluebook/transfers_
|
|
282
324
|
# and_payments.bluebook`'s own `Settlement` saga wrote a
|
|
@@ -287,6 +329,17 @@ module Hecks
|
|
|
287
329
|
# (`SagaInterpreter`'s own `completed_compensations`) and
|
|
288
330
|
# compensates only those, newest first, instead of trusting an
|
|
289
331
|
# author's static list to be complete and correctly ordered.
|
|
332
|
+
#
|
|
333
|
+
# @param command_ref [Symbol, String, Module] the command, as a bare constant (a
|
|
334
|
+
# `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing, quoted text
|
|
335
|
+
# @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
|
|
336
|
+
# arguments, the same `key => value` shape a policy's own `trigger ..., with:` takes;
|
|
337
|
+
# `nil` forwards the triggering context verbatim
|
|
338
|
+
# @yield the dispatch's own `compensates` body, `instance_eval`'d against a
|
|
339
|
+
# `DispatchBuilder`
|
|
340
|
+
# @return [Bluebook::DispatchSpec] the dispatch just recorded
|
|
341
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
|
|
342
|
+
# is quoted text outside shadow-parsing
|
|
290
343
|
def dispatch_impl(command_ref, with: nil, &block)
|
|
291
344
|
if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
|
|
292
345
|
raise InvalidProcessManager,
|
|
@@ -315,9 +368,9 @@ module Hecks
|
|
|
315
368
|
spec
|
|
316
369
|
end
|
|
317
370
|
|
|
318
|
-
#
|
|
371
|
+
# The nested scope `dispatch ... do ... end` opens — one word
|
|
319
372
|
# only (`compensates`), the compensating half of the dispatch it
|
|
320
|
-
# sits inside. Its own `compensates_impl` builds a
|
|
373
|
+
# sits inside. Its own `compensates_impl` builds a second
|
|
321
374
|
# `DispatchSpec`, shape-identical to `HandlerBuilder#dispatch_
|
|
322
375
|
# impl`'s own — a compensation takes the exact same two
|
|
323
376
|
# arguments (a bare command constant, an optional `with:`)
|
|
@@ -330,6 +383,17 @@ module Hecks
|
|
|
330
383
|
|
|
331
384
|
include WordGate
|
|
332
385
|
|
|
386
|
+
# Records the command that reverses the dispatch this `compensates` block sits inside.
|
|
387
|
+
#
|
|
388
|
+
# @param command_ref [Symbol, String, Module] the compensating command, as a bare
|
|
389
|
+
# constant (a `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing,
|
|
390
|
+
# quoted text
|
|
391
|
+
# @param with [Hash{Symbol => Object}, nil] a projection onto the command's own
|
|
392
|
+
# arguments, the same shape `HandlerBuilder#dispatch_impl`'s own `with:` takes; `nil`
|
|
393
|
+
# forwards the triggering context verbatim
|
|
394
|
+
# @return [Bluebook::DispatchSpec] the compensating dispatch just recorded
|
|
395
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if `command_ref`
|
|
396
|
+
# is quoted text outside shadow-parsing
|
|
333
397
|
def compensates_impl(command_ref, with: nil)
|
|
334
398
|
if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
|
|
335
399
|
raise InvalidProcessManager,
|