hecks 1.2.0 → 1.4.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 +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +60 -2
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -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/postgres_era.adapter +5 -0
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- 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.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +145 -0
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +51 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -3
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -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 +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- 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 +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- 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 +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- 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 +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +122 -86
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +301 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- 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 +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -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 -20
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +361 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +124 -0
- data/lib/hecks/fuzzing/form_census.rb +199 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +226 -38
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +161 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +100 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +321 -41
- data/lib/hecks/fuzzing/properties/guards.rb +129 -26
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +31 -11
- data/lib/hecks/fuzzing/properties/outbox.rb +142 -0
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +84 -39
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +224 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +94 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +676 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +526 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +101 -30
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +55 -8
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +31 -11
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +123 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +152 -31
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +39 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +53 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +180 -0
- data/lib/hecks/fuzzing/value_generator.rb +66 -14
- data/lib/hecks/fuzzing.rb +11 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- 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 +365 -15
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +89 -21
- data/lib/hecks/ports/access_control.rb +58 -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 +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- 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 +160 -58
- 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 +28 -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 +44 -6
- 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 +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +338 -83
- 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 +42 -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 +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -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 +29 -6
- 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 +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +250 -0
- data/lib/hecks/projections/glossary/markdown.rb +105 -0
- data/lib/hecks/projections/glossary/mermaid.rb +110 -0
- data/lib/hecks/projections/glossary/page.css +271 -0
- data/lib/hecks/projections/glossary/page.js +72 -0
- data/lib/hecks/projections/glossary/sections.rb +17 -0
- data/lib/hecks/projections/glossary/sentences.rb +205 -0
- data/lib/hecks/projections/glossary.rb +214 -286
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +15 -22
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +132 -24
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- 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 +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +64 -67
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- 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 +94 -19
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +282 -46
- data/lib/hecks/runtime/entity_interpreter.rb +160 -69
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +111 -56
- data/lib/hecks/runtime/reaction_invocation.rb +76 -9
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -112
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +56 -27
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -88
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +93 -56
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +379 -226
- data/lib/hecks/runtime/value/entity_list_coercion.rb +248 -0
- data/lib/hecks/runtime/value.rb +28 -23
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +207 -5
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +43 -2
|
@@ -3,11 +3,11 @@ module Hecks
|
|
|
3
3
|
module Bluebook
|
|
4
4
|
module DSL
|
|
5
5
|
# Parses a `.hecksagon` file's top-level DSL block into a `Hecksagon`
|
|
6
|
-
# — a domain's own
|
|
6
|
+
# — a domain's own wiring: which adapter binds to which verb
|
|
7
7
|
# (`persisted_by`, `projected_by`, ...), which framework/vendored
|
|
8
8
|
# bluebooks it attaches, which external events it subscribes to, and
|
|
9
9
|
# its bare chapter-root port. Kept separate from the bluebook itself
|
|
10
|
-
# (the domain's own declared
|
|
10
|
+
# (the domain's own declared model) because wiring is an operational
|
|
11
11
|
# decision, not a fact the domain states about itself.
|
|
12
12
|
class HecksagonBuilder
|
|
13
13
|
GRAMMAR_CONTEXT = "Hecksagon".freeze
|
|
@@ -20,6 +20,7 @@ module Hecks
|
|
|
20
20
|
|
|
21
21
|
attr_reader :binds, :subscriptions, :framework_members, :vendored_bluebooks
|
|
22
22
|
|
|
23
|
+
# @param domain [String] name of the domain whose wiring this hecksagon declares
|
|
23
24
|
def initialize(domain)
|
|
24
25
|
@domain = domain
|
|
25
26
|
@binds = []
|
|
@@ -28,44 +29,65 @@ module Hecks
|
|
|
28
29
|
@vendored_bluebooks = []
|
|
29
30
|
end
|
|
30
31
|
|
|
31
|
-
#
|
|
32
|
-
#
|
|
32
|
+
# Subscribes this hecksagon to an event it takes from outside the domain's own bluebook.
|
|
33
|
+
#
|
|
34
|
+
# @param event [String, Symbol] the external event's name
|
|
35
|
+
# @return [Array<String>] every subscription declared so far, this one last
|
|
33
36
|
def subscribe(event) = @subscriptions << event.to_s
|
|
34
37
|
|
|
38
|
+
# Attaches a framework member to this domain and loads it into the current registry.
|
|
39
|
+
#
|
|
35
40
|
# A framework/ member this domain wants attached —
|
|
36
41
|
# Governance, Identity, whatever else lands beside them.
|
|
37
|
-
# Attaching one is a
|
|
42
|
+
# Attaching one is a wiring decision, the same kind `persisted_by`/
|
|
38
43
|
# `projected_by` already are, so it lives here rather than as a
|
|
39
|
-
# fact stated in the domain's own bluebook. Recorded onto
|
|
44
|
+
# fact stated in the domain's own bluebook. Recorded onto this
|
|
40
45
|
# hecksagon, the same way `subscribe` records onto its own
|
|
41
46
|
# `subscriptions` — and loads the member's bluebook then its own
|
|
42
47
|
# hecksagon into whatever registry this one is loading into, see
|
|
43
48
|
# `Framework.load!`.
|
|
49
|
+
#
|
|
50
|
+
# @param name [String, Symbol] the member's name, such as `"Governance"`
|
|
51
|
+
# @return [Boolean, nil] true when this call loaded the member's bluebook, nil when the
|
|
52
|
+
# current registry already held it
|
|
53
|
+
# @raise [Runtime::WiringError] if no framework member has that name
|
|
44
54
|
def uses_framework(name)
|
|
45
55
|
@framework_members << name.to_s
|
|
46
56
|
Hecks::Framework.load!(name)
|
|
47
57
|
end
|
|
48
58
|
|
|
49
|
-
#
|
|
59
|
+
# Attaches a vendored embryonaut bluebook to this domain and loads its files into the
|
|
60
|
+
# current registry.
|
|
61
|
+
#
|
|
62
|
+
# A vendored, external bluebook this domain wants attached — same
|
|
50
63
|
# wiring-decision shape `uses_framework` already is, one level
|
|
51
64
|
# further out: not a member shipped inside hecks's own lib/,
|
|
52
|
-
# but a separate package (embryonaut_bluebooks) vendored into
|
|
65
|
+
# but a separate package (embryonaut_bluebooks) vendored into this
|
|
53
66
|
# project's own checkout. See EmbryonautBluebook's own header for
|
|
54
|
-
# the full reasoning on why its
|
|
67
|
+
# the full reasoning on why its root can't be a fixed constant the
|
|
55
68
|
# way Framework::ROOT is, and for the recovery provenance.
|
|
56
69
|
#
|
|
57
|
-
#
|
|
58
|
-
# already gives @framework_members — a
|
|
70
|
+
# Recorded onto @vendored_bluebooks, same shape `uses_framework`
|
|
71
|
+
# already gives @framework_members — a separate list on purpose:
|
|
59
72
|
# `framework_members` is load-bearing for `refuse_ungoverned_roles!`
|
|
60
|
-
# (
|
|
61
|
-
# the two would make a vendored bluebook attachment
|
|
62
|
-
# Governance check it has nothing to do with.
|
|
73
|
+
# (`Registry::Verification`) and for Governance's own attachment
|
|
74
|
+
# check; conflating the two would make a vendored bluebook attachment
|
|
75
|
+
# satisfy a Governance check it has nothing to do with.
|
|
76
|
+
#
|
|
77
|
+
# @param name [String, Symbol] the vendored package's directory name under
|
|
78
|
+
# `vendor/embryonaut_bluebooks/`
|
|
79
|
+
# @return [Array<String>, nil] paths of the `.bluebook` files this call loaded, or nil
|
|
80
|
+
# when the current registry already held the bluebook
|
|
81
|
+
# @raise [Runtime::WiringError] if the current registry has no root to vendor from, or
|
|
82
|
+
# no vendored bluebook of that name exists under it
|
|
63
83
|
def uses_embryonaut_bluebook(name)
|
|
64
84
|
@vendored_bluebooks << name.to_s
|
|
65
85
|
Hecks::EmbryonautBluebook.load!(name)
|
|
66
86
|
end
|
|
67
87
|
|
|
68
|
-
#
|
|
88
|
+
# Declares a port at the hecksagon's root and attaches it to the registered bluebook.
|
|
89
|
+
#
|
|
90
|
+
# The primary port, bare at the root — belongs to the chapter as a
|
|
69
91
|
# whole, not one aggregate. `BindingProxy#port` is the aggregate-
|
|
70
92
|
# scoped sibling (`Payments::Payment.port("Gateway") do ... end`);
|
|
71
93
|
# this is what's left when a port isn't about any one record. The
|
|
@@ -73,14 +95,24 @@ module Hecks
|
|
|
73
95
|
# loads after its bluebook, and this attaches to that real, final
|
|
74
96
|
# object directly rather than building a second copy MetaValidator
|
|
75
97
|
# would have to know how to reconstruct.
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
98
|
+
#
|
|
99
|
+
# Answers the `port` word through the table's `calls:` column —
|
|
100
|
+
# item #13's full metaprogrammed dispatch (slice 5). Not
|
|
101
|
+
# bootstrap-reachable (checked directly — no core/attached chapter
|
|
102
|
+
# declares a Hecksagon of its own). Reached through `WordGate`'s
|
|
103
|
+
# `word_gate_dispatch`, called explicitly below since
|
|
104
|
+
# `HecksagonBuilder`'s own class-level `method_missing` (the
|
|
105
|
+
# open-verb catch-all beneath this) always wins over the module's —
|
|
106
|
+
# see `word_gate.rb`'s own header for the full mechanism.
|
|
107
|
+
#
|
|
108
|
+
# @param name [String] the port's name
|
|
109
|
+
# @yield the port body, evaluated against a `DomainPortBuilder`: either `verb`/`signal`
|
|
110
|
+
# or `operation`/`tells`/`asks` blocks
|
|
111
|
+
# @return [Bluebook::Port, Bluebook::DomainPort] the built port: a verb-shaped `Port`
|
|
112
|
+
# registered on the current registry, or a `DomainPort` attached to the bluebook
|
|
113
|
+
# @raise [Bluebook::DSL::Malformed] if the current registry holds no bluebook for this
|
|
114
|
+
# domain, or the body declares both a verb and operations, neither, or an operation
|
|
115
|
+
# the port grammar refuses
|
|
84
116
|
def port_impl(name, &block)
|
|
85
117
|
bluebook_ir = Hecks.current_registry.bluebook(@domain) or
|
|
86
118
|
raise Malformed, "#{@domain} declares no such bluebook — a port needs one to belong to"
|
|
@@ -103,26 +135,33 @@ module Hecks
|
|
|
103
135
|
bluebook_ir.add_port(built)
|
|
104
136
|
end
|
|
105
137
|
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
138
|
+
# Assembles the collected binds, subscriptions and attachments into a `Hecksagon`.
|
|
139
|
+
#
|
|
140
|
+
# No ungoverned-role check here — see
|
|
141
|
+
# Registry::Verification#refuse_ungoverned_roles!. It lives outside
|
|
142
|
+
# per-block `build`, alongside `environment:`
|
|
109
143
|
# (Runtime::Loader.boot's comment has the provenance): a domain
|
|
110
144
|
# split across multiple hecksagon blocks (base + an
|
|
111
145
|
# `environments/<name>.hecksagon` overlay) would have every
|
|
112
146
|
# block but the one declaring `uses_framework "Governance"`
|
|
113
|
-
# refused
|
|
147
|
+
# refused here, even though `Registry#add_hecksagon` merges them
|
|
114
148
|
# into one Hecksagon before anything ever dispatches against it.
|
|
115
|
-
# Checking the
|
|
149
|
+
# Checking the merged result once, at verify! time — after every
|
|
116
150
|
# file for this domain has loaded — is both more permissive (no
|
|
117
151
|
# need to repeat `uses_framework` in every file) and strictly
|
|
118
152
|
# more correct (a check against an incomplete, not-yet-merged
|
|
119
153
|
# hecksagon can never see the real final shape).
|
|
154
|
+
#
|
|
155
|
+
# @return [Bluebook::Hecksagon] this block's wiring, which `Registry#add_hecksagon` merges
|
|
156
|
+
# with any other block declared for the same domain
|
|
120
157
|
def build
|
|
121
158
|
Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
|
|
122
159
|
framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks)
|
|
123
160
|
end
|
|
124
161
|
|
|
125
|
-
#
|
|
162
|
+
# Records any verb the grammar does not own as a domain-wide bind to the named adapter.
|
|
163
|
+
#
|
|
164
|
+
# **Domain-level default binds** — `persisted_by "Heki"` bare, at the top
|
|
126
165
|
# of a hecksagon block, applies to every aggregate in this domain
|
|
127
166
|
# that doesn't declare its own override. Mirrors `BindingProxy`'s own
|
|
128
167
|
# `method_missing` one level down (`aggregate:` filled in there,
|
|
@@ -130,12 +169,22 @@ module Hecks
|
|
|
130
169
|
# `persisted_by`/`projected_by` specifically, so any future verb
|
|
131
170
|
# gets a domain-level default for free too. See `Hecksagon#bind_for`
|
|
132
171
|
# for the fallback lookup this feeds.
|
|
172
|
+
#
|
|
173
|
+
# @param verb [Symbol] the word called, such as `:persisted_by`
|
|
174
|
+
# @param args [Array<Object>] positional arguments; the first names the adapter
|
|
175
|
+
# @param kwargs [Hash{Symbol => Object}] keyword arguments; `:role` is kept on the bind
|
|
176
|
+
# @yield an optional block, called once after the bind is recorded
|
|
177
|
+
# @return [Object] this builder after recording a bind, or the grammar word's own result
|
|
178
|
+
# when `verb` is one the `Hecksagon` grammar admits
|
|
179
|
+
# @raise [NoMethodError] if `verb` is no grammar word and names no adapter
|
|
180
|
+
# @raise [Bluebook::DSL::Malformed] if the grammar admits `verb` only in another context,
|
|
181
|
+
# or admits it here and its implementation refuses the declaration
|
|
133
182
|
def method_missing(verb, *args, **kwargs, &block)
|
|
134
183
|
# A closed-set grammar word (`port`, today) gets first refusal
|
|
135
184
|
# — item #13's full metaprogrammed dispatch (slice 5); see
|
|
136
185
|
# `WordGate#word_gate_dispatch`'s own header for why this class
|
|
137
186
|
# needs to call it explicitly rather than including it the
|
|
138
|
-
# ordinary way. Only once
|
|
187
|
+
# ordinary way. Only once that says "not admitted" does the
|
|
139
188
|
# genuinely open-ended `persisted_by "Heki"`-style bind
|
|
140
189
|
# vocabulary below get a turn.
|
|
141
190
|
result = word_gate_dispatch(verb, args, kwargs, block)
|
|
@@ -150,6 +199,16 @@ module Hecks
|
|
|
150
199
|
|
|
151
200
|
def respond_to_missing?(_name, _include_private = false) = true
|
|
152
201
|
|
|
202
|
+
# Evaluates a `Hecks.hecksagon` block with bare `Domain::Aggregate` constants resolving to
|
|
203
|
+
# bind-collecting proxies, and returns the wiring it declared.
|
|
204
|
+
#
|
|
205
|
+
# @param domain [String] name of the domain being wired
|
|
206
|
+
# @yield the hecksagon body, evaluated with the builder as `self`; may be omitted
|
|
207
|
+
# @return [Bluebook::Hecksagon] the declared wiring
|
|
208
|
+
# @raise [Bluebook::DSL::Malformed] if a `port` names no registered bluebook or aggregate,
|
|
209
|
+
# or declares a shape the port grammar refuses
|
|
210
|
+
# @raise [Runtime::WiringError] if `uses_framework` or `uses_embryonaut_bluebook` names
|
|
211
|
+
# something that cannot be found
|
|
153
212
|
def self.build(domain, &block)
|
|
154
213
|
builder = new(domain)
|
|
155
214
|
resolver = ->(name) { BindingProxy.namespace(name, builder.binds) }
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Bluebook
|
|
3
3
|
module DSL
|
|
4
|
-
# `identified_by` —
|
|
4
|
+
# `identified_by` — shared by AggregateBuilder and EntityBuilder
|
|
5
5
|
# only (S9, ADR 0025 — "EntityBuilder's duplicate identified_by and
|
|
6
6
|
# resolve_pending_identity! go"): a piece's own identity cannot
|
|
7
7
|
# spell differently from its aggregate's, and until this slice it
|
|
8
8
|
# was hand-duplicated onto both rather than shared.
|
|
9
9
|
#
|
|
10
|
-
# A
|
|
10
|
+
# A separate module from AttributeCollector on purpose — every
|
|
11
11
|
# `attribute()`-taking builder (Command, Query, PortOperation,
|
|
12
|
-
# ValueObject, ...) `include`s that one too, and none of
|
|
12
|
+
# ValueObject, ...) `include`s that one too, and none of them
|
|
13
13
|
# declares an identity of its own ; folding `identified_by` in
|
|
14
14
|
# there made it answer for six builders that never earned the
|
|
15
15
|
# word (`syntax_conformance_spec.rb` catches exactly this — a
|
|
16
16
|
# builder answering a method the grammar never grants it).
|
|
17
17
|
#
|
|
18
|
-
# Requires its includer to
|
|
18
|
+
# Requires its includer to also `include AttributeCollector`
|
|
19
19
|
# (for `attributes`/`resolve_identity_field!`/`resolve_identity_
|
|
20
|
-
# type!`, still declared there since every includer of
|
|
20
|
+
# type!`, still declared there since every includer of that
|
|
21
21
|
# module needs them) and to supply `identity_pool` (private) —
|
|
22
22
|
# the value-object list a bare field's own type resolves
|
|
23
23
|
# against: AggregateBuilder's own `@value_objects + closed_sets`,
|
|
24
|
-
# or EntityBuilder's
|
|
24
|
+
# or EntityBuilder's owner's, since a piece mints none of its own.
|
|
25
25
|
module IdentityDeclaration
|
|
26
26
|
# There are three live forms, deliberately distinguishable at the
|
|
27
27
|
# declaration site:
|
|
@@ -33,9 +33,9 @@ module Hecks
|
|
|
33
33
|
# One symbol is retired: it cannot say whether the author means a
|
|
34
34
|
# value concept or a field-shaped database key. Frozen source still
|
|
35
35
|
# reaches the old interpretation through `legacy_identified_by`.
|
|
36
|
-
#
|
|
36
|
+
# Renamed from `identified_by` — item #13's full metaprogrammed
|
|
37
37
|
# dispatch (slice 4c), same shared-mixin shape `attribute_impl`
|
|
38
|
-
# already proved in slice 3:
|
|
38
|
+
# already proved in slice 3: one renamed method, both Aggregate
|
|
39
39
|
# and Entity Keyword rows name it in `calls:`. Bootstrap-
|
|
40
40
|
# reachable (every self-hosted aggregate/entity declares an
|
|
41
41
|
# identity), so in BOOTSTRAP_CALLS_FALLBACK for both contexts.
|
|
@@ -67,15 +67,15 @@ module Hecks
|
|
|
67
67
|
&definition
|
|
68
68
|
)
|
|
69
69
|
rescue NameError => e
|
|
70
|
-
# A
|
|
70
|
+
# A removed spelling must refuse loudly, not degrade into a raw
|
|
71
71
|
# Ruby error — the one contract `EraGuard.shadow_parse` leans
|
|
72
72
|
# on to know a normal parse genuinely could not read this text
|
|
73
73
|
# (only `Malformed` triggers its shadow-mode retry, era_guard.rb's
|
|
74
|
-
# own comment). The
|
|
75
|
-
# whose text was
|
|
74
|
+
# own comment). The old `identified_by { name.value }` — a block
|
|
75
|
+
# whose text was never called, only extracted (legacy_
|
|
76
76
|
# identified_by, below) — is exactly this shape: read under the
|
|
77
|
-
#
|
|
78
|
-
#
|
|
77
|
+
# current grammar it is instead instance_eval'd as a value-object
|
|
78
|
+
# definition, and a bare identifier like `name` inside it resolves
|
|
79
79
|
# to nothing WordGate#method_missing recognizes, so Ruby itself
|
|
80
80
|
# raises NameError. Left uncaught, that NameError skipped
|
|
81
81
|
# shadow_parse's rescue entirely and reached callers as a raw
|
|
@@ -124,9 +124,9 @@ module Hecks
|
|
|
124
124
|
|
|
125
125
|
private
|
|
126
126
|
|
|
127
|
-
#
|
|
127
|
+
# Resolves whichever of the three `identified_by` shapes is
|
|
128
128
|
# pending, against `identity_pool` — the includer's own private
|
|
129
|
-
# hook. Called at
|
|
129
|
+
# hook. Called at build time, not at `identified_by`'s own call
|
|
130
130
|
# time — see `AttributeCollector#resolve_identity_field!`'s own
|
|
131
131
|
# comment on why.
|
|
132
132
|
def resolve_pending_identity!
|
|
@@ -161,8 +161,8 @@ module Hecks
|
|
|
161
161
|
raise Malformed, "#{@name} declares identified_by more than once"
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
#
|
|
165
|
-
# multi-line block), kept alive
|
|
164
|
+
# Legacy — the two removed spellings (a value object + as:, and the
|
|
165
|
+
# multi-line block), kept alive only for `EraGuard.shadow_parse`
|
|
166
166
|
# (S0a, ADR 0025) to still make sense of frozen era text that used
|
|
167
167
|
# them; unreachable outside `MetaValidator.shadow_parsing?`.
|
|
168
168
|
def legacy_identified_by(*targets, as:, &path)
|
|
@@ -12,16 +12,26 @@ module Hecks
|
|
|
12
12
|
|
|
13
13
|
include WordGate
|
|
14
14
|
|
|
15
|
+
# @param field [Symbol, String] the attribute the state machine lives on, such as `:status`
|
|
16
|
+
# @param default [String, Symbol] the state a new record starts in
|
|
15
17
|
def initialize(field, default:)
|
|
16
18
|
@field = field
|
|
17
19
|
@default = default
|
|
18
20
|
@transitions = []
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
#
|
|
23
|
+
# Records one transition row per command in `mapping`, all sharing its `from:` guard.
|
|
24
|
+
#
|
|
25
|
+
# Answers the `transition` word, which the grammar table routes here
|
|
26
|
+
# through its `calls:` column — item #13's full metaprogrammed
|
|
22
27
|
# dispatch (slice 4c). Bootstrap-reachable (syntax.bluebook's
|
|
23
28
|
# own Keyword/Argument entities describe their `status`
|
|
24
|
-
# lifecycle with it), so in BOOTSTRAP_CALLS_FALLBACK
|
|
29
|
+
# lifecycle with it), so in `BOOTSTRAP_CALLS_FALLBACK`.
|
|
30
|
+
#
|
|
31
|
+
# @param mapping [Hash{String, Symbol => String, Symbol, Array<String>}] command name to
|
|
32
|
+
# target state, as in `"Purchase" => "sold"`; the optional `:from` key holds the
|
|
33
|
+
# state, or Array of states, the transition applies from, and nil or absent means any
|
|
34
|
+
# @return [Hash] the command-to-target pairs just recorded, `:from` removed
|
|
25
35
|
def transition_impl(mapping)
|
|
26
36
|
mapping = mapping.dup
|
|
27
37
|
from = mapping.delete(:from)
|
|
@@ -34,11 +44,24 @@ module Hecks
|
|
|
34
44
|
end
|
|
35
45
|
end
|
|
36
46
|
|
|
47
|
+
# Assembles the declared transitions into a `Lifecycle`, refusing an ambiguous table.
|
|
48
|
+
#
|
|
49
|
+
# @return [Bluebook::Lifecycle] the state machine: its field, default and transitions
|
|
50
|
+
# @raise [Bluebook::DSL::Malformed] if two transitions for one command could both apply
|
|
51
|
+
# from the same state (C5.3); skipped while shadow-parsing frozen era text
|
|
37
52
|
def build
|
|
38
53
|
refuse_ambiguity!
|
|
39
54
|
Lifecycle.new(field: @field, default: @default, transitions: @transitions)
|
|
40
55
|
end
|
|
41
56
|
|
|
57
|
+
# Evaluates a `lifecycle` block against a fresh builder and returns what it built.
|
|
58
|
+
#
|
|
59
|
+
# @param field [Symbol, String] the attribute the state machine lives on
|
|
60
|
+
# @param default [String, Symbol] the state a new record starts in
|
|
61
|
+
# @yield the lifecycle body of `transition` rows, evaluated with the builder as `self`
|
|
62
|
+
# @return [Bluebook::Lifecycle] the built state machine
|
|
63
|
+
# @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap, or the
|
|
64
|
+
# block uses a word the `Lifecycle` grammar does not admit
|
|
42
65
|
def self.build(field, default:, &block)
|
|
43
66
|
builder = new(field, default: default)
|
|
44
67
|
builder.instance_eval(&block) if block
|
|
@@ -51,9 +74,9 @@ module Hecks
|
|
|
51
74
|
# one command whose `from:` sets overlap (or where either has no
|
|
52
75
|
# `from:` at all) were silently first-wins; refused where the state
|
|
53
76
|
# machine can be read whole. Two transitions for one command from
|
|
54
|
-
#
|
|
77
|
+
# disjoint states are the legitimate shape (`match_transition`
|
|
55
78
|
# picks by the current state) and stay. A `from:` naming a state
|
|
56
|
-
# nothing declares is
|
|
79
|
+
# nothing declares is not refused here: it is a reachability
|
|
57
80
|
# finding `bin/model_check` already reports (unreachable state,
|
|
58
81
|
# dead transition), and a bluebook may declare it on purpose.
|
|
59
82
|
def refuse_ambiguity!
|
|
@@ -17,10 +17,10 @@ module Hecks
|
|
|
17
17
|
@name = name
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
# `on Account::AccountFrozen` —
|
|
20
|
+
# `on Account::AccountFrozen` — a bare constant accepted (ADR
|
|
21
21
|
# 0025, S6 — "events first-class"), resolved through `ConstShim`
|
|
22
22
|
# the same way `trigger`/`dispatch` already resolve a command
|
|
23
|
-
# reference (`Naming.event_ref`, that method's own header).
|
|
23
|
+
# reference (`Naming.event_ref`, that method's own header). Not
|
|
24
24
|
# a required spelling yet, unlike `trigger`'s own quoted-text
|
|
25
25
|
# refusal — see `policy.bluebook`'s own KeywordSeed comment for
|
|
26
26
|
# why: event names aren't 100% migrated across the live corpus
|
|
@@ -28,7 +28,7 @@ module Hecks
|
|
|
28
28
|
# AccountFrozen"` (quoted) and `on Account::AccountFrozen`
|
|
29
29
|
# (bare) stay admitted until a full migration lands.
|
|
30
30
|
#
|
|
31
|
-
#
|
|
31
|
+
# Renamed from the generic single-fill coercion — item #13's
|
|
32
32
|
# full metaprogrammed dispatch, slice 1 (whole-project
|
|
33
33
|
# table-unification survey), now overridden here the same way
|
|
34
34
|
# `trigger_impl` overrides its own generic default.
|
|
@@ -36,7 +36,7 @@ module Hecks
|
|
|
36
36
|
@on_event = Naming.event_ref(event_ref)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# `with:` —
|
|
39
|
+
# `with:` — what the trigger is given, when the event's own shape
|
|
40
40
|
# is not it. Omitted, the whole event payload forwards verbatim,
|
|
41
41
|
# which is what every policy did before this existed.
|
|
42
42
|
#
|
|
@@ -46,10 +46,10 @@ module Hecks
|
|
|
46
46
|
# supplies itself. The reason it exists is the reason a saga's
|
|
47
47
|
# does — a reaction crosses an aggregate boundary, and the event
|
|
48
48
|
# on one side is under no obligation to be shaped like the
|
|
49
|
-
# command on the other. Without it the target has to
|
|
49
|
+
# command on the other. Without it the target has to declare
|
|
50
50
|
# every field the event happens to carry, whether it reads them
|
|
51
51
|
# or not.
|
|
52
|
-
#
|
|
52
|
+
# The command itself, not its name (ADR 0025, "events and
|
|
53
53
|
# reactions" — command references become first-class): `trigger
|
|
54
54
|
# Account::Debit`, a bare constant `ConstShim` resolves the same
|
|
55
55
|
# way `reference_to Account` always has, not a quoted verb string.
|
|
@@ -61,10 +61,10 @@ module Hecks
|
|
|
61
61
|
# `Account::Debit` and `Banking::Account::Debit` mean the same
|
|
62
62
|
# thing here).
|
|
63
63
|
#
|
|
64
|
-
#
|
|
64
|
+
# Legacy under shadow-parsing (S0a's own bridge) — frozen era
|
|
65
65
|
# text still writes the quoted form.
|
|
66
66
|
#
|
|
67
|
-
#
|
|
67
|
+
# Renamed from `trigger` — item #13's full metaprogrammed
|
|
68
68
|
# dispatch (slice 4), same reasoning as has_many_impl above: not
|
|
69
69
|
# bootstrap-reachable, reached through calls: with no fallback
|
|
70
70
|
# needed.
|
|
@@ -80,11 +80,19 @@ module Hecks
|
|
|
80
80
|
@projection_declared = !with.nil?
|
|
81
81
|
end
|
|
82
82
|
|
|
83
|
-
# `across`
|
|
84
|
-
#
|
|
83
|
+
# `across "Notifications"` names the domain a trigger reaches into.
|
|
84
|
+
# `expect_undelivered: true` declares that this domain expects that
|
|
85
|
+
# target never to be reached (no such domain, on purpose), which
|
|
86
|
+
# `ModelCheck` holds it to in both directions. Reached through
|
|
87
|
+
# `calls:` since it gained the named flag — the generic single-fill
|
|
88
|
+
# coercion takes no keyword arguments.
|
|
89
|
+
def across_impl(domain, expect_undelivered: false)
|
|
90
|
+
@target_domain = domain.to_s
|
|
91
|
+
@expect_undelivered = expect_undelivered == true
|
|
92
|
+
end
|
|
85
93
|
|
|
86
|
-
#
|
|
87
|
-
# use (Ports::Extraction reads the block's
|
|
94
|
+
# **The guard** — same extraction CommandBuilder#given/#ensures already
|
|
95
|
+
# use (Ports::Extraction reads the block's source ; the block itself
|
|
88
96
|
# is never called, here or at runtime — Runtime::PolicyInterpreter
|
|
89
97
|
# evaluates the extracted text through the same
|
|
90
98
|
# Bluebook::Expression::Evaluator a command's own given/ensures run
|
|
@@ -94,7 +102,7 @@ module Hecks
|
|
|
94
102
|
# this policy does not apply to this event, exactly like an
|
|
95
103
|
# `event_qualifier` miss, which carries no message either.
|
|
96
104
|
#
|
|
97
|
-
# Evaluated against the triggering
|
|
105
|
+
# Evaluated against the triggering event's own payload, not a
|
|
98
106
|
# stored record — a policy reacts to what just happened, and has no
|
|
99
107
|
# aggregate instance of its own to read state from.
|
|
100
108
|
def where(&predicate)
|
|
@@ -112,7 +120,7 @@ module Hecks
|
|
|
112
120
|
@where = canonical
|
|
113
121
|
end
|
|
114
122
|
|
|
115
|
-
#
|
|
123
|
+
# **The fan-out source** — a query verb, "Aggregate.query_name" or
|
|
116
124
|
# "Domain::Aggregate.query_name", the same qualified-or-not shape a
|
|
117
125
|
# saga's own `dispatch` command name already takes
|
|
118
126
|
# (SagaInterpreter#qualified). Runtime::PolicyInterpreter runs the
|
|
@@ -123,13 +131,14 @@ module Hecks
|
|
|
123
131
|
|
|
124
132
|
def build
|
|
125
133
|
Policy.new(
|
|
126
|
-
name:
|
|
127
|
-
on_event:
|
|
128
|
-
trigger_command:
|
|
129
|
-
target_domain:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
134
|
+
name: @name,
|
|
135
|
+
on_event: @on_event,
|
|
136
|
+
trigger_command: @trigger_command,
|
|
137
|
+
target_domain: @target_domain,
|
|
138
|
+
expect_undelivered: @expect_undelivered || false,
|
|
139
|
+
where: @where,
|
|
140
|
+
for_each: @for_each,
|
|
141
|
+
with_spec: @with_spec || []
|
|
133
142
|
).tap { |policy| policy.instance_variable_set(:@projection_declared, !!@projection_declared) }
|
|
134
143
|
end
|
|
135
144
|
|
|
@@ -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
|