hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -3,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,74 @@ module Hecks
|
|
|
103
135
|
bluebook_ir.add_port(built)
|
|
104
136
|
end
|
|
105
137
|
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
138
|
+
# A TRANSLATION BOUNDARY, NOT A BUSINESS RULE — `translates "Name"
|
|
139
|
+
# do on Foreign::Domain::SomeEvent; trigger Local.Command; end`
|
|
140
|
+
# builds the EXACT SAME `Policy` a `policy` block inside this
|
|
141
|
+
# domain's own `.bluebook` would (same `on_impl`/`trigger_impl`,
|
|
142
|
+
# same `PolicyInterpreter` runtime — reusing `PolicyBuilder`
|
|
143
|
+
# directly, zero new runtime semantics). What's new is only WHERE
|
|
144
|
+
# it can be written: a cross-domain reaction is a wiring/context-
|
|
145
|
+
# mapping decision (this chapter conforming to a foreign chapter's
|
|
146
|
+
# published event), the same kind of decision `port`/
|
|
147
|
+
# `uses_framework` already are — not a fact this domain's own
|
|
148
|
+
# model states about itself, which `policy` (inside the bluebook)
|
|
149
|
+
# remains the right word for.
|
|
150
|
+
#
|
|
151
|
+
# MUST BE A BLOCK, not `translates Event, into: Command` — a flat
|
|
152
|
+
# call's arguments are evaluated eagerly, under THIS builder's own
|
|
153
|
+
# `ConstShim` resolver (`BindingProxy.namespace`, set up by
|
|
154
|
+
# `self.build` below), which mints a `BindingProxy` INSTANCE per
|
|
155
|
+
# segment and cannot answer a further `::` (it is not a Module) —
|
|
156
|
+
# a multi-segment reference like `Deploy::Tenant::TenantProvisioned`
|
|
157
|
+
# would raise before this method ever ran. `port_impl`'s own
|
|
158
|
+
# `ConstShim.with(->(const) { const })` swap only works because
|
|
159
|
+
# its own callers never pass a multi-segment bare constant as an
|
|
160
|
+
# eagerly-evaluated argument — everything of that shape lives
|
|
161
|
+
# inside ITS block, evaluated later, under the swapped resolver.
|
|
162
|
+
# A block defers evaluation the same way; a flat call cannot.
|
|
163
|
+
def translates(name, &block)
|
|
164
|
+
bluebook_ir = Hecks.current_registry.bluebook(@domain) or
|
|
165
|
+
raise Malformed, "#{@domain} declares no such bluebook — translates needs one to attach its reaction to"
|
|
166
|
+
|
|
167
|
+
# SAME RESOLVER BluebookBuilder ITSELF uses for a `policy`
|
|
168
|
+
# block's own `on`/`trigger` (bluebook_builder.rb) — a
|
|
169
|
+
# `ScopedConstant`, not the bare passthrough `port_impl` swaps
|
|
170
|
+
# to, because `on`/`trigger` here take genuinely multi-segment
|
|
171
|
+
# references (`Deploy::Tenant::TenantProvisioned`), not a
|
|
172
|
+
# single-segment type name.
|
|
173
|
+
resolver = ->(const) { ConstShim::ScopedConstant.for(const) }
|
|
174
|
+
built = ConstShim.with(resolver) { PolicyBuilder.build(name, &block) }
|
|
175
|
+
|
|
176
|
+
bluebook_ir.add_policy(built)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Assembles the collected binds, subscriptions and attachments into a `Hecksagon`.
|
|
180
|
+
#
|
|
181
|
+
# No ungoverned-role check here — see
|
|
182
|
+
# Registry::Verification#refuse_ungoverned_roles!. It lives outside
|
|
183
|
+
# per-block `build`, alongside `environment:`
|
|
109
184
|
# (Runtime::Loader.boot's comment has the provenance): a domain
|
|
110
185
|
# split across multiple hecksagon blocks (base + an
|
|
111
186
|
# `environments/<name>.hecksagon` overlay) would have every
|
|
112
187
|
# block but the one declaring `uses_framework "Governance"`
|
|
113
|
-
# refused
|
|
188
|
+
# refused here, even though `Registry#add_hecksagon` merges them
|
|
114
189
|
# into one Hecksagon before anything ever dispatches against it.
|
|
115
|
-
# Checking the
|
|
190
|
+
# Checking the merged result once, at verify! time — after every
|
|
116
191
|
# file for this domain has loaded — is both more permissive (no
|
|
117
192
|
# need to repeat `uses_framework` in every file) and strictly
|
|
118
193
|
# more correct (a check against an incomplete, not-yet-merged
|
|
119
194
|
# hecksagon can never see the real final shape).
|
|
195
|
+
#
|
|
196
|
+
# @return [Bluebook::Hecksagon] this block's wiring, which `Registry#add_hecksagon` merges
|
|
197
|
+
# with any other block declared for the same domain
|
|
120
198
|
def build
|
|
121
199
|
Hecksagon.new(domain: @domain, binds: @binds, subscriptions: @subscriptions,
|
|
122
200
|
framework_members: @framework_members, vendored_bluebooks: @vendored_bluebooks)
|
|
123
201
|
end
|
|
124
202
|
|
|
125
|
-
#
|
|
203
|
+
# Records any verb the grammar does not own as a domain-wide bind to the named adapter.
|
|
204
|
+
#
|
|
205
|
+
# **Domain-level default binds** — `persisted_by "Heki"` bare, at the top
|
|
126
206
|
# of a hecksagon block, applies to every aggregate in this domain
|
|
127
207
|
# that doesn't declare its own override. Mirrors `BindingProxy`'s own
|
|
128
208
|
# `method_missing` one level down (`aggregate:` filled in there,
|
|
@@ -130,12 +210,22 @@ module Hecks
|
|
|
130
210
|
# `persisted_by`/`projected_by` specifically, so any future verb
|
|
131
211
|
# gets a domain-level default for free too. See `Hecksagon#bind_for`
|
|
132
212
|
# for the fallback lookup this feeds.
|
|
213
|
+
#
|
|
214
|
+
# @param verb [Symbol] the word called, such as `:persisted_by`
|
|
215
|
+
# @param args [Array<Object>] positional arguments; the first names the adapter
|
|
216
|
+
# @param kwargs [Hash{Symbol => Object}] keyword arguments; `:role` is kept on the bind
|
|
217
|
+
# @yield an optional block, called once after the bind is recorded
|
|
218
|
+
# @return [Object] this builder after recording a bind, or the grammar word's own result
|
|
219
|
+
# when `verb` is one the `Hecksagon` grammar admits
|
|
220
|
+
# @raise [NoMethodError] if `verb` is no grammar word and names no adapter
|
|
221
|
+
# @raise [Bluebook::DSL::Malformed] if the grammar admits `verb` only in another context,
|
|
222
|
+
# or admits it here and its implementation refuses the declaration
|
|
133
223
|
def method_missing(verb, *args, **kwargs, &block)
|
|
134
224
|
# A closed-set grammar word (`port`, today) gets first refusal
|
|
135
225
|
# — item #13's full metaprogrammed dispatch (slice 5); see
|
|
136
226
|
# `WordGate#word_gate_dispatch`'s own header for why this class
|
|
137
227
|
# needs to call it explicitly rather than including it the
|
|
138
|
-
# ordinary way. Only once
|
|
228
|
+
# ordinary way. Only once that says "not admitted" does the
|
|
139
229
|
# genuinely open-ended `persisted_by "Heki"`-style bind
|
|
140
230
|
# vocabulary below get a turn.
|
|
141
231
|
result = word_gate_dispatch(verb, args, kwargs, block)
|
|
@@ -150,6 +240,16 @@ module Hecks
|
|
|
150
240
|
|
|
151
241
|
def respond_to_missing?(_name, _include_private = false) = true
|
|
152
242
|
|
|
243
|
+
# Evaluates a `Hecks.hecksagon` block with bare `Domain::Aggregate` constants resolving to
|
|
244
|
+
# bind-collecting proxies, and returns the wiring it declared.
|
|
245
|
+
#
|
|
246
|
+
# @param domain [String] name of the domain being wired
|
|
247
|
+
# @yield the hecksagon body, evaluated with the builder as `self`; may be omitted
|
|
248
|
+
# @return [Bluebook::Hecksagon] the declared wiring
|
|
249
|
+
# @raise [Bluebook::DSL::Malformed] if a `port` names no registered bluebook or aggregate,
|
|
250
|
+
# or declares a shape the port grammar refuses
|
|
251
|
+
# @raise [Runtime::WiringError] if `uses_framework` or `uses_embryonaut_bluebook` names
|
|
252
|
+
# something that cannot be found
|
|
153
253
|
def self.build(domain, &block)
|
|
154
254
|
builder = new(domain)
|
|
155
255
|
resolver = ->(name) { BindingProxy.namespace(name, builder.binds) }
|
|
@@ -1,28 +1,31 @@
|
|
|
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
|
+
# Records which of the three live `identified_by` forms a builder declared, deferring
|
|
27
|
+
# resolution until build time.
|
|
28
|
+
#
|
|
26
29
|
# There are three live forms, deliberately distinguishable at the
|
|
27
30
|
# declaration site:
|
|
28
31
|
#
|
|
@@ -33,12 +36,12 @@ module Hecks
|
|
|
33
36
|
# One symbol is retired: it cannot say whether the author means a
|
|
34
37
|
# value concept or a field-shaped database key. Frozen source still
|
|
35
38
|
# reaches the old interpretation through `legacy_identified_by`.
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
39
|
+
# Both `AggregateBuilder` and `EntityBuilder` name this method in their Keyword rows'
|
|
40
|
+
# `calls:` column, so `GenericDispatch` forwards `identified_by` here unchanged, both
|
|
41
|
+
# after boot (reading the live grammar table) and during it
|
|
42
|
+
# (`GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`, which carries every `calls:`-routed row
|
|
43
|
+
# unconditionally, not a hand-picked bootstrap-reachable subset — though this one is
|
|
44
|
+
# exercised during boot too, since every self-hosted aggregate/entity declares an identity).
|
|
42
45
|
# Dispatches across the three live forms documented above (value-
|
|
43
46
|
# object + block, single type target, single/compound field
|
|
44
47
|
# target), each an early return that sets exactly one pending
|
|
@@ -46,6 +49,20 @@ module Hecks
|
|
|
46
49
|
# with-nil semantics and the shared `@name`/`identity_pool`
|
|
47
50
|
# threaded back out as parameters, for no gain beyond what the
|
|
48
51
|
# three-forms comment above already documents.
|
|
52
|
+
#
|
|
53
|
+
# @param targets [Array<Symbol, Module>] zero or more identity targets: a value-object
|
|
54
|
+
# type (a bareword, resolved by `ConstShim` to a `Module`), one or more attribute-name
|
|
55
|
+
# symbols for a field or compound key, or empty when `definition` is given
|
|
56
|
+
# @param as [Symbol, nil] the field name to mint for a value-object-type target; not
|
|
57
|
+
# accepted with a field or compound-key target
|
|
58
|
+
# @yield a bespoke value-object body, `instance_eval`'d by `ValueObjectBuilder.build` to
|
|
59
|
+
# mint the identity's own anonymous value object
|
|
60
|
+
# @return [Array<Symbol>, Array<String>, nil] `targets` when a compound key is declared,
|
|
61
|
+
# the resolved identity paths from `legacy_identified_by` in shadow-parsing mode, or
|
|
62
|
+
# `nil` otherwise; callers reach this through `GenericDispatch`, which discards it
|
|
63
|
+
# @raise [Bluebook::DSL::Malformed] if `identified_by` was already called, combines a
|
|
64
|
+
# block with targets, names no identity, mixes a value-object type with field names,
|
|
65
|
+
# passes `as:` with a field or compound key, or the block declares no attributes
|
|
49
66
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
|
50
67
|
def identified_by_impl(*targets, as: nil, &definition)
|
|
51
68
|
return legacy_identified_by(*targets, as: as, &definition) if MetaValidator.shadow_parsing?
|
|
@@ -67,15 +84,15 @@ module Hecks
|
|
|
67
84
|
&definition
|
|
68
85
|
)
|
|
69
86
|
rescue NameError => e
|
|
70
|
-
# A
|
|
87
|
+
# A removed spelling must refuse loudly, not degrade into a raw
|
|
71
88
|
# Ruby error — the one contract `EraGuard.shadow_parse` leans
|
|
72
89
|
# on to know a normal parse genuinely could not read this text
|
|
73
90
|
# (only `Malformed` triggers its shadow-mode retry, era_guard.rb's
|
|
74
|
-
# own comment). The
|
|
75
|
-
# whose text was
|
|
91
|
+
# own comment). The old `identified_by { name.value }` — a block
|
|
92
|
+
# whose text was never called, only extracted (legacy_
|
|
76
93
|
# identified_by, below) — is exactly this shape: read under the
|
|
77
|
-
#
|
|
78
|
-
#
|
|
94
|
+
# current grammar it is instead instance_eval'd as a value-object
|
|
95
|
+
# definition, and a bare identifier like `name` inside it resolves
|
|
79
96
|
# to nothing WordGate#method_missing recognizes, so Ruby itself
|
|
80
97
|
# raises NameError. Left uncaught, that NameError skipped
|
|
81
98
|
# shadow_parse's rescue entirely and reached callers as a raw
|
|
@@ -124,9 +141,9 @@ module Hecks
|
|
|
124
141
|
|
|
125
142
|
private
|
|
126
143
|
|
|
127
|
-
#
|
|
144
|
+
# Resolves whichever of the three `identified_by` shapes is
|
|
128
145
|
# pending, against `identity_pool` — the includer's own private
|
|
129
|
-
# hook. Called at
|
|
146
|
+
# hook. Called at build time, not at `identified_by`'s own call
|
|
130
147
|
# time — see `AttributeCollector#resolve_identity_field!`'s own
|
|
131
148
|
# comment on why.
|
|
132
149
|
def resolve_pending_identity!
|
|
@@ -161,8 +178,8 @@ module Hecks
|
|
|
161
178
|
raise Malformed, "#{@name} declares identified_by more than once"
|
|
162
179
|
end
|
|
163
180
|
|
|
164
|
-
#
|
|
165
|
-
# multi-line block), kept alive
|
|
181
|
+
# Legacy — the two removed spellings (a value object + as:, and the
|
|
182
|
+
# multi-line block), kept alive only for `EraGuard.shadow_parse`
|
|
166
183
|
# (S0a, ADR 0025) to still make sense of frozen era text that used
|
|
167
184
|
# them; unreachable outside `MetaValidator.shadow_parsing?`.
|
|
168
185
|
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!
|
|
@@ -13,14 +13,17 @@ module Hecks
|
|
|
13
13
|
|
|
14
14
|
include WordGate
|
|
15
15
|
|
|
16
|
+
# @param name [String] the policy's own name, as given to `policy "Name" do ... end`
|
|
16
17
|
def initialize(name)
|
|
17
18
|
@name = name
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
#
|
|
21
|
+
# Records the event this policy reacts to.
|
|
22
|
+
#
|
|
23
|
+
# `on Account::AccountFrozen` — a bare constant accepted (ADR
|
|
21
24
|
# 0025, S6 — "events first-class"), resolved through `ConstShim`
|
|
22
25
|
# the same way `trigger`/`dispatch` already resolve a command
|
|
23
|
-
# reference (`Naming.event_ref`, that method's own header).
|
|
26
|
+
# reference (`Naming.event_ref`, that method's own header). Not
|
|
24
27
|
# a required spelling yet, unlike `trigger`'s own quoted-text
|
|
25
28
|
# refusal — see `policy.bluebook`'s own KeywordSeed comment for
|
|
26
29
|
# why: event names aren't 100% migrated across the live corpus
|
|
@@ -28,17 +31,22 @@ module Hecks
|
|
|
28
31
|
# AccountFrozen"` (quoted) and `on Account::AccountFrozen`
|
|
29
32
|
# (bare) stay admitted until a full migration lands.
|
|
30
33
|
#
|
|
31
|
-
#
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
#
|
|
34
|
+
# Reached through `calls: "on_impl"` rather than `GenericDispatch`'s generic single-fill
|
|
35
|
+
# coercion, the same way `trigger_impl` below overrides its own generic default — `on`
|
|
36
|
+
# admits both a bare constant and quoted text, not one single argument kind.
|
|
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]
|
|
35
41
|
def on_impl(event_ref)
|
|
36
42
|
@on_event = Naming.event_ref(event_ref)
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
45
|
+
# Records the command this policy dispatches and, optionally, how the event's payload
|
|
46
|
+
# projects onto it.
|
|
47
|
+
#
|
|
48
|
+
# `with:` — what the trigger is given, when the event's own shape
|
|
49
|
+
# is not it. Omitted, the whole event payload forwards verbatim.
|
|
42
50
|
#
|
|
43
51
|
# Same `key => value` shape a saga's own `dispatch ..., with:`
|
|
44
52
|
# takes, and read the same way at runtime: a Symbol names a field
|
|
@@ -46,28 +54,35 @@ module Hecks
|
|
|
46
54
|
# supplies itself. The reason it exists is the reason a saga's
|
|
47
55
|
# does — a reaction crosses an aggregate boundary, and the event
|
|
48
56
|
# on one side is under no obligation to be shaped like the
|
|
49
|
-
# command on the other. Without it the target has to
|
|
57
|
+
# command on the other. Without it the target has to declare
|
|
50
58
|
# every field the event happens to carry, whether it reads them
|
|
51
59
|
# or not.
|
|
52
|
-
#
|
|
60
|
+
# The command itself, not its name (ADR 0025, "events and
|
|
53
61
|
# reactions" — command references become first-class): `trigger
|
|
54
62
|
# Account::Debit`, a bare constant `ConstShim` resolves the same
|
|
55
63
|
# way `reference_to Account` always has, not a quoted verb string.
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
#
|
|
62
|
-
# thing here).
|
|
64
|
+
# Matches the qualified-or-not shape a saga's own `dispatch`
|
|
65
|
+
# command name takes — see `Naming.command_ref`'s own header for
|
|
66
|
+
# how the `::`/`.` rewrite works, and `SagaInterpreter#qualified`
|
|
67
|
+
# for why an unqualified form is always enough (same-domain is
|
|
68
|
+
# the fallback, so `Account::Debit` and `Banking::Account::Debit`
|
|
69
|
+
# mean the same thing here).
|
|
63
70
|
#
|
|
64
|
-
#
|
|
71
|
+
# Legacy under shadow-parsing (S0a's own bridge) — frozen era
|
|
65
72
|
# text still writes the quoted form.
|
|
66
73
|
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
74
|
+
# `["Policy", "trigger"] => :trigger_impl` is in
|
|
75
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`, which carries every `calls:`-routed row
|
|
76
|
+
# unconditionally — this one is also exercised during boot itself, since every
|
|
77
|
+
# self-hosted policy declares a trigger.
|
|
78
|
+
#
|
|
79
|
+
# @param command_ref [Symbol, String, Module] the command, as a bare constant (a
|
|
80
|
+
# `ScopedConstant` module `ConstShim` resolves) or, under shadow-parsing, quoted text
|
|
81
|
+
# @param with [Hash{Symbol => Object}, nil] a projection from the triggering event's payload
|
|
82
|
+
# onto the command's own arguments; a Symbol value names an event field, anything else is
|
|
83
|
+
# a literal; `nil` forwards the whole event payload verbatim
|
|
84
|
+
# @return [void]
|
|
85
|
+
# @raise [Bluebook::DSL::Malformed] if `command_ref` is quoted text outside shadow-parsing
|
|
71
86
|
def trigger_impl(command_ref, with: nil)
|
|
72
87
|
if command_ref.is_a?(::String) && !MetaValidator.shadow_parsing?
|
|
73
88
|
raise Malformed,
|
|
@@ -80,11 +95,30 @@ module Hecks
|
|
|
80
95
|
@projection_declared = !with.nil?
|
|
81
96
|
end
|
|
82
97
|
|
|
83
|
-
#
|
|
84
|
-
#
|
|
98
|
+
# Records the domain this policy's trigger reaches into.
|
|
99
|
+
#
|
|
100
|
+
# `across "Notifications"` names the domain a trigger reaches into.
|
|
101
|
+
# `expect_undelivered: true` declares that this domain expects that
|
|
102
|
+
# target never to be reached (no such domain, on purpose), which
|
|
103
|
+
# `ModelCheck` holds it to in both directions. Reached through
|
|
104
|
+
# `calls: "across_impl"` rather than the generic single-fill
|
|
105
|
+
# coercion, because `expect_undelivered:` is a keyword argument the
|
|
106
|
+
# generic coercion cannot take.
|
|
107
|
+
#
|
|
108
|
+
# @param domain [String, Symbol] the target domain's name
|
|
109
|
+
# @param expect_undelivered [Boolean] whether this domain expects `across`'s target to
|
|
110
|
+
# name no real domain; `ModelCheck` holds it to that in both directions
|
|
111
|
+
# @return [void]
|
|
112
|
+
def across_impl(domain, expect_undelivered: false)
|
|
113
|
+
@target_domain = domain.to_s
|
|
114
|
+
@expect_undelivered = expect_undelivered == true
|
|
115
|
+
end
|
|
85
116
|
|
|
86
|
-
#
|
|
87
|
-
#
|
|
117
|
+
# Records the guard predicate that decides whether this policy applies to a triggering
|
|
118
|
+
# event.
|
|
119
|
+
#
|
|
120
|
+
# **The guard** — same extraction CommandBuilder#given/#ensures already
|
|
121
|
+
# use (Ports::Extraction reads the block's source ; the block itself
|
|
88
122
|
# is never called, here or at runtime — Runtime::PolicyInterpreter
|
|
89
123
|
# evaluates the extracted text through the same
|
|
90
124
|
# Bluebook::Expression::Evaluator a command's own given/ensures run
|
|
@@ -94,9 +128,15 @@ module Hecks
|
|
|
94
128
|
# this policy does not apply to this event, exactly like an
|
|
95
129
|
# `event_qualifier` miss, which carries no message either.
|
|
96
130
|
#
|
|
97
|
-
# Evaluated against the triggering
|
|
131
|
+
# Evaluated against the triggering event's own payload, not a
|
|
98
132
|
# stored record — a policy reacts to what just happened, and has no
|
|
99
133
|
# aggregate instance of its own to read state from.
|
|
134
|
+
#
|
|
135
|
+
# @yield the guard predicate, extracted as source text and evaluated later by
|
|
136
|
+
# `Runtime::PolicyInterpreter` against the triggering event's payload; never called here
|
|
137
|
+
# @return [void]
|
|
138
|
+
# @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted, or if it
|
|
139
|
+
# references a pattern `Expression::AstJson.refuse_unshared_patterns!` does not share
|
|
100
140
|
def where(&predicate)
|
|
101
141
|
canonical = Ports::Extraction.canonical(predicate)
|
|
102
142
|
|
|
@@ -112,7 +152,7 @@ module Hecks
|
|
|
112
152
|
@where = canonical
|
|
113
153
|
end
|
|
114
154
|
|
|
115
|
-
#
|
|
155
|
+
# **The fan-out source** — a query verb, "Aggregate.query_name" or
|
|
116
156
|
# "Domain::Aggregate.query_name", the same qualified-or-not shape a
|
|
117
157
|
# saga's own `dispatch` command name already takes
|
|
118
158
|
# (SagaInterpreter#qualified). Runtime::PolicyInterpreter runs the
|
|
@@ -121,18 +161,28 @@ module Hecks
|
|
|
121
161
|
# `for_each` — item #13's full metaprogrammed dispatch, slice 1:
|
|
122
162
|
# same shape as `on`, above.
|
|
123
163
|
|
|
164
|
+
# Builds the `Policy` this builder has accumulated.
|
|
165
|
+
#
|
|
166
|
+
# @return [Bluebook::Policy] the built policy, carrying every field set by `on`,
|
|
167
|
+
# `trigger`, `across`, `where`, `for_each`, and `with:`
|
|
124
168
|
def build
|
|
125
169
|
Policy.new(
|
|
126
|
-
name:
|
|
127
|
-
on_event:
|
|
128
|
-
trigger_command:
|
|
129
|
-
target_domain:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
170
|
+
name: @name,
|
|
171
|
+
on_event: @on_event,
|
|
172
|
+
trigger_command: @trigger_command,
|
|
173
|
+
target_domain: @target_domain,
|
|
174
|
+
expect_undelivered: @expect_undelivered || false,
|
|
175
|
+
where: @where,
|
|
176
|
+
for_each: @for_each,
|
|
177
|
+
with_spec: @with_spec || []
|
|
133
178
|
).tap { |policy| policy.instance_variable_set(:@projection_declared, !!@projection_declared) }
|
|
134
179
|
end
|
|
135
180
|
|
|
181
|
+
# Builds a `Policy` from a `policy "Name" do ... end` block.
|
|
182
|
+
#
|
|
183
|
+
# @param name [String] the policy's own name
|
|
184
|
+
# @yield the policy's body, `instance_eval`'d against a new builder
|
|
185
|
+
# @return [Bluebook::Policy] the built policy
|
|
136
186
|
def self.build(name, &block)
|
|
137
187
|
builder = new(name)
|
|
138
188
|
builder.instance_eval(&block) if block
|