hecks 1.3.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 +5 -4
- 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/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 +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -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 +7 -7
- 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 +101 -87
- 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 +170 -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 +180 -19
- 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 +140 -45
- 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 +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- 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 +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- 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 +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -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 +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- 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 +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 +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 +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 +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 +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 +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 +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 +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 +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- 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 +11 -11
- 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 +109 -27
- 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 +42 -63
- 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 +35 -35
- 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 +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- 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 +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- 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 -115
- 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 +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- 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 +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- 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 +205 -4
- 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 +22 -2
|
@@ -10,103 +10,96 @@ module Hecks
|
|
|
10
10
|
# increment/decrement land on an Integer or a one-numeric-field value
|
|
11
11
|
# object.
|
|
12
12
|
module Arithmetic
|
|
13
|
-
# The ops Runtime::CommandInterpreter applies
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
13
|
+
# The ops Runtime::CommandInterpreter applies — Vocabulary::MutationOp
|
|
14
|
+
# (language/bluebook/vocabulary.bluebook), read off the generated
|
|
15
|
+
# table, so increment/decrement's sign cannot drift from what the
|
|
16
|
+
# language says each op means. Every other op (set, append, multiply,
|
|
17
|
+
# clamp, remove, delegate, corrects) declares an empty sign — it does
|
|
18
|
+
# no add-or-subtract arithmetic — which reads here as nil.
|
|
18
19
|
MutationOp = Struct.new(:name, :sign, keyword_init: true)
|
|
19
20
|
|
|
20
|
-
MUTATION_OPS =
|
|
21
|
-
MutationOp.new(name: "
|
|
22
|
-
|
|
23
|
-
MutationOp.new(name: "increment", sign: 1),
|
|
24
|
-
MutationOp.new(name: "decrement", sign: -1),
|
|
25
|
-
# Vendored addition, not (yet) upstream hecks (migration
|
|
26
|
-
# plan task 4, i106): multiply/clamp carry no sign -- like
|
|
27
|
-
# set/append, they do no add-or-subtract arithmetic (multiply
|
|
28
|
-
# scales, clamp bounds). See #multiply/#clamp below.
|
|
29
|
-
MutationOp.new(name: "multiply", sign: nil),
|
|
30
|
-
MutationOp.new(name: "clamp", sign: nil),
|
|
31
|
-
# Vendored addition, not (yet) upstream hecks (migration
|
|
32
|
-
# plan task 4): remove -- carries no sign, like set/append; it
|
|
33
|
-
# matches a list element by value rather than doing arithmetic.
|
|
34
|
-
# Declared here so this table stays exactly what
|
|
35
|
-
# Vocabulary::MutationOp declares (spec/vocabulary_conformance_spec
|
|
36
|
-
# holds the two equal) -- MutationApplier's own `when :remove`
|
|
37
|
-
# branch (mutation_applier.rb) never calls #sign_of, so this was
|
|
38
|
-
# a declared-vocabulary gap, not a behaviour gap.
|
|
39
|
-
MutationOp.new(name: "remove", sign: nil),
|
|
40
|
-
# Vendored addition, not (yet) upstream hecks —
|
|
41
|
-
# CommandBuilder#delegates_to's own comment gives the full
|
|
42
|
-
# reasoning; carries no sign, like set/append/remove — it does
|
|
43
|
-
# no arithmetic, only a synchronous handoff into one nested
|
|
44
|
-
# entity command. Declared here so this table stays exactly
|
|
45
|
-
# what Vocabulary::MutationOp declares — MutationApplier's own
|
|
46
|
-
# `when :delegate` branch (mutation_applier.rb) never calls
|
|
47
|
-
# #sign_of either, same as `remove`'s own note above.
|
|
48
|
-
MutationOp.new(name: "delegate", sign: nil),
|
|
49
|
-
# CommandBuilder#corrects_impl's own comment gives the full
|
|
50
|
-
# reasoning — a command amending a past event rather than
|
|
51
|
-
# acting fresh. Carries no sign, like delegate: it does no
|
|
52
|
-
# arithmetic of its own; the record's actual change, if any,
|
|
53
|
-
# is an ordinary `sets` declared alongside it.
|
|
54
|
-
MutationOp.new(name: "corrects", sign: nil)
|
|
55
|
-
].freeze
|
|
21
|
+
MUTATION_OPS = Hecks::Vocabulary.rows("MutationOp").map do |row|
|
|
22
|
+
MutationOp.new(name: row["name"], sign: row["sign"].empty? ? nil : Integer(row["sign"]))
|
|
23
|
+
end.freeze
|
|
56
24
|
|
|
57
|
-
#
|
|
25
|
+
# Reads what a mutation's source means: an argument's value for a Symbol, the literal
|
|
26
|
+
# itself for anything else.
|
|
27
|
+
#
|
|
28
|
+
# A mutation's source is either the name of an argument or a literal, and
|
|
58
29
|
# the two are told apart by type : a Symbol is always a name, a String or a
|
|
59
30
|
# number is always a value. Checked across all eight chapters — `to: :name`
|
|
60
31
|
# and `to: "sold"`, never a Symbol meant as a value.
|
|
61
32
|
#
|
|
62
|
-
# `&& args.key?(source)`
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
# the literal `:name`, coercion
|
|
66
|
-
# and the refusal read "name is a PersonName — pass its fields as an
|
|
33
|
+
# The lookup is unconditional, with no `&& args.key?(source)` guard: such a
|
|
34
|
+
# guard makes an absent argument fall through to `source` and return the symbol
|
|
35
|
+
# itself as the value. `Customer.Register` without its `name` would set name to
|
|
36
|
+
# the literal `:name`, coercion would meet a Symbol where a PersonName belongs,
|
|
37
|
+
# and the refusal would read "name is a PersonName — pass its fields as an
|
|
67
38
|
# object, not :name" — a message describing a mistake the caller had not
|
|
68
|
-
# made. The real mistake, an absent argument,
|
|
39
|
+
# made. The real mistake, an absent argument, would never be the one refused,
|
|
69
40
|
# which is what fuzz surfaced.
|
|
70
41
|
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
# `attribute_collector.rb`): `sets` already sources correctly from
|
|
42
|
+
# An absent argument resolving to nil is correct, not pending work. The
|
|
43
|
+
# language can say which arguments are optional (`attribute ...,
|
|
44
|
+
# optional: true` — `CommandBuilder#attribute_impl`,
|
|
45
|
+
# `attribute_collector.rb`): `sets` sources correctly from
|
|
76
46
|
# an optional attribute, resolving absent to nil exactly as this
|
|
77
|
-
# method does, and
|
|
47
|
+
# method does, and refusing it here would be wrong, not merely
|
|
78
48
|
# undone work — `TillRoom::Till.TakeIn`'s own `note` (spec/
|
|
79
49
|
# fixtures/till.bluebook) and Banking's `CardPayment.Authorize`'s
|
|
80
50
|
# `tags` (payment_cards.bluebook) are real, live commands whose
|
|
81
51
|
# `sets` mutation is deliberately sourced from an optional
|
|
82
52
|
# attribute the caller may omit — `spec/runtime/command_rules_spec
|
|
83
|
-
# .rb`'s own "says an absent
|
|
53
|
+
# .rb`'s own "says an absent optional argument is nil, not the
|
|
84
54
|
# name of the argument" pins exactly this as correct, not pending.
|
|
85
55
|
# The meta-domain's own self-hosted commands (Command.Declare's
|
|
86
56
|
# `role`/`goal`/`provenance`/`from`/`position`, and ~35 more sites
|
|
87
57
|
# across the language) all lean on the identical pattern — nil is
|
|
88
|
-
# the
|
|
58
|
+
# the right answer for a `sets` sourced from a declared-optional
|
|
89
59
|
# attribute the caller left out, every time.
|
|
90
60
|
#
|
|
91
|
-
# The one
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# time from a legitimate optional absence. Closed at BUILD time
|
|
61
|
+
# The one narrow gap that leaves — a `sets` source Symbol naming
|
|
62
|
+
# nothing the command declares at all (a typo, not an optional
|
|
63
|
+
# argument), which would resolve to nil here, indistinguishable
|
|
64
|
+
# from a legitimate optional absence — is closed at build time
|
|
96
65
|
# instead of here: `CommandBuilder#refuse_unknown_argument_sources!`
|
|
97
66
|
# refuses it the moment the `.bluebook` file loads, mirroring
|
|
98
67
|
# `AggregateBuilder#seal_query_argument`'s identical check for a
|
|
99
|
-
# query's own where-clause argument. This function stays
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
68
|
+
# query's own where-clause argument. This function stays a pure,
|
|
69
|
+
# unconditional lookup because by the time any mutation reaches
|
|
70
|
+
# it, the source has already been proven to name either a real,
|
|
71
|
+
# possibly-optional argument, or a StateRef/literal; there is
|
|
72
|
+
# nothing left here to refuse.
|
|
73
|
+
#
|
|
74
|
+
# @param source [Symbol, Object] a mutation's source: a Symbol names a command argument,
|
|
75
|
+
# anything else (String, Numeric, Array, Hash, `StateRef`) is returned as is
|
|
76
|
+
# @param args [Hash{Symbol => Object}] the normalized command arguments
|
|
77
|
+
# @return [Object, nil] the named argument's value, nil when the caller left that
|
|
78
|
+
# argument out; otherwise `source` itself
|
|
104
79
|
def resolve_source(source, args)
|
|
105
80
|
return args[source] if source.is_a?(Symbol)
|
|
106
81
|
|
|
107
82
|
source
|
|
108
83
|
end
|
|
109
84
|
|
|
85
|
+
# Adds or subtracts `amount` from an attribute's current value, on a bare number or on
|
|
86
|
+
# the one numeric field two value objects share.
|
|
87
|
+
#
|
|
88
|
+
# @param current [Numeric, Runtime::Value, nil] the attribute's pre-dispatch value; nil
|
|
89
|
+
# (never set) counts as 0
|
|
90
|
+
# @param amount [Numeric, Runtime::Value] how much to move by; a value object is combined
|
|
91
|
+
# field by field with a value-object `current`, and otherwise unwrapped to its single
|
|
92
|
+
# numeric field
|
|
93
|
+
# @param target [Symbol, String] name of the attribute, used only to word a refusal
|
|
94
|
+
# @param sign [Integer] 1 to increment, -1 to decrement, as `sign_of` answers
|
|
95
|
+
# @return [Numeric, Runtime::Value] the new value: a `Runtime::Value` when both sides
|
|
96
|
+
# are value objects, otherwise a bare number the caller re-wraps
|
|
97
|
+
# @raise [Runtime::TypeMismatch] if `amount` or `current` is not numeric, or two value
|
|
98
|
+
# objects do not share exactly one numeric field
|
|
99
|
+
# @raise [Runtime::InvariantViolation] if a value-object result breaks one of its own
|
|
100
|
+
# invariants
|
|
101
|
+
# @raise [Bluebook::Expression::EvaluationError] if the result does not fit a signed
|
|
102
|
+
# 64-bit Integer, or is a non-finite Float
|
|
110
103
|
def arithmetic(current, amount, target, sign)
|
|
111
104
|
op = sign.positive? ? "increment" : "decrement"
|
|
112
105
|
current ||= 0
|
|
@@ -117,7 +110,7 @@ module Hecks
|
|
|
117
110
|
# `amount` arrives VO-wrapped — a real command argument typed the
|
|
118
111
|
# same as the attribute, but with nothing to combine field-by-
|
|
119
112
|
# field against yet (that is what `arithmetic_value_object`,
|
|
120
|
-
# above, is for once
|
|
113
|
+
# above, is for once both sides carry real fields). Before this,
|
|
121
114
|
# falling straight to `unless amount.is_a?(Numeric)` below
|
|
122
115
|
# refused with "increment needs an Integer, got 500" — true of
|
|
123
116
|
# nothing: 500 is exactly the Integer it asked for, just still
|
|
@@ -135,12 +128,12 @@ module Hecks
|
|
|
135
128
|
# counters before this corpus existed. Integer stays the common
|
|
136
129
|
# case; Float is now accepted the same way.
|
|
137
130
|
unless amount.is_a?(Numeric)
|
|
138
|
-
raise TypeMismatch, RefusalWording.
|
|
139
|
-
|
|
131
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_amount",
|
|
132
|
+
op: op, target: target, offered: Rendering.describe(amount))
|
|
140
133
|
end
|
|
141
134
|
unless current.is_a?(Numeric)
|
|
142
|
-
raise TypeMismatch, RefusalWording.
|
|
143
|
-
|
|
135
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_current",
|
|
136
|
+
op: op, target: target, offered: Rendering.describe(current))
|
|
144
137
|
end
|
|
145
138
|
|
|
146
139
|
bounded(current + (sign * amount), op, current, sign * amount, sign.positive? ? "+" : "-")
|
|
@@ -148,11 +141,23 @@ module Hecks
|
|
|
148
141
|
|
|
149
142
|
# C3.3/C3.4 — an effect's arithmetic is held to the same value
|
|
150
143
|
# model an expression's is: Integer is signed 64-bit, Float is
|
|
151
|
-
# finite. A result outside that is an evaluation
|
|
144
|
+
# finite. A result outside that is an evaluation fault (never a
|
|
152
145
|
# refusal, C8.3), worded as the Rust kernel's own generated
|
|
153
146
|
# `checked_add`/`checked_sub`/`checked_mul` word it.
|
|
154
147
|
INT64_RANGE = (-(2**63))..((2**63) - 1)
|
|
155
148
|
|
|
149
|
+
# Passes an arithmetic result through only if it fits the value model: a signed 64-bit
|
|
150
|
+
# Integer or a finite Float.
|
|
151
|
+
#
|
|
152
|
+
# @param result [Numeric] the computed value to check
|
|
153
|
+
# @param oper [String] the op's name (`"increment"`, `"decrement"`, `"multiply"`),
|
|
154
|
+
# the first word of the fault message
|
|
155
|
+
# @param lhs [Numeric] the left operand, quoted in the fault message
|
|
156
|
+
# @param rhs [Numeric] the right operand; its absolute value is quoted
|
|
157
|
+
# @param symbol [String] the operator to print between the operands: `"+"`, `"-"`, `"*"`
|
|
158
|
+
# @return [Numeric] `result`, unchanged
|
|
159
|
+
# @raise [Bluebook::Expression::EvaluationError] if an Integer result is outside
|
|
160
|
+
# `INT64_RANGE`, or a Float result is NaN or infinite
|
|
156
161
|
def bounded(result, oper, lhs, rhs, symbol)
|
|
157
162
|
if result.is_a?(Integer)
|
|
158
163
|
return result if INT64_RANGE.cover?(result)
|
|
@@ -166,6 +171,22 @@ module Hecks
|
|
|
166
171
|
"#{oper} overflowed: #{lhs} #{symbol} #{rhs.abs} is not a finite number"
|
|
167
172
|
end
|
|
168
173
|
|
|
174
|
+
# Adds or subtracts on the one numeric field two value objects share, answering a new
|
|
175
|
+
# value object of `current`'s type.
|
|
176
|
+
#
|
|
177
|
+
# @param current [Runtime::Value] the attribute's pre-dispatch value
|
|
178
|
+
# @param amount [Runtime::Value] how much to move by, coerced to the attribute's type
|
|
179
|
+
# @param target [Symbol, String] name of the attribute, used only to word a refusal
|
|
180
|
+
# @param sign [Integer] 1 to add, -1 to subtract
|
|
181
|
+
# @param oper [String] `"increment"` or `"decrement"`, for refusal and fault wording
|
|
182
|
+
# @return [Runtime::Value] a copy of `current` with the shared field replaced, rebuilt
|
|
183
|
+
# and re-validated through `Value#with`
|
|
184
|
+
# @raise [Runtime::TypeMismatch] if the two do not share exactly one numeric field, or
|
|
185
|
+
# the rebuilt value object refuses the new field value
|
|
186
|
+
# @raise [Runtime::InvariantViolation] if the new field value breaks one of the value
|
|
187
|
+
# object's own invariants
|
|
188
|
+
# @raise [Bluebook::Expression::EvaluationError] if the result does not fit a signed
|
|
189
|
+
# 64-bit Integer, or is a non-finite Float
|
|
169
190
|
def arithmetic_value_object(current, amount, target, sign, oper)
|
|
170
191
|
current_fields = current.to_h
|
|
171
192
|
amount_fields = amount.to_h
|
|
@@ -180,7 +201,7 @@ module Hecks
|
|
|
180
201
|
end
|
|
181
202
|
unless shared_numeric.size == 1
|
|
182
203
|
raise TypeMismatch,
|
|
183
|
-
RefusalWording.
|
|
204
|
+
RefusalWording.render_site("TypeMismatch", "arithmetic_shared_field", op: oper, target: target)
|
|
184
205
|
end
|
|
185
206
|
|
|
186
207
|
field = shared_numeric.first
|
|
@@ -188,10 +209,12 @@ module Hecks
|
|
|
188
209
|
current[field], amount[field], sign.positive? ? "+" : "-"))
|
|
189
210
|
end
|
|
190
211
|
|
|
191
|
-
#
|
|
192
|
-
#
|
|
193
|
-
#
|
|
194
|
-
#
|
|
212
|
+
# Looks up whether a mutation op adds or subtracts, from the generated `MUTATION_OPS` table.
|
|
213
|
+
#
|
|
214
|
+
# Not a bare `.find(...)&.sign || -1` — that would silently answer
|
|
215
|
+
# decrement's sign for both an op this table has never heard of
|
|
216
|
+
# and a declared, real op that simply carries no sign at all
|
|
217
|
+
# (set/append/multiply/clamp/remove — see `MUTATION_OPS` above).
|
|
195
218
|
# Callers today only ever reach this for :increment/:decrement
|
|
196
219
|
# (both MutationApplier#apply and EntityInterpreter#
|
|
197
220
|
# apply_to_element gate every other op through their own `case`
|
|
@@ -199,11 +222,19 @@ module Hecks
|
|
|
199
222
|
# raise is not a real runtime path yet — it is the same
|
|
200
223
|
# backstop one level down, in case a future caller reaches
|
|
201
224
|
# #sign_of directly for an op that was never meant to have one.
|
|
225
|
+
#
|
|
226
|
+
# @param oper [Symbol, String] the mutation op's name, such as `:increment`
|
|
227
|
+
# @return [Integer] 1 for increment, -1 for decrement
|
|
228
|
+
# @raise [Runtime::WiringError] if the op is unknown, or is declared with no sign
|
|
229
|
+
# (set, append, multiply, clamp, remove, delegate, corrects)
|
|
202
230
|
def sign_of(oper)
|
|
203
231
|
MUTATION_OPS.find { |candidate| candidate.name == oper.to_s }&.sign ||
|
|
204
232
|
raise(WiringError, "no sign declared for mutation op #{oper.inspect} — add one before calling #sign_of")
|
|
205
233
|
end
|
|
206
234
|
|
|
235
|
+
# Scales an attribute's current value by `amount`, on a bare number or on the one numeric
|
|
236
|
+
# field two value objects share.
|
|
237
|
+
#
|
|
207
238
|
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
208
239
|
# task 4, i106): `current * amount` -- the scaling counterpart to
|
|
209
240
|
# increment/decrement's add/subtract. Same raw-vs-value-object
|
|
@@ -211,6 +242,21 @@ module Hecks
|
|
|
211
242
|
# rather than duplicated verb-for-verb (a `Proc` picks the actual
|
|
212
243
|
# arithmetic; everything else -- the Value unwrap/rewrap, the
|
|
213
244
|
# TypeMismatch refusals -- is identical to the additive pair).
|
|
245
|
+
#
|
|
246
|
+
# @param current [Numeric, Runtime::Value, nil] the attribute's pre-dispatch value; nil
|
|
247
|
+
# (never set) counts as 0
|
|
248
|
+
# @param amount [Numeric, Runtime::Value] the factor; a value object is combined field
|
|
249
|
+
# by field with a value-object `current`, and otherwise unwrapped to its single
|
|
250
|
+
# numeric field
|
|
251
|
+
# @param target [Symbol, String] name of the attribute, used only to word a refusal
|
|
252
|
+
# @return [Numeric, Runtime::Value] the product: a `Runtime::Value` when both sides are
|
|
253
|
+
# value objects, otherwise a bare number the caller re-wraps
|
|
254
|
+
# @raise [Runtime::TypeMismatch] if `amount` or `current` is not numeric, or two value
|
|
255
|
+
# objects do not share exactly one numeric field
|
|
256
|
+
# @raise [Runtime::InvariantViolation] if a value-object result breaks one of its own
|
|
257
|
+
# invariants
|
|
258
|
+
# @raise [Bluebook::Expression::EvaluationError] if the product does not fit a signed
|
|
259
|
+
# 64-bit Integer, or is a non-finite Float
|
|
214
260
|
def multiply(current, amount, target)
|
|
215
261
|
current ||= 0
|
|
216
262
|
|
|
@@ -225,16 +271,16 @@ module Hecks
|
|
|
225
271
|
amount = unwrap_single_numeric_field(amount) if amount.is_a?(Value)
|
|
226
272
|
|
|
227
273
|
unless amount.is_a?(Numeric) && current.is_a?(Numeric)
|
|
228
|
-
raise TypeMismatch, RefusalWording.
|
|
229
|
-
|
|
230
|
-
|
|
274
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_amount",
|
|
275
|
+
op: "multiply", target: target,
|
|
276
|
+
offered: Rendering.describe(current.is_a?(Numeric) ? amount : current))
|
|
231
277
|
end
|
|
232
278
|
|
|
233
279
|
bounded(current * amount, "multiply", current, amount, "*")
|
|
234
280
|
end
|
|
235
281
|
|
|
236
282
|
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
237
|
-
# task 4, i106): bound the
|
|
283
|
+
# task 4, i106): bound the current value into `[min, max]` -- no
|
|
238
284
|
# "amount" to combine, so it does not go through
|
|
239
285
|
# #arithmetic/#multiply's shared-numeric-field matching at all;
|
|
240
286
|
# it clamps whichever single numeric field the wrapping value
|
|
@@ -242,12 +288,12 @@ module Hecks
|
|
|
242
288
|
# one, per Part 3a's auto-synthesis).
|
|
243
289
|
def clamp(current, bounds, target)
|
|
244
290
|
min, max = bounds
|
|
245
|
-
#
|
|
246
|
-
#
|
|
291
|
+
# The same `current ||= 0` #arithmetic/#multiply both give a
|
|
292
|
+
# phantom (never-set) numeric field, one line up from each —
|
|
247
293
|
# this was the one arithmetic op that didn't, so a VO-typed
|
|
248
294
|
# attribute with no declared `default:` (genuinely absent,
|
|
249
295
|
# `Instance.defaults`/`#default_for`) hit TypeMismatch on the
|
|
250
|
-
#
|
|
296
|
+
# first clamp. (#arithmetic/#multiply's own absent-current gap
|
|
251
297
|
# was a real, separate bug this comment used to describe wrong —
|
|
252
298
|
# they did not "silently treat the same absent field as zero";
|
|
253
299
|
# they raised too, blaming a perfectly valid `amount` for not
|
|
@@ -257,14 +303,14 @@ module Hecks
|
|
|
257
303
|
if current.is_a?(Value)
|
|
258
304
|
fields = current.to_h
|
|
259
305
|
field = fields.keys.find { |f| fields[f].is_a?(Numeric) } or
|
|
260
|
-
raise TypeMismatch, RefusalWording.
|
|
261
|
-
|
|
306
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_current",
|
|
307
|
+
op: "clamp", target: target, offered: Rendering.describe(current))
|
|
262
308
|
return current.with(field, fields[field].clamp(min, max))
|
|
263
309
|
end
|
|
264
310
|
|
|
265
311
|
unless current.is_a?(Numeric)
|
|
266
|
-
raise TypeMismatch, RefusalWording.
|
|
267
|
-
|
|
312
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "arithmetic_current",
|
|
313
|
+
op: "clamp", target: target, offered: Rendering.describe(current))
|
|
268
314
|
end
|
|
269
315
|
|
|
270
316
|
current.clamp(min, max)
|
|
@@ -275,14 +321,14 @@ module Hecks
|
|
|
275
321
|
# `amount` arrives VO-wrapped whenever the command's own declared
|
|
276
322
|
# attribute type says so (a real `Money`, not a bare Integer) —
|
|
277
323
|
# true whether or not `current` has ever been set. Only meaningful
|
|
278
|
-
# to call once `current` is known
|
|
324
|
+
# to call once `current` is known not to be a Value itself (the
|
|
279
325
|
# `current.is_a?(Value) && amount.is_a?(Value)` branch, above in
|
|
280
326
|
# both callers, already owns the case where both sides carry real
|
|
281
327
|
# fields to combine). Refuses rather than guesses when more than
|
|
282
328
|
# one field is numeric — genuinely ambiguous which one an absent
|
|
283
329
|
# `current` should be treated as zero for, the same reasoning
|
|
284
330
|
# `combine_value_object`'s own `shared_numeric.size == 1` check
|
|
285
|
-
# already holds to when both sides
|
|
331
|
+
# already holds to when both sides are present.
|
|
286
332
|
def unwrap_single_numeric_field(value)
|
|
287
333
|
fields = value.to_h
|
|
288
334
|
numeric_fields = fields.keys.select { |field| fields[field].is_a?(Numeric) }
|
|
@@ -299,7 +345,7 @@ module Hecks
|
|
|
299
345
|
end
|
|
300
346
|
unless shared_numeric.size == 1
|
|
301
347
|
raise TypeMismatch,
|
|
302
|
-
RefusalWording.
|
|
348
|
+
RefusalWording.render_site("TypeMismatch", "arithmetic_shared_field", op: oper, target: target)
|
|
303
349
|
end
|
|
304
350
|
|
|
305
351
|
field = shared_numeric.first
|
|
@@ -10,26 +10,40 @@ module Hecks
|
|
|
10
10
|
# the one check that runs before any domain-state work, alongside the
|
|
11
11
|
# argument gate rather than after it.
|
|
12
12
|
#
|
|
13
|
-
#
|
|
13
|
+
# **Two checks, not a replacement** — ADR 0025 §9's own caution against
|
|
14
14
|
# "silently downgrading `role` to documentation" cuts both ways: a
|
|
15
|
-
# caller who never named
|
|
15
|
+
# caller who never named who they are (every caller before this) is
|
|
16
16
|
# checked exactly the way it always has been, string equality
|
|
17
|
-
# against the command's own `role`. Only a caller that
|
|
17
|
+
# against the command's own `role`. Only a caller that also binds an
|
|
18
18
|
# `actor_id` (`Hecks.as_caller(role:, actor_id:)`) reaches the
|
|
19
|
-
# real check — a live
|
|
20
|
-
#
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
19
|
+
# real check — a live lookup through `Ports::Authorization`, once the
|
|
20
|
+
# command's domain has an authorization provider: its own chapter,
|
|
21
|
+
# or a framework member it attaches, declaring `provides
|
|
22
|
+
# "authorization"` (Governance, via `uses_framework "Governance"`).
|
|
23
|
+
# Boot refuses a domain that declares a `role` and has no provider —
|
|
24
|
+
# `Registry::Verification#refuse_ungoverned_roles!`. An identified caller is never
|
|
24
25
|
# let back through the string fallback: a real identity that holds
|
|
25
26
|
# no matching grant is refused, not waved through because it also
|
|
26
27
|
# happens to type the right word.
|
|
27
28
|
module Authorization
|
|
28
|
-
#
|
|
29
|
+
# Refuses the command when the ambient caller (`Caller.current`) does not hold the role
|
|
30
|
+
# the command declares.
|
|
31
|
+
#
|
|
32
|
+
# Opt-in, on both sides. No caller bound: unchecked, exactly as
|
|
29
33
|
# today. No role declared: unchecked too — `role` is genuinely
|
|
30
34
|
# optional in this language (roughly a third of banking's own
|
|
31
35
|
# commands declare none), so a command that never named a role has
|
|
32
36
|
# nothing to check a caller against.
|
|
37
|
+
#
|
|
38
|
+
# @param command [Bluebook::Command] the command about to run; its `role` is the one
|
|
39
|
+
# required, and a nil or empty role means no check
|
|
40
|
+
# @param domain [String] name of the command's domain, whose authorization provider
|
|
41
|
+
# answers the check
|
|
42
|
+
# @return [nil] when no caller is bound, no role is declared, or the caller is authorized
|
|
43
|
+
# @raise [Runtime::Unauthorized] if an identified caller holds no live grant of the role
|
|
44
|
+
# where a provider is attached, or otherwise if the caller's role string differs
|
|
45
|
+
# @raise [Runtime::WiringError] if an identified caller is checked and zero or more than
|
|
46
|
+
# one adapter implements the authorization port
|
|
33
47
|
def refuse_role_mismatch(command, domain)
|
|
34
48
|
caller = Caller.current
|
|
35
49
|
return unless caller
|
|
@@ -45,19 +59,26 @@ module Hecks
|
|
|
45
59
|
|
|
46
60
|
return if authorized
|
|
47
61
|
|
|
48
|
-
raise Unauthorized, RefusalWording.
|
|
49
|
-
|
|
50
|
-
|
|
62
|
+
raise Unauthorized, RefusalWording.render_site("Unauthorized", "role_mismatch",
|
|
63
|
+
command: command.hecks_name, role: command.role,
|
|
64
|
+
caller_role: caller.role)
|
|
51
65
|
end
|
|
52
66
|
|
|
53
67
|
private
|
|
54
68
|
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
69
|
+
# **Declared, not named** — `Registry#authorization_provider_for`.
|
|
70
|
+
#
|
|
71
|
+
# The provider's own commands are looked up too, not waved through
|
|
72
|
+
# the string fallback: `Governance::RoleAssignment.Assign` declares
|
|
73
|
+
# `role "Governance administrator"`, and an identified caller
|
|
74
|
+
# dispatching it is checked against a live assignment of that role
|
|
75
|
+
# like any other gated command (ADR 0025 §9). The Rust kernel's
|
|
76
|
+
# `check_role` (`rust/src/kernel/repository.rs`) does the same. The
|
|
77
|
+
# consequence is deliberate: the first administrator grant has to
|
|
78
|
+
# come from a caller that binds no `actor_id` (the unchecked,
|
|
79
|
+
# string-compared path) — a bootstrap step, not a hole.
|
|
59
80
|
def governance_attached?(domain)
|
|
60
|
-
|
|
81
|
+
!registry.authorization_provider_for(domain).nil?
|
|
61
82
|
end
|
|
62
83
|
end
|
|
63
84
|
end
|
|
@@ -7,12 +7,29 @@ module Hecks
|
|
|
7
7
|
# and — where the store can hold one — a recorded event beside the
|
|
8
8
|
# data it describes.
|
|
9
9
|
module Emission
|
|
10
|
-
#
|
|
10
|
+
# Builds one frozen event per name the command `emits`, appends each to the registry's
|
|
11
|
+
# event log, and records it in the store when the repository keeps events.
|
|
12
|
+
#
|
|
13
|
+
# `correlation` arrives here rather than being merged onto the
|
|
11
14
|
# event afterwards. It is part of the transaction — known from
|
|
12
15
|
# `dispatch`'s own argument before anything is emitted — and an
|
|
13
16
|
# event that is still being written to is not yet a record of
|
|
14
17
|
# what happened. Setting it at construction is what lets the
|
|
15
18
|
# event be frozen the moment it exists.
|
|
19
|
+
#
|
|
20
|
+
# @param command [Bluebook::Command] the command whose `emits` names the events
|
|
21
|
+
# @param domain [String] name of the emitting domain, the prefix of each event's
|
|
22
|
+
# `aggregate` (`"Domain::Aggregate"`)
|
|
23
|
+
# @param aggregate [Bluebook::Aggregate] the aggregate the record belongs to
|
|
24
|
+
# @param instance [Runtime::Instance] the settled record; its `id` stamps every event
|
|
25
|
+
# @param args [Hash{Symbol => Object}] the normalized command arguments, used as each
|
|
26
|
+
# event's payload and deep-frozen by this call
|
|
27
|
+
# @param repository [Ports::Persistence::AppendOnly] the aggregate's repository; asked to
|
|
28
|
+
# `record_event` only if it responds to it
|
|
29
|
+
# @param correlation [Hash, nil] saga correlation head => value for a saga-caused
|
|
30
|
+
# dispatch; nil otherwise
|
|
31
|
+
# @return [Array<Runtime::Event>] the emitted events in `emits` order, each frozen;
|
|
32
|
+
# `[]` when the command emits nothing
|
|
16
33
|
def emit(command, domain, aggregate, instance, args, repository, correlation = nil)
|
|
17
34
|
command.emits.map do |event_name|
|
|
18
35
|
event = Event.new(
|