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
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
require_relative "../errors"
|
|
2
|
+
require_relative "../refusal_wording"
|
|
3
|
+
require_relative "../../rendering"
|
|
4
|
+
|
|
5
|
+
module Hecks
|
|
6
|
+
module Runtime
|
|
7
|
+
class Value
|
|
8
|
+
# How a `list_of` attribute's own elements GET hydrated — entity-typed
|
|
9
|
+
# and value-object-typed alike — split out of `Coercion` (this file's
|
|
10
|
+
# sibling, extended into `Value` alongside it exactly the way
|
|
11
|
+
# `Admission` already is) once `Coercion` itself grew past
|
|
12
|
+
# `Metrics/ModuleLength`'s budget from two, unrelated fixes landing in
|
|
13
|
+
# the same method the same week (BUG#32's own `hydrate_entity_identity`
|
|
14
|
+
# and BUG#33's own `check_entity_list_identities` — see each method's
|
|
15
|
+
# own header, and this repo's `.rubocop_todo.yml` for the merge that
|
|
16
|
+
# made the split necessary). Cross-calls into `Coercion`'s own
|
|
17
|
+
# `for_attribute`/`build`/`fields_for`/`value_object_for`/
|
|
18
|
+
# `trusting_stored_state?` work unqualified here exactly as they did
|
|
19
|
+
# before the split, because both modules land on the same `Value`
|
|
20
|
+
# singleton class once extended — `self` never has to know which file
|
|
21
|
+
# a sibling method actually lives in.
|
|
22
|
+
module EntityListCoercion
|
|
23
|
+
# S17, ADR 0026 — searches the whole entity tree, not only the
|
|
24
|
+
# root's own direct children. `aggregate` here is always the
|
|
25
|
+
# root aggregate — `for_attribute`'s own `aggregate` argument is
|
|
26
|
+
# never reassigned as hydration recurses into a nested element,
|
|
27
|
+
# because coercion has to resolve value objects, and only the
|
|
28
|
+
# root answers `.value_object` at all (Entity's own header
|
|
29
|
+
# comment: an entity must not answer to it, or `Value.
|
|
30
|
+
# for_attribute` could no longer tell a piece from a head). So
|
|
31
|
+
# a nested entity — Dispatch, inside Handler — is not a direct
|
|
32
|
+
# child of the root the way Handler itself is, and a plain
|
|
33
|
+
# `aggregate.entities.find` stops one level short of it.
|
|
34
|
+
def find_entity(construct, name)
|
|
35
|
+
construct.entities.each do |candidate|
|
|
36
|
+
return candidate if candidate.hecks_name == name
|
|
37
|
+
|
|
38
|
+
found = find_entity(candidate, name)
|
|
39
|
+
return found if found
|
|
40
|
+
end
|
|
41
|
+
nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Frozen through: a list read back out of the store is an answer,
|
|
45
|
+
# not a handle on what is stored.
|
|
46
|
+
#
|
|
47
|
+
# ADR 0047 — this used to bail (`return value unless entity`) the
|
|
48
|
+
# moment `attribute.type` named a value object rather than an
|
|
49
|
+
# entity, handing back the raw, un-hydrated argument untouched.
|
|
50
|
+
# A `sets :field` mutation sourced from a whole-array argument (as
|
|
51
|
+
# opposed to element-by-element `append:`) went straight through
|
|
52
|
+
# `for_attribute`'s `:list` branch, so `Banking::CardPayment.
|
|
53
|
+
# Authorize`'s own `sets :tags` (`list_of(Tag)`) stored plain
|
|
54
|
+
# Ruby Hashes as its `tags` elements forever — never a real
|
|
55
|
+
# `Value`, never through `Tag`'s own `pattern:`/`invariant`
|
|
56
|
+
# checks. `remove:`'s `==` comparison (a real `Value` against a
|
|
57
|
+
# raw `Hash`) then always failed, since `Hash#==` refuses anything
|
|
58
|
+
# that isn't itself a compatible Hash — the bug ADR 0047 traces in
|
|
59
|
+
# full. Delegating to `hydrate_value_object_list` below closes
|
|
60
|
+
# that gap the same way the entity branch already worked: build a
|
|
61
|
+
# real, validated `Value` per element, reusing `for_attribute`'s
|
|
62
|
+
# own composite-construction path rather than inventing a second
|
|
63
|
+
# one.
|
|
64
|
+
# BUG#32 (QualityControl ledger) — `remove:`'s own single-target
|
|
65
|
+
# value used to fall straight into `Array(value).map { ... }`
|
|
66
|
+
# below unguarded, unlike this method's own delegated
|
|
67
|
+
# `hydrate_value_object_list` sibling, whose `value.is_a?(Array)`
|
|
68
|
+
# check exists for exactly this shape (see that method's own
|
|
69
|
+
# comment). For an entity-typed list, `remove:`'s scalar target
|
|
70
|
+
# (`Ledger.Void`'s `sequence: EntrySequence`) is never a Hash, so
|
|
71
|
+
# `Array(2)` merely wrapped it as `[2]` rather than shredding it —
|
|
72
|
+
# but the wrapping itself was still wrong: `MutationApplier#
|
|
73
|
+
# removed`'s `element == value` then compared a stored `Entry`
|
|
74
|
+
# Hash against a one-element Array, which can never be `==` a
|
|
75
|
+
# Hash, so nothing was ever removed and nothing ever refused
|
|
76
|
+
# either. Routed to `hydrate_entity_identity` instead, below.
|
|
77
|
+
def hydrate_entity_list(aggregate, attribute, value)
|
|
78
|
+
entity = find_entity(aggregate, attribute.type.to_s)
|
|
79
|
+
return hydrate_value_object_list(aggregate, attribute, value) unless entity
|
|
80
|
+
|
|
81
|
+
return hydrate_entity_identity(aggregate, entity, value) unless value.is_a?(Array)
|
|
82
|
+
|
|
83
|
+
hydrated = value.map do |element|
|
|
84
|
+
next element unless element.is_a?(Hash)
|
|
85
|
+
|
|
86
|
+
element.each_with_object({}) do |(name, field_value), acc|
|
|
87
|
+
key = name.to_sym
|
|
88
|
+
field = entity.attribute(key)
|
|
89
|
+
acc[key] = field ? for_attribute(aggregate, field, field_value) : field_value
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
# BUG#33 — only a genuine whole-list offering (`value.is_a?(Array)`)
|
|
93
|
+
# is a caller naming every element's own identity at once; the
|
|
94
|
+
# single-target shape this same method also hydrates (a `remove:`
|
|
95
|
+
# target, wrapped one level up by `Array()`) never reaches this
|
|
96
|
+
# check, and never should — see `check_entity_list_identities`'s
|
|
97
|
+
# own comment.
|
|
98
|
+
check_entity_list_identities(aggregate, entity, hydrated) if value.is_a?(Array)
|
|
99
|
+
Freezer.deep(hydrated)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# `MutationApplier#check_entity_collision`'s own guard
|
|
103
|
+
# (mutation_applier.rb), extended from a single caller-supplied
|
|
104
|
+
# append (BUG#13) to a whole-list replace — `sets :entries` bare
|
|
105
|
+
# (`Ledger.ReplaceEntries`, the corpus's first `list_of(ENTITY)`
|
|
106
|
+
# command argument/mutation, qa/stress_domains/corrections). The
|
|
107
|
+
# array branch above rebuilds each offered element's own declared
|
|
108
|
+
# fields but never checked the offered list itself for either way
|
|
109
|
+
# it can misname its own entities:
|
|
110
|
+
#
|
|
111
|
+
# - two elements sharing one identity — the same silent-duplicate
|
|
112
|
+
# hazard BUG#13's own comment describes: `EntityInterpreter#
|
|
113
|
+
# element_of`'s own `find_index` always matches the first
|
|
114
|
+
# match, so the second becomes permanently unaddressable by any
|
|
115
|
+
# later command.
|
|
116
|
+
# - an element missing its identity altogether — there is no
|
|
117
|
+
# auto-mint for a whole-list replace the way `MutationApplier#
|
|
118
|
+
# entity_element` mints one for a single append (which element
|
|
119
|
+
# would it mint for? every element here is the caller's own
|
|
120
|
+
# whole state, offered at once), so an absent identity is
|
|
121
|
+
# refused rather than guessed.
|
|
122
|
+
#
|
|
123
|
+
# Non-composite identities only, exactly BUG#13's own scope
|
|
124
|
+
# (`entity.identified_by` answers a single Symbol only when
|
|
125
|
+
# `identity_heads.size == 1`, `Behaviour::Traits#derive_identity`) —
|
|
126
|
+
# a composite identity's own duplicate/missing question is the
|
|
127
|
+
# same pre-existing, narrower gap BUG#13 documented and left open,
|
|
128
|
+
# not widened here.
|
|
129
|
+
#
|
|
130
|
+
# Skipped under `trusting_stored_state?`, the same guard `validate!`
|
|
131
|
+
# already gives every value object (C6.3): a record already
|
|
132
|
+
# written is trusted as it was, so tightening this check can never
|
|
133
|
+
# make an old, already-persisted record unreadable.
|
|
134
|
+
def check_entity_list_identities(aggregate, entity, elements)
|
|
135
|
+
identity = entity.identified_by
|
|
136
|
+
return unless identity
|
|
137
|
+
return if trusting_stored_state?
|
|
138
|
+
|
|
139
|
+
field = entity.attribute(identity)
|
|
140
|
+
seen = []
|
|
141
|
+
elements.each do |fields|
|
|
142
|
+
next unless fields.is_a?(Hash)
|
|
143
|
+
|
|
144
|
+
offered = fields[identity]
|
|
145
|
+
if offered.nil?
|
|
146
|
+
raise TypeMismatch,
|
|
147
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
148
|
+
type: entity.hecks_name, field: identity,
|
|
149
|
+
expected: field&.type, offered: "nil")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if seen.include?(offered)
|
|
153
|
+
raise AlreadyExists,
|
|
154
|
+
RefusalWording.render_site("AlreadyExists", "entity_duplicate",
|
|
155
|
+
entity: entity.hecks_name, aggregate: aggregate.hecks_name,
|
|
156
|
+
identity: entity.identity_paths.join(", "),
|
|
157
|
+
offered: [Rendering.describe(offered)])
|
|
158
|
+
end
|
|
159
|
+
seen << offered
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# BUG#32 — `remove:`'s own single-target value against an
|
|
164
|
+
# entity-typed list. An entity is never offered to `remove:`
|
|
165
|
+
# whole the way a value object is (`hydrate_value_object_list`'s
|
|
166
|
+
# own `element == value` full-value-equality shape) — an entity
|
|
167
|
+
# must never answer `.value_object` at all (`Entity`'s own header
|
|
168
|
+
# comment), so there is no whole-value shape here to rebuild in
|
|
169
|
+
# the first place, only the one field a caller would otherwise
|
|
170
|
+
# have to name to address that element any other way
|
|
171
|
+
# (`EntityElement#element_of`'s own `wants`). So this coerces the
|
|
172
|
+
# offered scalar against the entity's own identity field's
|
|
173
|
+
# declared type — not the entity's full shape — and hands back a
|
|
174
|
+
# real, correctly-typed `Value` ready to compare against each
|
|
175
|
+
# stored element's own identity field
|
|
176
|
+
# (`EntityElement.list_element_match?`, the shared rule both
|
|
177
|
+
# `MutationApplier#removed` and `EntityElement#
|
|
178
|
+
# removed_from_element` match against).
|
|
179
|
+
#
|
|
180
|
+
# A composite identity (more than one head) has no single field a
|
|
181
|
+
# bare `remove:` target could mean, so this passes the value
|
|
182
|
+
# through uncoerced rather than guessing which head — the same
|
|
183
|
+
# "nothing in this corpus needs it yet" boundary
|
|
184
|
+
# `MutationApplier#check_entity_collision`'s own header already
|
|
185
|
+
# draws for entity identity elsewhere in this runtime.
|
|
186
|
+
# `list_element_match?` treats an uncoerced value the same way
|
|
187
|
+
# it always treated the pre-fix wrapped Array: never a match, a
|
|
188
|
+
# documented no-op rather than a crash.
|
|
189
|
+
def hydrate_entity_identity(aggregate, entity, value)
|
|
190
|
+
return value if value.is_a?(self)
|
|
191
|
+
|
|
192
|
+
heads = entity.identity_heads
|
|
193
|
+
return value unless heads.one?
|
|
194
|
+
|
|
195
|
+
field = entity.attribute(heads.first)
|
|
196
|
+
return value unless field
|
|
197
|
+
|
|
198
|
+
for_attribute(aggregate, field, value)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# The value-object sibling of the entity branch above: an element
|
|
202
|
+
# already shaped like the target `Value` (or a `Hash`/scalar that
|
|
203
|
+
# `fields_for` can still open) is rebuilt through the same `build`
|
|
204
|
+
# a scalar composite attribute already uses (`for_attribute`'s own
|
|
205
|
+
# `coerced = ... build(value_object, fields_for(...), aggregate)`
|
|
206
|
+
# line) — same defaults, same `pattern:`/`admits:`/invariant
|
|
207
|
+
# checks, same `trusting_stored_state?` bypass on a trusted load.
|
|
208
|
+
# `attribute.type` naming neither an entity nor a value object
|
|
209
|
+
# (a `list_of(String)`, say) has no shape to rebuild into, so the
|
|
210
|
+
# element passes through unchanged, exactly as the entity branch's
|
|
211
|
+
# own non-Hash elements do.
|
|
212
|
+
#
|
|
213
|
+
# Not `Array(value).map` (unlike the entity branch above) — `value`
|
|
214
|
+
# here is not always genuinely list-shaped. `MutationApplier#
|
|
215
|
+
# removed`'s own `Value.for_attribute(aggregate, attribute, value)`
|
|
216
|
+
# call (`attribute` = the list attribute, `mutation.target`; `value`
|
|
217
|
+
# = the single remove-target argument, already a real `Value` by
|
|
218
|
+
# the time it gets here) reuses this exact branch — for `remove:`,
|
|
219
|
+
# not for a whole-list `sets`. `Array(a_real_Value)` alone would be
|
|
220
|
+
# harmless (`Value` defines neither `to_a` nor `to_ary`, so Kernel
|
|
221
|
+
# wraps it `[value]`), but `Array(a_Hash)` is not harmless: Ruby's
|
|
222
|
+
# `Array()` opens a bare Hash into its own `[[k, v], ...]` pairs,
|
|
223
|
+
# not `[hash]` — silently shredding a single-element Hash-shaped
|
|
224
|
+
# target into garbage instead of hydrating it. Branching on
|
|
225
|
+
# `value.is_a?(Array)` up front (true only for a genuine whole-list
|
|
226
|
+
# `sets`/hydrate load) keeps the single-target shape a single
|
|
227
|
+
# target, hydrated the same way, never listified.
|
|
228
|
+
def hydrate_value_object_list(aggregate, attribute, value)
|
|
229
|
+
return value unless aggregate.respond_to?(:value_object)
|
|
230
|
+
|
|
231
|
+
value_object = value_object_for(aggregate, attribute.type)
|
|
232
|
+
return value unless value_object
|
|
233
|
+
|
|
234
|
+
return hydrate_value_object_element(aggregate, attribute, value_object, value) unless value.is_a?(Array)
|
|
235
|
+
|
|
236
|
+
hydrated = value.map { |element| hydrate_value_object_element(aggregate, attribute, value_object, element) }
|
|
237
|
+
Freezer.deep(hydrated)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def hydrate_value_object_element(aggregate, attribute, value_object, element)
|
|
241
|
+
return element if element.is_a?(self) && element.type_name == value_object.hecks_name
|
|
242
|
+
|
|
243
|
+
build(value_object, fields_for(value_object, attribute.name, element), aggregate)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
end
|
data/lib/hecks/runtime/value.rb
CHANGED
|
@@ -2,25 +2,30 @@ require "json"
|
|
|
2
2
|
require_relative "../rendering"
|
|
3
3
|
require_relative "value/invariant_violation"
|
|
4
4
|
require_relative "value/coercion"
|
|
5
|
+
require_relative "value/entity_list_coercion"
|
|
5
6
|
require_relative "value/admission"
|
|
6
7
|
|
|
7
8
|
module Hecks
|
|
8
9
|
module Runtime
|
|
9
10
|
# A typed value object in hand: frozen fields, read by name. How one is
|
|
10
|
-
#
|
|
11
|
+
# made — coerced from a raw argument, checked against its declared
|
|
11
12
|
# numeric types, patterns and closed sets — is the class-side engine in
|
|
12
|
-
# value/coercion.rb
|
|
13
|
-
#
|
|
13
|
+
# value/coercion.rb, value/entity_list_coercion.rb (a `list_of`
|
|
14
|
+
# attribute's own elements — split out once growing Coercion tripped
|
|
15
|
+
# Metrics/ModuleLength, see that file's own header), and
|
|
16
|
+
# value/admission.rb, extended here so the door stays where it always
|
|
17
|
+
# was: `Value.for`, `Value.build`.
|
|
14
18
|
class Value
|
|
15
19
|
extend Coercion
|
|
20
|
+
extend EntityListCoercion
|
|
16
21
|
extend Admission
|
|
17
22
|
|
|
18
23
|
attr_reader :value_object
|
|
19
24
|
|
|
20
|
-
#
|
|
25
|
+
# Frozen through, not just on top.
|
|
21
26
|
#
|
|
22
27
|
# `@fields.freeze` alone stops a key being added or removed and
|
|
23
|
-
# nothing else: the String, Array or Hash a field
|
|
28
|
+
# nothing else: the String, Array or Hash a field holds stays
|
|
24
29
|
# mutable, so `vo[:value] << "!"` edits a value object in place —
|
|
25
30
|
# demonstrated on a real dispatch before this was written, not
|
|
26
31
|
# supposed. Same shape as the three freezing bugs already fixed
|
|
@@ -28,7 +33,7 @@ module Hecks
|
|
|
28
33
|
# was frozen and the contents were not.
|
|
29
34
|
#
|
|
30
35
|
# A value object is the one thing in the domain that has no
|
|
31
|
-
# identity to change over — `with` already answers a
|
|
36
|
+
# identity to change over — `with` already answers a new one rather
|
|
32
37
|
# than mutating — so freezing it through is what it always claimed
|
|
33
38
|
# to be.
|
|
34
39
|
def initialize(value_object, fields)
|
|
@@ -63,13 +68,13 @@ module Hecks
|
|
|
63
68
|
# `materialize`, but a single-attribute value object (`sole_attribute`
|
|
64
69
|
# — [[feedback_name_the_scalar_field]]) recurses into its own bare
|
|
65
70
|
# field instead of building `{field: ...}` — a Board's own `label`
|
|
66
|
-
# unwraps to `"Kanban"`, not `{value: "Kanban"}`.
|
|
71
|
+
# unwraps to `"Kanban"`, not `{value: "Kanban"}`. Not a replacement
|
|
67
72
|
# for `materialize` itself: every existing report/query/command
|
|
68
73
|
# caller keeps the wrapped shape it already depends on (Banking's
|
|
69
74
|
# own `CustomerPortfolio` reads `payment[:amount][:cents]`, and
|
|
70
75
|
# changing that out from under it would be a real breaking change,
|
|
71
76
|
# not a bug fix). This is read_model_interpreter.rb's own opt-in,
|
|
72
|
-
# used
|
|
77
|
+
# used only for a `group_by`-declared head's own rows — grouping
|
|
73
78
|
# needs a real scalar to key by regardless, so a report already
|
|
74
79
|
# asking for that gets the unwrap for free.
|
|
75
80
|
def self.materialize_unwrapped(value)
|
|
@@ -78,12 +83,12 @@ module Hecks
|
|
|
78
83
|
sole = value.value_object.sole_attribute
|
|
79
84
|
return materialize_unwrapped(value[sole.name]) if sole
|
|
80
85
|
|
|
81
|
-
#
|
|
86
|
+
# Not `value.to_h` — `Value#to_h` materializes each field through
|
|
82
87
|
# plain `materialize`, so a VO nested inside a multi-attribute VO
|
|
83
88
|
# would already be a plain Hash by the time this method ever saw
|
|
84
89
|
# it, and never reach the `when self` branch above. Read each
|
|
85
90
|
# field straight off `value` instead, so recursion actually
|
|
86
|
-
# happens through
|
|
91
|
+
# happens through this method the whole way down.
|
|
87
92
|
value.value_object.attributes.to_h { |attr| [attr.name, materialize_unwrapped(value[attr.name])] }
|
|
88
93
|
when Array then value.map { |item| materialize_unwrapped(item) }
|
|
89
94
|
when Hash then value.transform_values { |item| materialize_unwrapped(item) }
|
|
@@ -91,17 +96,17 @@ module Hecks
|
|
|
91
96
|
end
|
|
92
97
|
end
|
|
93
98
|
|
|
94
|
-
#
|
|
99
|
+
# Reduces an append-only sub-log to its current state — the same
|
|
95
100
|
# "a later fact supersedes an earlier one" reduction this runtime
|
|
96
|
-
# already performs replaying an
|
|
101
|
+
# already performs replaying an aggregate's own command history
|
|
97
102
|
# into its current attributes, applied here to a single `list_of`
|
|
98
|
-
#
|
|
103
|
+
# field acting as its own miniature append-only log (a placement
|
|
99
104
|
# history, a tombstone-style soft-delete list, a versioned
|
|
100
105
|
# setting). `rows` is what a `list_of` attribute hands back — an
|
|
101
106
|
# Array of `Value`, in append order — and `key` names the field
|
|
102
107
|
# that identifies "the same logical thing" across entries.
|
|
103
108
|
#
|
|
104
|
-
#
|
|
109
|
+
# **Grouping only, never interpretation**. What counts as "removed,"
|
|
105
110
|
# how to order what survives — that meaning belongs to whichever
|
|
106
111
|
# domain declared the field, never here: a generic reduction that
|
|
107
112
|
# started guessing domain semantics would need to keep guessing
|
|
@@ -114,17 +119,17 @@ module Hecks
|
|
|
114
119
|
def method_missing(name, *args)
|
|
115
120
|
return @fields[name] if @fields.key?(name)
|
|
116
121
|
|
|
117
|
-
#
|
|
122
|
+
# The language rule, not a convenience: any value object with
|
|
118
123
|
# exactly one declared attribute answers `.value`, whatever that
|
|
119
124
|
# attribute is actually named — a single-attribute value object
|
|
120
|
-
# is a
|
|
125
|
+
# is a name for a scalar, not a genuine group
|
|
121
126
|
# ([[feedback_name_the_scalar_field]], `Behaviour::ValueObject#
|
|
122
127
|
# sole_attribute`), so `money.value` reads `Money`'s own `amount`
|
|
123
128
|
# exactly as `label.value` reads a shorthand-declared `value`.
|
|
124
|
-
#
|
|
125
|
-
# literally named `value` is already answered there (and
|
|
129
|
+
# After the real-field lookup above, on purpose: a field
|
|
130
|
+
# literally named `value` is already answered there (and is the
|
|
126
131
|
# sole attribute whenever the count is one), so this branch only
|
|
127
|
-
# ever aliases, never shadows. A
|
|
132
|
+
# ever aliases, never shadows. A multi-attribute value object
|
|
128
133
|
# keeps its NoMethodError — `sole_attribute` answers nil for it,
|
|
129
134
|
# and falling through to `super` is exactly the refusal it
|
|
130
135
|
# always gave: with two or more fields there is no single value
|
|
@@ -146,15 +151,15 @@ module Hecks
|
|
|
146
151
|
|
|
147
152
|
private
|
|
148
153
|
|
|
149
|
-
#
|
|
154
|
+
# **The `.value` alias for indexed access** — the same language rule
|
|
150
155
|
# `method_missing` above enforces for method reads, applied to
|
|
151
156
|
# `[]`/`key?`/`with`: `:value` names a single-attribute value
|
|
152
157
|
# object's sole field whatever that field is actually called. A
|
|
153
|
-
#
|
|
158
|
+
# real key always wins first (a field literally named `value` is
|
|
154
159
|
# its own answer, and is the sole attribute anyway whenever the
|
|
155
160
|
# count is one), so this only ever resolves a `:value` that would
|
|
156
|
-
# otherwise
|
|
157
|
-
# `with(:value, x)` in particular
|
|
161
|
+
# otherwise miss — it can never redirect a genuine field read.
|
|
162
|
+
# `with(:value, x)` in particular needs this: merging a literal
|
|
158
163
|
# `:value` key beside a sole field named `amount` would build a
|
|
159
164
|
# two-key hash for a one-field shape and be refused (or worse,
|
|
160
165
|
# stored) downstream — aliasing at the merge is what keeps the
|
data/lib/hecks/runtime.rb
CHANGED
|
@@ -6,24 +6,24 @@
|
|
|
6
6
|
# declaration is being collected into — sat on the top-level Hecks module
|
|
7
7
|
# beside the DSL words.
|
|
8
8
|
#
|
|
9
|
-
# What lives here is what
|
|
9
|
+
# What lives here is what runs a domain :
|
|
10
10
|
#
|
|
11
11
|
# Runtime.boot(path) load a directory and return its Dispatcher
|
|
12
12
|
# Runtime.with_registry(r) bind the ambient registry for the duration of a load
|
|
13
13
|
# Runtime.current_registry the registry a declaration is landing in, or nil
|
|
14
14
|
#
|
|
15
|
-
# What does
|
|
15
|
+
# What does not live here is what declares one — `Hecks.bluebook`,
|
|
16
16
|
# `.hecksagon`, `.port`, `.adapter`, `.world` are loading words and stay on the
|
|
17
17
|
# top-level module, which now reads as a facade over this.
|
|
18
18
|
#
|
|
19
19
|
# runtime = Hecks::Runtime.boot("examples/pizzas/bluebook")
|
|
20
20
|
# runtime.dispatch("Pizzas::Pizza.CreatePizza", name: "Margherita")
|
|
21
21
|
#
|
|
22
|
-
#
|
|
22
|
+
# Note, and it is the reason this file exists : `current_registry` is still
|
|
23
23
|
# process-global. Each boot builds a fresh Registry, and `Loader.bind_runtime`
|
|
24
|
-
# installs a fresh facade door whose modules close over
|
|
24
|
+
# installs a fresh facade door whose modules close over that boot's dispatcher
|
|
25
25
|
# — no class-level runtime binding remains, so two boots in one process no
|
|
26
|
-
# longer share dispatch state, only the top-level
|
|
26
|
+
# longer share dispatch state, only the top-level name (the last-bound door
|
|
27
27
|
# wins the constant, which is what per-boot install means). Owning the state
|
|
28
28
|
# here was the first move ; the dispatcher reachable per-runtime rather than
|
|
29
29
|
# through a constant was the second, and it is done — the door is the only
|
|
@@ -91,7 +91,7 @@ module Hecks
|
|
|
91
91
|
# the block — who a command's declared `role`, if any, is checked
|
|
92
92
|
# against.
|
|
93
93
|
#
|
|
94
|
-
# `as_of:` is
|
|
94
|
+
# `as_of:` is optional, same opt-in shape as `actor_id:` — a caller
|
|
95
95
|
# that wants a Governance `RoleAssignment`'s `starts_at` enforced
|
|
96
96
|
# passes `as_of: Ports::Clock.now(registry)` here, at the door,
|
|
97
97
|
# exactly where `cli_runner.rb` already merges `Clock.now` into a
|
|
@@ -99,7 +99,7 @@ module Hecks
|
|
|
99
99
|
# itself — see `Ports::Clock`'s own header for why — so an unbound
|
|
100
100
|
# `as_of` leaves `starts_at` unchecked, exactly as before.
|
|
101
101
|
#
|
|
102
|
-
# `scope:` is
|
|
102
|
+
# `scope:` is optional too — a caller that states which scope it is
|
|
103
103
|
# acting in gets that scope checked against the matching
|
|
104
104
|
# `RoleAssignment`'s own `scope`, not just its `role_name`. See
|
|
105
105
|
# `Runtime::Caller::Current`'s own header for why this lives here
|