hecks 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +72 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +83 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +33 -12
- data/lib/hecks/adapters/driven/governance_authorization.rb +87 -18
- data/lib/hecks/adapters/driven/heki/journal.rb +13 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +56 -10
- data/lib/hecks/adapters/driven/heki/snapshot.rb +1 -1
- data/lib/hecks/adapters/driven/heki.rb +106 -9
- data/lib/hecks/adapters/driven/identity_registry.rb +12 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +25 -3
- data/lib/hecks/adapters/driven/in_process_key_vault.adapter +3 -0
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +53 -0
- data/lib/hecks/adapters/driven/lambda/client.rb +67 -14
- data/lib/hecks/adapters/driven/lambda.rb +82 -35
- data/lib/hecks/adapters/driven/local_storage.rb +83 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +21 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +50 -11
- data/lib/hecks/adapters/driven/secure_random_identity.rb +3 -0
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven/system_clock.rb +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.adapter +3 -0
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +66 -0
- data/lib/hecks/adapters/driven.rb +6 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +31 -18
- data/lib/hecks/behaviors/dsl.rb +60 -2
- data/lib/hecks/behaviors/expectations.rb +190 -29
- data/lib/hecks/behaviors/ir.rb +12 -1
- data/lib/hecks/behaviors/rspec.rb +9 -1
- data/lib/hecks/behaviors/runner.rb +21 -2
- data/lib/hecks/behaviors.rb +9 -1
- data/lib/hecks/bluebook/aggregate.rb +43 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +17 -10
- data/lib/hecks/bluebook/assembly/build.rb +26 -5
- data/lib/hecks/bluebook/assembly/contract.rb +98 -23
- data/lib/hecks/bluebook/assembly/contracts.rb +59 -52
- data/lib/hecks/bluebook/assembly/marks.rb +159 -30
- data/lib/hecks/bluebook/assembly/specializer.rb +38 -21
- data/lib/hecks/bluebook/assembly.rb +32 -14
- data/lib/hecks/bluebook/attribute.rb +26 -12
- data/lib/hecks/bluebook/behaviour/aggregate.rb +43 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +18 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +76 -5
- data/lib/hecks/bluebook/behaviour/command.rb +55 -25
- data/lib/hecks/bluebook/behaviour/domain_port.rb +27 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +20 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +30 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +27 -6
- data/lib/hecks/bluebook/behaviour/policy.rb +42 -17
- data/lib/hecks/bluebook/behaviour/process_manager.rb +39 -8
- data/lib/hecks/bluebook/behaviour/query.rb +6 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +29 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +48 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +21 -9
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +51 -9
- data/lib/hecks/bluebook/command.rb +62 -17
- data/lib/hecks/bluebook/domain_port.rb +34 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +282 -123
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +112 -75
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +81 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +486 -117
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +179 -47
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +284 -122
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +191 -61
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +130 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +38 -21
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +86 -36
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +111 -47
- data/lib/hecks/bluebook/dsl/query_builder.rb +37 -8
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +127 -52
- data/lib/hecks/bluebook/dsl/rule_reference.rb +97 -43
- data/lib/hecks/bluebook/dsl/translation_builder.rb +150 -44
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +68 -20
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +40 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +128 -36
- data/lib/hecks/bluebook/expression/ast_reader.rb +32 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +55 -16
- data/lib/hecks/bluebook/expression/evaluator.rb +221 -43
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +54 -18
- data/lib/hecks/bluebook/expression/resolver.rb +369 -128
- data/lib/hecks/bluebook/hexagon.rb +35 -1
- data/lib/hecks/bluebook/lifecycle.rb +12 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +2 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +126 -108
- data/lib/hecks/bluebook/meta_validator/plan.rb +81 -46
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +3 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +200 -50
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +68 -41
- data/lib/hecks/bluebook/meta_validator/shapes.rb +166 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +286 -46
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +11 -10
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -5
- data/lib/hecks/bluebook/meta_validator.rb +235 -139
- data/lib/hecks/bluebook/model_check.rb +434 -104
- data/lib/hecks/bluebook/pattern_subset.rb +32 -10
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +27 -14
- data/lib/hecks/bluebook/project_discovery.rb +5 -0
- data/lib/hecks/bluebook/project_loader.rb +40 -0
- data/lib/hecks/bluebook/project_register.rb +50 -6
- data/lib/hecks/bluebook/query.rb +31 -4
- data/lib/hecks/bluebook/read_model.rb +35 -15
- data/lib/hecks/bluebook/reference.rb +26 -13
- data/lib/hecks/bluebook/smoke_test.rb +46 -23
- data/lib/hecks/bluebook/synthesizer.rb +46 -12
- data/lib/hecks/bluebook/translation.rb +34 -5
- data/lib/hecks/bluebook/value_object.rb +29 -11
- data/lib/hecks/bluebook.rb +5 -6
- data/lib/hecks/codemod.rb +138 -50
- data/lib/hecks/construct.rb +21 -7
- data/lib/hecks/corpus.rb +438 -0
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +19 -0
- data/lib/hecks/doc/reference.rb +200 -31
- data/lib/hecks/embryonaut_bluebook.rb +38 -15
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +155 -35
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +50 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +183 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/page.rb +14 -0
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +14 -2
- data/lib/hecks/forms/query_form_renderer.rb +65 -2
- data/lib/hecks/forms/record_renderer.rb +60 -2
- data/lib/hecks/forms/record_table.rb +28 -1
- data/lib/hecks/forms/reference_options.rb +24 -0
- data/lib/hecks/forms/value_object_shape.rb +13 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +59 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework/bluebook/privacy.bluebook +155 -0
- data/lib/hecks/framework/oidc.json +15 -0
- data/lib/hecks/framework.rb +79 -25
- data/lib/hecks/freezer.rb +27 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +241 -106
- data/lib/hecks/fuzzing/combination_miner.rb +178 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +241 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +161 -0
- data/lib/hecks/fuzzing/differential.rb +192 -0
- data/lib/hecks/fuzzing/domain_generator.rb +871 -0
- data/lib/hecks/fuzzing/era_boundary.rb +68 -19
- data/lib/hecks/fuzzing/form_census.rb +121 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +171 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +45 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +137 -80
- data/lib/hecks/fuzzing/nondeterministic.rb +79 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +111 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +34 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +300 -51
- data/lib/hecks/fuzzing/properties/guards.rb +86 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +80 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +40 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +70 -32
- data/lib/hecks/fuzzing/properties/querying.rb +82 -28
- data/lib/hecks/fuzzing/properties.rb +84 -51
- data/lib/hecks/fuzzing/qa_settings.rb +164 -0
- data/lib/hecks/fuzzing/replay.rb +241 -133
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -28
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +139 -0
- data/lib/hecks/fuzzing/self_consistency.rb +315 -137
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +46 -40
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +59 -32
- data/lib/hecks/fuzzing/sequence_generator.rb +158 -23
- data/lib/hecks/fuzzing/shrinker.rb +309 -0
- data/lib/hecks/fuzzing/structural_skips.rb +37 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +12 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +107 -29
- data/lib/hecks/fuzzing/value_generator.rb +110 -22
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +188 -12
- data/lib/hecks/grammar.rb +53 -7
- data/lib/hecks/ir.rb +51 -20
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +11 -0
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +41 -9
- data/lib/hecks/naming.rb +112 -31
- data/lib/hecks/ports/access_control.rb +53 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +50 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +23 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +17 -1
- data/lib/hecks/ports/key_vault.port +6 -0
- data/lib/hecks/ports/key_vault.rb +58 -0
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +34 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +95 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +48 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +58 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +96 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +15 -0
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +9 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +41 -8
- data/lib/hecks/ports/query/ordering.rb +21 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/ports.rb +1 -0
- data/lib/hecks/projections/bootstrap_table.rb +147 -0
- data/lib/hecks/projections/diagrams.rb +315 -79
- data/lib/hecks/projections/glossary/html.rb +91 -3
- data/lib/hecks/projections/glossary/markdown.rb +50 -3
- data/lib/hecks/projections/glossary/mermaid.rb +49 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +110 -5
- data/lib/hecks/projections/glossary.rb +173 -31
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +62 -17
- data/lib/hecks/projections/model.rb +75 -21
- data/lib/hecks/projections/oidc.rb +45 -8
- data/lib/hecks/projections/parser_table.rb +53 -12
- data/lib/hecks/projections/reference.rb +15 -3
- data/lib/hecks/projections/rust_vocabulary.rb +646 -0
- data/lib/hecks/projections/shape.rb +10 -2
- data/lib/hecks/projections/statements.rb +69 -22
- data/lib/hecks/projections/vocabulary.rb +26 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +245 -37
- data/lib/hecks/projector/docs_projector.rb +154 -28
- data/lib/hecks/projector/exporter.rb +104 -29
- data/lib/hecks/projector/ir_projector.rb +7 -1
- data/lib/hecks/projector/narrate_projector.rb +144 -23
- data/lib/hecks/projector/target.rb +42 -18
- data/lib/hecks/projector.rb +86 -18
- data/lib/hecks/query_ir.rb +94 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +61 -15
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +9 -3
- data/lib/hecks/router/namespace_installer.rb +16 -3
- data/lib/hecks/router.rb +56 -1
- data/lib/hecks/runtime/aggregate_lock.rb +20 -11
- data/lib/hecks/runtime/boot_gates.rb +21 -3
- data/lib/hecks/runtime/caller.rb +40 -8
- data/lib/hecks/runtime/capability_graph.rb +13 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +40 -41
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +45 -67
- data/lib/hecks/runtime/command_interpreter.rb +195 -113
- data/lib/hecks/runtime/command_rules/admissibility.rb +231 -80
- data/lib/hecks/runtime/command_rules/arithmetic.rb +154 -96
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +145 -55
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +56 -11
- data/lib/hecks/runtime/dispatcher.rb +263 -92
- data/lib/hecks/runtime/entity_element.rb +304 -67
- data/lib/hecks/runtime/entity_interpreter.rb +149 -88
- data/lib/hecks/runtime/errors.rb +37 -23
- data/lib/hecks/runtime/event.rb +15 -10
- data/lib/hecks/runtime/identity.rb +91 -23
- data/lib/hecks/runtime/instance.rb +103 -18
- data/lib/hecks/runtime/interpreting.rb +24 -16
- data/lib/hecks/runtime/invocation.rb +358 -0
- data/lib/hecks/runtime/loader.rb +105 -19
- data/lib/hecks/runtime/outbox.rb +164 -26
- data/lib/hecks/runtime/policy_interpreter.rb +73 -60
- data/lib/hecks/runtime/port_operation_interpreter.rb +42 -19
- data/lib/hecks/runtime/query_interpreter.rb +93 -74
- data/lib/hecks/runtime/reaction_invocation.rb +73 -28
- data/lib/hecks/runtime/read_model_interpreter.rb +60 -44
- data/lib/hecks/runtime/rebuild_sweep.rb +32 -4
- data/lib/hecks/runtime/reference_hop.rb +48 -6
- data/lib/hecks/runtime/refusal_wording.rb +142 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +32 -21
- data/lib/hecks/runtime/registry/verification.rb +153 -28
- data/lib/hecks/runtime/registry.rb +202 -32
- data/lib/hecks/runtime/remote_dispatcher.rb +125 -24
- data/lib/hecks/runtime/routing.rb +36 -154
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +25 -27
- data/lib/hecks/runtime/saga_interpreter.rb +90 -76
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +33 -13
- data/lib/hecks/runtime/tenant_scope.rb +23 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +555 -142
- data/lib/hecks/runtime/value/entity_list_coercion.rb +132 -60
- data/lib/hecks/runtime/value.rb +71 -21
- data/lib/hecks/runtime.rb +39 -7
- data/lib/hecks/storehouse.rb +368 -72
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +130 -0
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +32 -0
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +91 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +19 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +24 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +38 -14
- metadata +28 -2
|
@@ -16,9 +16,9 @@ module Hecks
|
|
|
16
16
|
|
|
17
17
|
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
18
18
|
# task 4): a sentinel for "this keyword was never passed", distinct
|
|
19
|
-
# from Ruby's own nil/false. `then_set`'s
|
|
19
|
+
# from Ruby's own nil/false. `then_set`'s original default (`to:
|
|
20
20
|
# nil`) could not tell "not given" apart from "given, and the
|
|
21
|
-
# value
|
|
21
|
+
# value is false" — `to || from` silently treats `to: false` the
|
|
22
22
|
# same as an absent `to:` and falls through to `from` (also
|
|
23
23
|
# absent), so `then_set :accepted, to: false` raised "names no
|
|
24
24
|
# operation" for the one value most likely to be written that way
|
|
@@ -29,6 +29,19 @@ module Hecks
|
|
|
29
29
|
UNSET = Object.new.freeze
|
|
30
30
|
private_constant :UNSET
|
|
31
31
|
|
|
32
|
+
# @param name [String] the command's name, as written after `command`
|
|
33
|
+
# @param owner [String, nil] name of the aggregate or entity this command belongs to
|
|
34
|
+
# @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
|
|
35
|
+
# command guards from; nil admits from any state
|
|
36
|
+
# @param named_givens [Hash{String => Bluebook::Given}] the owner's own given pool, for
|
|
37
|
+
# bare `given` references to resolve against
|
|
38
|
+
# @param owner_attributes [Array<Bluebook::Attribute>] the owner's own attributes, for
|
|
39
|
+
# implicit `sets`/`state` resolution
|
|
40
|
+
# @param owner_constructs [Array<Bluebook::ValueObject, Bluebook::Entity>] the owner's
|
|
41
|
+
# own value objects and entities, for an `append:` element type to resolve against
|
|
42
|
+
# @param entity_shared_givens [Hash{String => Bluebook::Given}] a sibling piece's own
|
|
43
|
+
# entity-level given pool, for a piece-owned command's bare reference; empty for an
|
|
44
|
+
# aggregate-owned command
|
|
32
45
|
def initialize(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
|
|
33
46
|
entity_shared_givens: {})
|
|
34
47
|
@name = name
|
|
@@ -40,14 +53,14 @@ module Hecks
|
|
|
40
53
|
@named_givens = named_givens
|
|
41
54
|
@owner_attributes = owner_attributes
|
|
42
55
|
@owner_constructs = owner_constructs
|
|
43
|
-
#
|
|
56
|
+
# The aggregate-wide cross-entity pool — see
|
|
44
57
|
# `AggregateBuilder#entity`'s own comment and `EntityBuilder#
|
|
45
|
-
# given`'s. Empty (never populated) for an
|
|
58
|
+
# given`'s. Empty (never populated) for an aggregate-owned
|
|
46
59
|
# command, which already checks its own owner's `named_givens`
|
|
47
60
|
# directly and has no siblings to reach across; real only for
|
|
48
|
-
# an
|
|
61
|
+
# an entity-owned command's own bare reference.
|
|
49
62
|
@entity_shared_givens = entity_shared_givens
|
|
50
|
-
#
|
|
63
|
+
# Normalized the exact same way `StateTransition#from` already
|
|
51
64
|
# is — one state or several, a single spelling either way,
|
|
52
65
|
# both read back through `Array(...)` at check time.
|
|
53
66
|
@from = case from
|
|
@@ -57,20 +70,27 @@ module Hecks
|
|
|
57
70
|
end
|
|
58
71
|
end
|
|
59
72
|
|
|
60
|
-
# A command carries
|
|
61
|
-
# declared an
|
|
73
|
+
# A command carries one responsibility role — the language never
|
|
74
|
+
# declared an or between two roles, so a second `role` call would
|
|
62
75
|
# otherwise silently win while the first still looked declared,
|
|
63
76
|
# exactly the failure mode `reference_to`'s own duplicate guard
|
|
64
77
|
# (below) already exists to prevent for a command's root.
|
|
65
78
|
#
|
|
66
|
-
#
|
|
67
|
-
#
|
|
79
|
+
# Sets the command's one responsibility role, refusing a second declaration.
|
|
80
|
+
#
|
|
81
|
+
# Answers the `role` word through the table's `calls:` column —
|
|
82
|
+
# item #13's full metaprogrammed dispatch
|
|
83
|
+
# (slice 4). This is a uniqueness gate on prior state (`@role`
|
|
68
84
|
# already set), not a pure function of the argument's own value —
|
|
69
85
|
# a genuinely different shape than a plain fill, so it stays
|
|
70
86
|
# hand-written and is reached through `calls:` like `attribute`
|
|
71
87
|
# was in slice 3. Bootstrap-reachable (every self-hosted command
|
|
72
88
|
# declares a role), so also named in
|
|
73
89
|
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
90
|
+
#
|
|
91
|
+
# @param value [String, Symbol] the role's name
|
|
92
|
+
# @return [Object] `value` as stored
|
|
93
|
+
# @raise [Bluebook::DSL::Malformed] if a role is already declared
|
|
74
94
|
def role_impl(value)
|
|
75
95
|
if @role
|
|
76
96
|
raise Malformed,
|
|
@@ -82,29 +102,55 @@ module Hecks
|
|
|
82
102
|
@role = value
|
|
83
103
|
end
|
|
84
104
|
|
|
105
|
+
# Sets the human-readable description shown for this command.
|
|
106
|
+
#
|
|
107
|
+
# @param value [String] the description text
|
|
108
|
+
# @return [String] the description as stored
|
|
85
109
|
def goal(value) = @goal = value
|
|
86
110
|
|
|
87
|
-
#
|
|
111
|
+
# Names where a concept adopted from a canonical source came from.
|
|
112
|
+
#
|
|
113
|
+
# See `AggregateBuilder#provenance_impl`'s own comment — identical shape,
|
|
88
114
|
# one level down.
|
|
89
|
-
#
|
|
115
|
+
#
|
|
116
|
+
# Answers the `provenance` word through the table's `calls:`
|
|
117
|
+
# column — item #13's full metaprogrammed
|
|
90
118
|
# dispatch (slice 4c). Bootstrap-reachable, in
|
|
91
|
-
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
119
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
120
|
+
#
|
|
121
|
+
# @param from [Object] the canonical source, captured exactly as written
|
|
122
|
+
# @return [Object] `from` as stored
|
|
92
123
|
def provenance_impl(from:) = @provenance = from
|
|
93
124
|
|
|
125
|
+
# Declares the aggregate this command acts on (with no `as:`), or a cross-reference to
|
|
126
|
+
# another aggregate (with `as:`).
|
|
127
|
+
#
|
|
94
128
|
# `optional:` rides here as well as on a plain attribute : `as:` makes a
|
|
95
|
-
# reference into a
|
|
129
|
+
# reference into a named argument, and a named argument is exactly the kind
|
|
96
130
|
# of fact that may or may not be given. The meta-domain's Verb.Declare
|
|
97
131
|
# points at the Entity a command belongs to — and most commands belong to no
|
|
98
132
|
# entity at all.
|
|
99
|
-
#
|
|
133
|
+
#
|
|
134
|
+
# Answers the `reference_to` word through the table's `calls:`
|
|
135
|
+
# column — item #13's full metaprogrammed
|
|
100
136
|
# dispatch (slice 4b). Bootstrap-reachable, in
|
|
101
|
-
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
137
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
138
|
+
#
|
|
139
|
+
# @param type [Module, Symbol, String] the referenced aggregate, written as a bare
|
|
140
|
+
# constant
|
|
141
|
+
# @param as [Symbol, nil] the attribute's name for a cross-reference; nil declares the
|
|
142
|
+
# root this command acts on instead, unless `type` is a different aggregate than the
|
|
143
|
+
# owner
|
|
144
|
+
# @param optional [Boolean] whether the reference may be absent
|
|
145
|
+
# @return [void]
|
|
146
|
+
# @raise [Bluebook::DSL::Malformed] if the command already acts on a root, or (for a
|
|
147
|
+
# cross-reference) `as` is already declared
|
|
102
148
|
def reference_to_impl(type, as: nil, optional: false)
|
|
103
149
|
demodulised = Naming.demodulise(type)
|
|
104
150
|
# moved to the language: given "a command names what it acts on", on Verb.ActsOn
|
|
105
151
|
|
|
106
|
-
# `as:`
|
|
107
|
-
# can point at another instance of its
|
|
152
|
+
# `as:` means "a named attribute", not "the root I act on" — so a command
|
|
153
|
+
# can point at another instance of its own kind. Without this,
|
|
108
154
|
# `reference_to Aggregate, as: :points_at` on a command owned by Aggregate
|
|
109
155
|
# read as a second self-reference and was refused as naming two roots,
|
|
110
156
|
# which is how the meta-domain's own Aggregate.Reference could not say the
|
|
@@ -131,19 +177,32 @@ module Hecks
|
|
|
131
177
|
|
|
132
178
|
public
|
|
133
179
|
|
|
134
|
-
#
|
|
180
|
+
# Declares a precondition this command requires, or references one the owning aggregate
|
|
181
|
+
# (or a sibling piece) already declared.
|
|
182
|
+
#
|
|
183
|
+
# No block is a reference, not a fresh declaration (S10, ADR
|
|
135
184
|
# 0025 — "a precondition shared across commands is declared
|
|
136
185
|
# once. An aggregate declares it by name and commands
|
|
137
|
-
# reference it"): the
|
|
138
|
-
# (`AggregateBuilder#
|
|
186
|
+
# reference it"): the same word, the same shape
|
|
187
|
+
# (`AggregateBuilder#given_impl`, block required there), so naming a
|
|
139
188
|
# precondition back is spelled exactly like declaring one would
|
|
140
189
|
# be, minus the block — one idea, one word, never a second
|
|
141
190
|
# spelling ("requires"/"precondition") for "use the one already
|
|
142
|
-
# named". Resolved against whatever the
|
|
143
|
-
# declared so far — see `AggregateBuilder#
|
|
191
|
+
# named". Resolved against whatever the owning aggregate has
|
|
192
|
+
# declared so far — see `AggregateBuilder#command_impl`'s own
|
|
144
193
|
# comment on why that means declaration order matters here.
|
|
145
|
-
#
|
|
146
|
-
#
|
|
194
|
+
#
|
|
195
|
+
# Answers the `given` word through the table's `calls:` column —
|
|
196
|
+
# item #13's full metaprogrammed dispatch
|
|
197
|
+
# (slice 4b), same reasoning as `reference_to_impl` above.
|
|
198
|
+
#
|
|
199
|
+
# @param description [String] the rule's description; also the name the owning
|
|
200
|
+
# aggregate's rule is referenced by when no block is given
|
|
201
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
202
|
+
# @return [void]
|
|
203
|
+
# @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted, or
|
|
204
|
+
# given no block and the description names no precondition the owner (or a sibling
|
|
205
|
+
# piece under the same aggregate) declares
|
|
147
206
|
def given_impl(description, &predicate)
|
|
148
207
|
return reference_named_given(description) unless predicate
|
|
149
208
|
|
|
@@ -155,10 +214,10 @@ module Hecks
|
|
|
155
214
|
|
|
156
215
|
private
|
|
157
216
|
|
|
158
|
-
#
|
|
159
|
-
# command's own owner (as always),
|
|
160
|
-
# command, where it is real — a
|
|
161
|
-
# declaration under the
|
|
217
|
+
# Primitive 1 (RuleReference#resolve_hash_chain) — first this
|
|
218
|
+
# command's own owner (as always), then — only for a piece-owned
|
|
219
|
+
# command, where it is real — a sibling piece's own entity-level
|
|
220
|
+
# declaration under the same aggregate (`@entity_shared_givens`,
|
|
162
221
|
# threaded from `EntityBuilder#given`'s own write-through).
|
|
163
222
|
# "customer is active" declared once on `Visit`, referenced bare
|
|
164
223
|
# by `KeyIssuance.Return` — two different pieces, same aggregate,
|
|
@@ -179,11 +238,19 @@ module Hecks
|
|
|
179
238
|
|
|
180
239
|
public
|
|
181
240
|
|
|
182
|
-
#
|
|
241
|
+
# Declares a postcondition, checked against the settled record after the command's own
|
|
242
|
+
# mutations apply.
|
|
243
|
+
#
|
|
244
|
+
# The postcondition — a given for the far side of the mutations,
|
|
183
245
|
# evaluated against the settled record with `old` naming the state
|
|
184
246
|
# as it stood before them: `ensures("...") { old.balance.cents ==
|
|
185
247
|
# balance.cents + amount.cents }`. Same extraction, same Rule
|
|
186
|
-
# shape, same refusal form; EnsuresNotMet instead of GivenNotMet
|
|
248
|
+
# shape, same refusal form; `EnsuresNotMet` instead of `GivenNotMet`.
|
|
249
|
+
#
|
|
250
|
+
# @param description [String] the rule's description
|
|
251
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
252
|
+
# @return [void]
|
|
253
|
+
# @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted
|
|
187
254
|
def ensures(description, &predicate)
|
|
188
255
|
@ensures << build_rule(Given, description, predicate, owner_name: @name, word: "ensures",
|
|
189
256
|
extraction_failure: "a postcondition is carried as text, and this one has none")
|
|
@@ -205,14 +272,14 @@ module Hecks
|
|
|
205
272
|
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
206
273
|
# task 4, i106 in-DSL math): `multiply:`/`clamp:` -- per-tick organ
|
|
207
274
|
# math (miette's body/organs/bluebook: strength decays ×0.98,
|
|
208
|
-
# weight/strength clamp to [0, 1])
|
|
209
|
-
#
|
|
275
|
+
# weight/strength clamp to [0, 1]), moved into the bluebook itself
|
|
276
|
+
# from shell-side awk. `multiply:` mirrors
|
|
210
277
|
# increment/decrement's shape exactly (a Numeric amount, applied
|
|
211
278
|
# by CommandRules::Arithmetic -- see that file's own comment on
|
|
212
279
|
# the matching Float-support widening this required). `clamp:`
|
|
213
280
|
# is a genuinely different shape -- its source is always a literal
|
|
214
281
|
# `[min, max]` pair, never an argument reference, and it bounds
|
|
215
|
-
# the
|
|
282
|
+
# the current value rather than combining it with an amount -- so
|
|
216
283
|
# it does not reuse `arithmetic`/`arithmetic_value_object` at all;
|
|
217
284
|
# see MutationApplier#apply's own `:clamp` branch.
|
|
218
285
|
#
|
|
@@ -222,7 +289,7 @@ module Hecks
|
|
|
222
289
|
# commands: "the runtime list-remove primitive (then_set remove:)
|
|
223
290
|
# drops it from the list element-wise, with no read-modify-write
|
|
224
291
|
# -- so a concurrent Add can never be lost"). Matches an element
|
|
225
|
-
# by
|
|
292
|
+
# by value equality against `mutation.source` (resolved and
|
|
226
293
|
# Value-coerced the same way increment/decrement/multiply already
|
|
227
294
|
# coerce their own amount -- see MutationApplier#removed).
|
|
228
295
|
#
|
|
@@ -235,23 +302,23 @@ module Hecks
|
|
|
235
302
|
# boolean occurrences of the bare-positional-second-arg shape
|
|
236
303
|
# anywhere in the corpus). Folded into `to:` itself rather than
|
|
237
304
|
# given its own mutation op -- semantically identical, same
|
|
238
|
-
# UNSET
|
|
305
|
+
# `UNSET`-sentinel discipline the `to: false` fix already
|
|
239
306
|
# established (a positional `false` must read as "set to
|
|
240
307
|
# false," not "absent," same as the keyword form). Only applied
|
|
241
|
-
# when `to:` itself was
|
|
308
|
+
# when `to:` itself was not also given, so an explicit `to:`
|
|
242
309
|
# keyword always wins over a stray positional.
|
|
243
310
|
#
|
|
244
311
|
# `sets` is the word (ADR 0025 reverts `then_set` — the grammar
|
|
245
312
|
# already declared `sets`, `was: "then_set"`, and 143 of 143 live
|
|
246
313
|
# call sites are `then_set`, so this method was the one thing
|
|
247
|
-
# still backwards). `to:` is
|
|
314
|
+
# still backwards). `to:` is omittable when it would only repeat
|
|
248
315
|
# the target — `sets :number` alone already means `to: :number`
|
|
249
|
-
# — and the
|
|
316
|
+
# — and the redundant explicit spelling is refused outright
|
|
250
317
|
# (principle 1, "one idea, one spelling": `sets :number, to:
|
|
251
318
|
# :number` says nothing `sets :number` doesn't). `from:` — a
|
|
252
319
|
# pure synonym for `to:` the language's own refusal message had
|
|
253
320
|
# already forgotten about — is gone; write `to:`.
|
|
254
|
-
#
|
|
321
|
+
# The op each kwarg selects — `spec/syntax_conformance_spec.rb`'s own
|
|
255
322
|
# "selects the same op..." check holds this constant to the self-
|
|
256
323
|
# hosted table's own `Argument#selects` column (`"op=set"`,
|
|
257
324
|
# `"op=append"`, ...; whole-project table-unification survey, item
|
|
@@ -262,24 +329,47 @@ module Hecks
|
|
|
262
329
|
KWARG_TO_OP = { to: :set, append: :append, increment: :increment, decrement: :decrement,
|
|
263
330
|
multiply: :multiply, clamp: :clamp, remove: :remove }.freeze
|
|
264
331
|
|
|
265
|
-
#
|
|
332
|
+
# Declares one mutation this command applies to `target`, its op selected by whichever
|
|
333
|
+
# single keyword (or the omitted, self-referential form) names a source.
|
|
334
|
+
#
|
|
335
|
+
# Answers the `sets` word through the table's `calls:` column —
|
|
336
|
+
# item #13's full metaprogrammed dispatch
|
|
266
337
|
# (slice 4c). The `KWARG_TO_OP` op-selection mapping is already
|
|
267
|
-
# table-verified (`Argument#selects`), but the REST (UNSET
|
|
338
|
+
# table-verified (`Argument#selects`), but the REST (`UNSET`-
|
|
268
339
|
# sentinel discipline, redundant-spelling refusal, omittable-
|
|
269
340
|
# `to:` fallback, one-mutation-only refusal, the position-
|
|
270
|
-
# preserving `resolve_*!` reinsertion) is keyed off
|
|
271
|
-
#
|
|
341
|
+
# preserving `resolve_*!` reinsertion) is keyed off runtime
|
|
342
|
+
# state, not a pure function of a static row — stays hand-
|
|
272
343
|
# written, reached through `calls:` like everything else here.
|
|
273
|
-
# Bootstrap-reachable, in BOOTSTRAP_CALLS_FALLBACK
|
|
344
|
+
# Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
345
|
+
#
|
|
346
|
+
# @param target [Symbol, String] the field this mutation writes
|
|
347
|
+
# @param positional_to [Object] a bare positional value, folded into `to:` when `to:`
|
|
348
|
+
# itself is not given; the corpus's only real use is a boolean shorthand
|
|
349
|
+
# (`sets :deployed, true`)
|
|
350
|
+
# @param to [Object] the value or field reference to set `target` to; a Symbol equal to
|
|
351
|
+
# `target` is redundant and refused, and omitting `to:` entirely means the same thing
|
|
352
|
+
# @param append [Object] a Hash of fields for a new list element, or a bare value for the
|
|
353
|
+
# one-field shorthand
|
|
354
|
+
# @param increment [Object] the amount to add to `target`'s current value
|
|
355
|
+
# @param decrement [Object] the amount to subtract from `target`'s current value
|
|
356
|
+
# @param multiply [Object] the amount to multiply `target`'s current value by
|
|
357
|
+
# @param clamp [Array(Object, Object)] the `[min, max]` pair to bound `target`'s current
|
|
358
|
+
# value to
|
|
359
|
+
# @param remove [Object] the value to remove from `target`'s own list, by equality
|
|
360
|
+
# @return [void]
|
|
361
|
+
# @raise [Bluebook::DSL::Malformed] if `to:` redundantly repeats `target`, or more than
|
|
362
|
+
# one of `to:`/`append:`/`increment:`/`decrement:`/`multiply:`/`clamp:`/`remove:` (or
|
|
363
|
+
# the folded `positional_to`) is given
|
|
274
364
|
def sets_impl(target, positional_to = UNSET, to: UNSET, append: UNSET,
|
|
275
365
|
increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
|
|
276
366
|
# moved to the language: given "a mutation names a target", on Verb.Change
|
|
277
367
|
|
|
278
368
|
to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
|
|
279
369
|
|
|
280
|
-
# `to:` only ever
|
|
370
|
+
# `to:` only ever repeats the target when it's a Symbol naming a
|
|
281
371
|
# field — a literal (`to: false`, the bare positional-boolean
|
|
282
|
-
# shorthand, a String, ...) is a
|
|
372
|
+
# shorthand, a String, ...) is a value, never a redundant name,
|
|
283
373
|
# so it never has `.to_sym` to compare in the first place.
|
|
284
374
|
if to.is_a?(Symbol) && to == target.to_sym
|
|
285
375
|
raise Malformed,
|
|
@@ -292,7 +382,7 @@ module Hecks
|
|
|
292
382
|
.reject { |_, source| source.equal?(UNSET) }
|
|
293
383
|
named = given.to_h { |kwarg, source| [KWARG_TO_OP.fetch(kwarg), source] }
|
|
294
384
|
|
|
295
|
-
#
|
|
385
|
+
# **The omittable case**. No operation was named at all — not even a
|
|
296
386
|
# bare `to:` — so this is `sets :field` alone, which means
|
|
297
387
|
# exactly what the redundant, refused spelling above would have.
|
|
298
388
|
named = { set: target } if named.empty?
|
|
@@ -307,31 +397,45 @@ module Hecks
|
|
|
307
397
|
@mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
|
|
308
398
|
end
|
|
309
399
|
|
|
310
|
-
#
|
|
311
|
-
#
|
|
400
|
+
# Refuses the retired `then_set` spelling, except while shadow-parsing frozen era text,
|
|
401
|
+
# where it is read as the legacy mutation shape.
|
|
402
|
+
#
|
|
403
|
+
# Legacy under shadow-parsing (S0a's own bridge) — frozen era
|
|
404
|
+
# text still parses under it; live source refuses it,
|
|
312
405
|
# naming the replacement.
|
|
313
|
-
#
|
|
314
|
-
#
|
|
406
|
+
#
|
|
407
|
+
# Answers the `then_set` word — item #13's full metaprogrammed
|
|
408
|
+
# dispatch (slice 5). Not bootstrap-reachable. Has its own
|
|
315
409
|
# dedicated, `status: "deprecated"` Keyword row (syntax.bluebook)
|
|
316
410
|
# rather than living only as `sets`'s own `was:` — see that
|
|
317
411
|
# row's own comment for why.
|
|
412
|
+
#
|
|
413
|
+
# @param target [Symbol, String] the field this legacy mutation writes
|
|
414
|
+
# @param positional_to [Object] the bare second positional argument; `UNSET` when
|
|
415
|
+
# omitted, forwarded to `legacy_then_set` verbatim under shadow-parsing, along with
|
|
416
|
+
# every keyword argument this call received
|
|
417
|
+
# @return [void]
|
|
418
|
+
# @raise [Bluebook::DSL::Malformed] outside shadow-parsing, always; under
|
|
419
|
+
# shadow-parsing, if `legacy_then_set` names no operation or more than one
|
|
318
420
|
def then_set_impl(target, positional_to = UNSET, **)
|
|
319
421
|
return legacy_then_set(target, positional_to, **) if MetaValidator.shadow_parsing?
|
|
320
422
|
|
|
321
423
|
raise Malformed, "#{@name}'s then_set is gone — sets is the word now"
|
|
322
424
|
end
|
|
323
425
|
|
|
426
|
+
# Declares one event this command announces to the outside.
|
|
427
|
+
#
|
|
324
428
|
# No raise here. "an event is named" is declared in the language itself —
|
|
325
429
|
# language/bluebook/behavior.bluebook, on Command.Announce — and MetaValidator is what
|
|
326
|
-
# enforces it. This is the first rule to move
|
|
430
|
+
# enforces it. This is the first rule to move across rather than be
|
|
327
431
|
# duplicated : delete the declaration and an unnamed event is accepted,
|
|
328
432
|
# which is what makes the meta-domain load-bearing rather than decorative.
|
|
329
433
|
#
|
|
330
|
-
#
|
|
434
|
+
# Bare constant accepted (ADR 0025, S6 — "events first-class"),
|
|
331
435
|
# `emits Account::AccountFrozen`, resolved through `ConstShim` the
|
|
332
436
|
# same way `trigger`/`dispatch` already resolve a command
|
|
333
|
-
# reference (`Naming.event_ref`, that method's own header).
|
|
334
|
-
# yet a
|
|
437
|
+
# reference (`Naming.event_ref`, that method's own header). Not
|
|
438
|
+
# yet a required spelling, deliberately, unlike `trigger`/
|
|
335
439
|
# `dispatch`'s own quoted-text refusal: those were safe to refuse
|
|
336
440
|
# only because command references are already 100% migrated
|
|
337
441
|
# across the live corpus (verified 2026-08-27) — `emits`/`on`
|
|
@@ -341,27 +445,41 @@ module Hecks
|
|
|
341
445
|
# are accepted in live source until a full corpus migration
|
|
342
446
|
# lands and the same refusal this file's `reference_to`/
|
|
343
447
|
# `trigger_impl` already carry can be added here safely.
|
|
448
|
+
#
|
|
449
|
+
# @param event_name [String, Symbol, Module] the event, quoted text or a bare constant
|
|
450
|
+
# such as `Account::AccountFrozen`
|
|
451
|
+
# @return [Array<String>] every event declared so far, this one last
|
|
344
452
|
def emits(event_name)
|
|
345
453
|
@emits << Naming.event_ref(event_name)
|
|
346
454
|
end
|
|
347
455
|
|
|
348
|
-
#
|
|
456
|
+
# The record's own value as a mutation source — `sets :positions,
|
|
349
457
|
# append: { ply: state(:ply), knights: state(:knights) }` copies
|
|
350
|
-
# what the record holds
|
|
458
|
+
# what the record holds now into the new element; `sets :last,
|
|
351
459
|
# to: state(:current)` copies one field onto another. A bare
|
|
352
|
-
# Symbol always names an argument (
|
|
460
|
+
# Symbol always names an argument (`resolve_append_fields!`'s own
|
|
461
|
+
# comment has the full account),
|
|
353
462
|
# so without this a command could not snapshot its own state at
|
|
354
463
|
# all. `Literal::StateRef`'s own comment has the wire spelling.
|
|
464
|
+
#
|
|
465
|
+
# References the record's own current field as a mutation source, as opposed to a
|
|
466
|
+
# bare Symbol, which names an argument.
|
|
467
|
+
#
|
|
468
|
+
# @param name [Symbol, String] the field to read from the pre-dispatch record
|
|
469
|
+
# @return [Literal::StateRef] the wrapped reference
|
|
355
470
|
def state(name) = StateRef.new(name.to_sym)
|
|
356
471
|
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
472
|
+
# Declares a synchronous, atomic delegation of this command's own dispatch to one
|
|
473
|
+
# nested entity command.
|
|
474
|
+
#
|
|
475
|
+
# The synchronous cousin of `trigger` — an aggregate-level command
|
|
476
|
+
# that hands its own dispatch to one nested entity command, checked
|
|
477
|
+
# and applied within the same atomic dispatch rather than a second
|
|
360
478
|
# one. Built because `trigger`/`saga`'s own dispatch (`Dispatcher
|
|
361
|
-
# #reenter`) is a
|
|
479
|
+
# #reenter`) is a reaction — the triggering command has already
|
|
362
480
|
# committed by the time it runs, and both `PolicyInterpreter#deliver`
|
|
363
481
|
# and `SagaInterpreter#deliver_saga_dispatch` rescue a target's own
|
|
364
|
-
# refusal and
|
|
482
|
+
# refusal and record it rather than raising it back to the original
|
|
365
483
|
# caller. That is correct for what those two exist for (an
|
|
366
484
|
# eventually-consistent process that can compensate), and wrong for
|
|
367
485
|
# a caller who needs a synchronous yes/no on whether the thing they
|
|
@@ -370,59 +488,66 @@ module Hecks
|
|
|
370
488
|
# project. `delegates_to` fills exactly that gap: the target
|
|
371
489
|
# entity command's own `given`/`ensures` are enforced as real,
|
|
372
490
|
# unrescued Ruby exceptions, so a refusal deep in the entity's own
|
|
373
|
-
# rules is the
|
|
491
|
+
# rules is the delegating command's own refusal too, and nothing
|
|
374
492
|
# from either side is saved unless both sides pass.
|
|
375
493
|
#
|
|
376
|
-
# `target` is always
|
|
494
|
+
# `target` is always one hop, `"Entity.Command"` — an aggregate
|
|
377
495
|
# names the entity it owns directly, same reach a bare `given`
|
|
378
496
|
# reference already has (see `Knight`'s own comment on this
|
|
379
497
|
# domain's shared givens), not a multi-segment dispatch chain.
|
|
380
|
-
# `with:` resolves the
|
|
498
|
+
# `with:` resolves the same way `sets ..., append: {...}`'s own
|
|
381
499
|
# field map and a policy's own `trigger ..., with: {...}` already
|
|
382
|
-
# do: each value names one of
|
|
500
|
+
# do: each value names one of this command's own declared/implicit
|
|
383
501
|
# arguments, read at dispatch time and handed to the target under
|
|
384
502
|
# its own key.
|
|
385
503
|
#
|
|
386
|
-
#
|
|
504
|
+
# Mutually exclusive with `sets`/`emits` on the same command — a
|
|
387
505
|
# delegating command is a pure passthrough by design (see this
|
|
388
|
-
# method's own header), so it declares no
|
|
389
|
-
# its own; its result
|
|
506
|
+
# method's own header), so it declares no other mutation or event of
|
|
507
|
+
# its own; its result is whatever the delegated entity command's own
|
|
390
508
|
# `sets`/`emits` produced. Enforced in `build`, once every builder
|
|
391
509
|
# call has already run, so declaration order does not matter.
|
|
392
510
|
#
|
|
393
|
-
#
|
|
511
|
+
# Stored as a mutation, not a new Command field — a real, deliberate
|
|
394
512
|
# choice, not a shortcut. `Command`'s own shape (givens/ensures/
|
|
395
513
|
# mutations/emits/...) is not just Ruby: it round-trips through this
|
|
396
|
-
# language's
|
|
514
|
+
# language's own self-hosted meta-domain (`Bluebook::MetaValidator`
|
|
397
515
|
# dispatches every declaration into a "Bluebook" domain describing
|
|
398
|
-
# itself, then
|
|
516
|
+
# itself, then rebuilds the real runtime graph from what that domain
|
|
399
517
|
# holds — `Hecks.bluebook` registers what `MetaValidator.call`
|
|
400
518
|
# returns, never the builder's own object graph directly, confirmed
|
|
401
519
|
# by reading `meta_validator.rb`'s own `self.call`/`self.hold`).
|
|
402
|
-
# A genuinely
|
|
520
|
+
# A genuinely new top-level Command field needs the meta-domain's
|
|
403
521
|
# own grammar (`language/bluebook/behavior.bluebook` or wherever
|
|
404
522
|
# Verb.Rule/Ensure/Change live) taught to carry it too — the same
|
|
405
523
|
# scale of change as the real "item #13" migration this file's own
|
|
406
|
-
# comments document throughout. A
|
|
407
|
-
# own `mutations:` field is
|
|
524
|
+
# comments document throughout. A new mutation op does not: `sets`'s
|
|
525
|
+
# own `mutations:` field is already a fully round-tripped part of
|
|
408
526
|
# that contract (`Assembly::CONTRACTS["Command"].fields[:mutations]`),
|
|
409
|
-
# and an append-shaped mutation
|
|
527
|
+
# and an append-shaped mutation already carries a multi-key `fields:`
|
|
410
528
|
# hash the exact shape `with:` needs — so `delegates_to` rides that
|
|
411
529
|
# existing, already-correct wire format under a new `op: :delegate`
|
|
412
530
|
# instead of inventing a parallel one. `MutationOp`'s own closed set
|
|
413
531
|
# (vocabulary.bluebook) gained `"delegate"` alongside `"append"`
|
|
414
|
-
# for exactly this reason, and the
|
|
532
|
+
# for exactly this reason, and the three meta-domain touch points
|
|
415
533
|
# that hard-coded `op == "append"` for the multi-binding shape
|
|
416
534
|
# (`meta_validator/readings.rb#mutation_rows`,
|
|
417
535
|
# `meta_validator/shapes.rb#mutation`, `assembly/marks.rb#mutation`)
|
|
418
536
|
# now check for `:delegate` alongside it, each with a comment
|
|
419
537
|
# pointing back here.
|
|
420
|
-
#
|
|
421
|
-
# — matches `sets_impl`/`given_impl`/`reference_to_impl`'s own
|
|
538
|
+
# Answers the `delegates_to` word through the table's `calls:`
|
|
539
|
+
# column — matches `sets_impl`/`given_impl`/`reference_to_impl`'s own
|
|
422
540
|
# convention (language/bluebook/syntax.bluebook's own Keyword row
|
|
423
541
|
# for this word names `calls: "delegates_to_impl"`), the same
|
|
424
542
|
# `word`-vs-`_impl` split every hand-written (not yet item-#13-
|
|
425
543
|
# generic-dispatch-migrated) DSL word here already follows.
|
|
544
|
+
#
|
|
545
|
+
# @param target [String, Symbol] the delegated command, dotted `"Entity.Command"`
|
|
546
|
+
# @param with [Hash{Symbol => Symbol, Object}] projects this command's own arguments
|
|
547
|
+
# onto the target's; a Symbol value names one of this command's own arguments,
|
|
548
|
+
# anything else is a literal
|
|
549
|
+
# @return [Array<Bluebook::Mutation>] every mutation declared so far, this one last
|
|
550
|
+
# @raise [Bluebook::DSL::Malformed] if `target` is not `"Entity.Command"` shaped
|
|
426
551
|
def delegates_to_impl(target, with: {})
|
|
427
552
|
entity_name, _dot, command_name = target.to_s.rpartition(".")
|
|
428
553
|
if entity_name.empty? || command_name.empty?
|
|
@@ -434,10 +559,12 @@ module Hecks
|
|
|
434
559
|
@mutations << Mutation.new(target: target.to_s, op: :delegate, source: with)
|
|
435
560
|
end
|
|
436
561
|
|
|
437
|
-
#
|
|
562
|
+
# Declares that this command amends a past event, rather than rewriting it.
|
|
563
|
+
#
|
|
564
|
+
# A command declaring what past fact it amends — the append-only
|
|
438
565
|
# answer to "what if this record's history turns out to have been
|
|
439
566
|
# wrong": never rewrite the original event (the log stays exactly
|
|
440
|
-
# what it was), always append a
|
|
567
|
+
# what it was), always append a new fact on top. `event` names the
|
|
441
568
|
# event this command corrects; `as:` optionally binds the located
|
|
442
569
|
# instance for a `given`/`ensures` to reference, the same shape
|
|
443
570
|
# `ensures`'s own `old` binding already has; `reason:` is not
|
|
@@ -446,33 +573,45 @@ module Hecks
|
|
|
446
573
|
# here is why"), refused when blank the same way a `given`'s own
|
|
447
574
|
# description is required to say something.
|
|
448
575
|
#
|
|
449
|
-
#
|
|
576
|
+
# Stored as a mutation, not a new Command field — see the
|
|
450
577
|
# KeywordSeed row's own comment (command.bluebook) for why: this
|
|
451
578
|
# is the exact same choice `delegates_to` already made, for the
|
|
452
|
-
# exact same reason. Rides the
|
|
579
|
+
# exact same reason. Rides the same multi-binding wire shape
|
|
453
580
|
# `append`/`delegate` use — `as:`/`reason:`/`reverses:` assembled
|
|
454
581
|
# by hand into one `source` hash, the way `sets_impl` assembles up
|
|
455
582
|
# to seven kwargs into one `named` hash above.
|
|
456
583
|
#
|
|
457
|
-
# `reverses: true`
|
|
584
|
+
# `reverses: true` names an intent to auto-derive the corrective
|
|
458
585
|
# `sets` from the original event's own mutations, rather than the
|
|
459
586
|
# author writing it — see `AggregateBuilder#seal_correction_targets`,
|
|
460
587
|
# where that derivation actually happens (it needs every sibling
|
|
461
588
|
# command in the aggregate already known, which this builder alone
|
|
462
|
-
# cannot see).
|
|
589
|
+
# cannot see). Mutually exclusive with an explicit `sets` on the
|
|
463
590
|
# same command — two ways of saying the same thing is exactly the
|
|
464
591
|
# redundancy `sets`'s own omittable-`to:` rule refuses elsewhere.
|
|
465
592
|
#
|
|
466
|
-
# `as:`
|
|
593
|
+
# `as:` is always stored as text, never left a bare Symbol —
|
|
467
594
|
# `Mutation#classified_source`/`#appended_fields` (Behaviour::
|
|
468
595
|
# Mutation) classify any bare Symbol field as `kind: "argument"`,
|
|
469
|
-
# meaning "resolve this against one of
|
|
596
|
+
# meaning "resolve this against one of this command's own declared
|
|
470
597
|
# attributes at dispatch time" (append/delegate's own meaning for a
|
|
471
598
|
# Symbol). `as:` names no such thing — it is a plain label, not yet
|
|
472
599
|
# wired into the expression evaluator (a future round's work, once
|
|
473
600
|
# a real runtime consumer exists) — so coercing it to a String here
|
|
474
601
|
# keeps it out of that machinery entirely rather than silently
|
|
475
602
|
# miscategorised as an unresolvable argument reference.
|
|
603
|
+
#
|
|
604
|
+
# @param event [String, Symbol, Module] the event this command corrects, quoted text or
|
|
605
|
+
# a bare constant
|
|
606
|
+
# @param as [Symbol, nil] binds the located instance under this label, for a
|
|
607
|
+
# `given`/`ensures` to reference; nil declares no binding
|
|
608
|
+
# @param reason [String, nil] why the correction is being made, carried as audit data;
|
|
609
|
+
# required, non-blank
|
|
610
|
+
# @param reverses [Boolean] true to auto-derive the corrective `sets` from the original
|
|
611
|
+
# event's own mutations, checked once every sibling command is known; mutually
|
|
612
|
+
# exclusive with an explicit `sets` on the same command
|
|
613
|
+
# @return [Array<Bluebook::Mutation>] every mutation declared so far, this one last
|
|
614
|
+
# @raise [Bluebook::DSL::Malformed] if `reason` is nil or blank
|
|
476
615
|
def corrects_impl(event, as: nil, reason: nil, reverses: false)
|
|
477
616
|
if reason.to_s.strip.empty?
|
|
478
617
|
raise Malformed,
|
|
@@ -485,10 +624,17 @@ module Hecks
|
|
|
485
624
|
source: { as: as&.to_s, reason: reason.to_s, reverses: reverses })
|
|
486
625
|
end
|
|
487
626
|
|
|
488
|
-
#
|
|
627
|
+
# The effects that write a field of the record — `delegate` and
|
|
489
628
|
# `corrects` name a command and an event, never a field.
|
|
490
629
|
FIELD_EFFECTS = %i[set append remove increment decrement multiply clamp].freeze
|
|
491
630
|
|
|
631
|
+
# Resolves implicit attributes, refuses conflicting mutations, and assembles the
|
|
632
|
+
# declared rules and effects into a `Command`.
|
|
633
|
+
#
|
|
634
|
+
# @return [Bluebook::Command] the built command
|
|
635
|
+
# @raise [Bluebook::DSL::Malformed] if the same field is written twice, an argument or
|
|
636
|
+
# state source names an undeclared field, or a delegating command also declares its
|
|
637
|
+
# own mutations or events
|
|
492
638
|
def build
|
|
493
639
|
resolve_implicit_attributes!
|
|
494
640
|
refuse_duplicate_targets!
|
|
@@ -516,6 +662,21 @@ module Hecks
|
|
|
516
662
|
)
|
|
517
663
|
end
|
|
518
664
|
|
|
665
|
+
# Evaluates a `command` block against a fresh builder and returns what it built.
|
|
666
|
+
#
|
|
667
|
+
# @param name [String] the command's name
|
|
668
|
+
# @param owner [String, nil] name of the aggregate or entity this command belongs to
|
|
669
|
+
# @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
|
|
670
|
+
# command guards from
|
|
671
|
+
# @param named_givens [Hash{String => Bluebook::Given}] the owner's own given pool
|
|
672
|
+
# @param owner_attributes [Array<Bluebook::Attribute>] the owner's own attributes
|
|
673
|
+
# @param owner_constructs [Array<Bluebook::ValueObject, Bluebook::Entity>] the owner's
|
|
674
|
+
# own value objects and entities
|
|
675
|
+
# @param entity_shared_givens [Hash{String => Bluebook::Given}] a sibling piece's own
|
|
676
|
+
# entity-level given pool
|
|
677
|
+
# @yield the command body, evaluated with the builder as `self`; may be omitted
|
|
678
|
+
# @return [Bluebook::Command] the built command
|
|
679
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
|
|
519
680
|
def self.build(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
|
|
520
681
|
entity_shared_givens: {}, &block)
|
|
521
682
|
builder = new(name, owner: owner, from: from, named_givens: named_givens,
|
|
@@ -528,7 +689,7 @@ module Hecks
|
|
|
528
689
|
private
|
|
529
690
|
|
|
530
691
|
# C4.2 (docs/semantics/bluebook-semantics.md) — a command's effects
|
|
531
|
-
# are
|
|
692
|
+
# are one update set over the pre-dispatch state, so a field
|
|
532
693
|
# written twice has no meaning to give: last-wins would make
|
|
533
694
|
# declaration order significant, which the update set says it is
|
|
534
695
|
# not. Refused here, where the declaration can still be read whole.
|
|
@@ -548,21 +709,21 @@ module Hecks
|
|
|
548
709
|
end
|
|
549
710
|
end
|
|
550
711
|
|
|
551
|
-
#
|
|
712
|
+
# **Resolution rules** — see `docs/resolution-rules/README.md` for the
|
|
552
713
|
# precise, language-agnostic algorithm each of `resolve_bare_set!`/
|
|
553
714
|
# `resolve_append_fields!` implements (`implicit-command-attributes.md`
|
|
554
715
|
# / `implicit-append-fields.md`) — the contract a Rust mirror is
|
|
555
716
|
# written from, not inferred from this comment.
|
|
556
717
|
#
|
|
557
|
-
# `sets :field`
|
|
718
|
+
# `sets :field` alone (S5's own bare form — no `to:`, meaning
|
|
558
719
|
# `to: :field`) already says the command accepts an argument
|
|
559
|
-
# named `:field`; requiring a
|
|
720
|
+
# named `:field`; requiring a separate `attribute :field, Type`
|
|
560
721
|
# line that retypes what the owning aggregate/entity already
|
|
561
722
|
# declared is the same redundancy S10's `given` reference already
|
|
562
723
|
# killed for preconditions ("a precondition shared across
|
|
563
724
|
# commands is declared once... a command references it by
|
|
564
725
|
# name"). Same move here, one level down: when the command
|
|
565
|
-
# hasn't declared its own `:field`, import the
|
|
726
|
+
# hasn't declared its own `:field`, import the owner's
|
|
566
727
|
# already-built `Attribute` verbatim (same type, pattern,
|
|
567
728
|
# optional, admits) instead of retyping it.
|
|
568
729
|
#
|
|
@@ -576,7 +737,7 @@ module Hecks
|
|
|
576
737
|
#
|
|
577
738
|
# Declaration order matters here the same way it already does
|
|
578
739
|
# for `identified_by`/`given` — the owner's own attribute must
|
|
579
|
-
# exist by the time
|
|
740
|
+
# exist by the time this builder's `build` runs, which every
|
|
580
741
|
# real bluebook already satisfies (the aggregate/entity always
|
|
581
742
|
# declares its attributes before the commands that act on them).
|
|
582
743
|
def resolve_implicit_attributes!
|
|
@@ -587,9 +748,9 @@ module Hecks
|
|
|
587
748
|
end
|
|
588
749
|
refuse_unknown_state_sources!(mutation)
|
|
589
750
|
end
|
|
590
|
-
# A
|
|
751
|
+
# A second, separate pass — not folded into the loop above — so
|
|
591
752
|
# every mutation's own self-referential import (resolve_bare_set!)
|
|
592
|
-
# has already landed before any mutation's
|
|
753
|
+
# has already landed before any mutation's source is checked
|
|
593
754
|
# against the final `attributes` list. `sets :a, to: :b` declared
|
|
594
755
|
# before `sets :b` (bare, importing :b from the owner) is real
|
|
595
756
|
# and legal; checking inline, mutation by mutation, would refuse
|
|
@@ -601,8 +762,8 @@ module Hecks
|
|
|
601
762
|
@mutations.each { |mutation| refuse_unknown_argument_sources!(mutation) }
|
|
602
763
|
end
|
|
603
764
|
|
|
604
|
-
#
|
|
605
|
-
#
|
|
765
|
+
# The ones `resolve_source` (CommandRules::Arithmetic) only ever
|
|
766
|
+
# reads from `args` — never a fallback to the record's own current
|
|
606
767
|
# state the way `append`'s own per-field resolution legitimately
|
|
607
768
|
# can (`MutationApplier#resolve_append_source`'s own `instance
|
|
608
769
|
# [source]` fallback, a real, intentional second meaning this
|
|
@@ -612,8 +773,8 @@ module Hecks
|
|
|
612
773
|
# runtime, not merely optional: no caller can ever supply a value
|
|
613
774
|
# under a name the command never declared (ArgumentGate's own
|
|
614
775
|
# `refuse_unknown_arguments` already refuses that), so the source
|
|
615
|
-
# resolves to nil
|
|
616
|
-
# absent
|
|
776
|
+
# resolves to nil forever, indistinguishable from a legitimately
|
|
777
|
+
# absent optional argument until this check existed to tell them
|
|
617
778
|
# apart. Mirrors `AggregateBuilder#seal_query_argument`'s
|
|
618
779
|
# identical shape for a query's own where-clause argument —
|
|
619
780
|
# same mistake, one construct over.
|
|
@@ -626,9 +787,9 @@ module Hecks
|
|
|
626
787
|
# The bare self-referential shape (`sets :field` alone, `source
|
|
627
788
|
# == target`) is `resolve_bare_set!`'s own territory, not this
|
|
628
789
|
# check's — when neither the command nor the owner declares
|
|
629
|
-
# that name, the
|
|
790
|
+
# that name, the more specific, pre-existing refusal one level
|
|
630
791
|
# up (`AggregateBuilder#seal_mutation_targets`, checking the
|
|
631
|
-
# mutation's
|
|
792
|
+
# mutation's target against the aggregate's own fields) is the
|
|
632
793
|
# one that should fire, naming the field as a target problem,
|
|
633
794
|
# not — confusingly — as a source problem this check would
|
|
634
795
|
# otherwise misreport it as.
|
|
@@ -642,7 +803,7 @@ module Hecks
|
|
|
642
803
|
"caller actually sent"
|
|
643
804
|
end
|
|
644
805
|
|
|
645
|
-
# `state(:name)` names one of the
|
|
806
|
+
# `state(:name)` names one of the owner's own fields — a snapshot
|
|
646
807
|
# of something the record actually holds. Refused at build, by
|
|
647
808
|
# name, the way an unknown `given` reference is; nothing here can
|
|
648
809
|
# read a field the aggregate never declared.
|
|
@@ -657,11 +818,11 @@ module Hecks
|
|
|
657
818
|
end
|
|
658
819
|
|
|
659
820
|
def resolve_bare_set!(mutation)
|
|
660
|
-
# A
|
|
821
|
+
# A symbol naming its own target — never a literal that merely
|
|
661
822
|
# spells the same word. `sets :moved, to: "moved"` (a chess rook
|
|
662
823
|
# recording that it has moved, into a closed set whose member is
|
|
663
|
-
# literally "moved")
|
|
664
|
-
# the owner's `moved` attribute onto the command — a phantom
|
|
824
|
+
# literally "moved") must not read as the shorthand and import
|
|
825
|
+
# the owner's `moved` attribute onto the command — that would add a phantom
|
|
665
826
|
# argument nothing ever passes, harmless at runtime only
|
|
666
827
|
# because the owner's default filled it, and a real, silent
|
|
667
828
|
# divergence for every projection that reads the command's
|
|
@@ -673,9 +834,9 @@ module Hecks
|
|
|
673
834
|
attributes << owner_attr if owner_attr
|
|
674
835
|
end
|
|
675
836
|
|
|
676
|
-
#
|
|
837
|
+
# One hop deeper than `resolve_bare_set!` — an `append:` mutation
|
|
677
838
|
# (`sets :ledger, append: { narrative: :narrative, ... }`) builds
|
|
678
|
-
# a
|
|
839
|
+
# a new element of a list field, not the command's own root
|
|
679
840
|
# record, so a bare self-referential field inside it (the hash
|
|
680
841
|
# key equals its own value, same shorthand `resolve_bare_set!`
|
|
681
842
|
# already reads) can't resolve against `@owner_attributes` — the
|
|
@@ -683,7 +844,7 @@ module Hecks
|
|
|
683
844
|
# element's own construct does (`attribute :ledger,
|
|
684
845
|
# list_of(LedgerEntry)`, and `LedgerEntry` is what actually
|
|
685
846
|
# declares `:narrative`). Resolves the list field's own element
|
|
686
|
-
#
|
|
847
|
+
# type first (`element_type_for`), then that construct's own
|
|
687
848
|
# attribute of the same name — same verbatim-import, one level
|
|
688
849
|
# further down the same reasoning `resolve_bare_set!`'s own
|
|
689
850
|
# comment already gives.
|
|
@@ -693,21 +854,21 @@ module Hecks
|
|
|
693
854
|
# its own key ever qualifies, identical to `resolve_bare_set!`'s
|
|
694
855
|
# own target/source text comparison.
|
|
695
856
|
#
|
|
696
|
-
#
|
|
857
|
+
# Position-preserving, not appended at the end — the exported IR
|
|
697
858
|
# is array-order-sensitive (attributes carry their own declared
|
|
698
859
|
# order onto the wire), so an append's fields are resolved as
|
|
699
|
-
#
|
|
700
|
-
# reinserted at whichever position the group's leftmost
|
|
701
|
-
#
|
|
860
|
+
# one contiguous group, in the mutation's own hash order,
|
|
861
|
+
# reinserted at whichever position the group's leftmost still-
|
|
862
|
+
# declared member already occupies (or the end, if every member
|
|
702
863
|
# of the group is resolved). A plain `attributes << owner_attr`
|
|
703
864
|
# here would only ever reproduce the original order when the
|
|
704
865
|
# missing field happened to already be last — real, live
|
|
705
866
|
# evidence: `Keyword#was`/`Argument#variadic` (both genuinely
|
|
706
867
|
# last in their own append hash) round-tripped correctly under
|
|
707
|
-
# the naive append; every
|
|
868
|
+
# the naive append; every other field in the same hash did not,
|
|
708
869
|
# caught by this codemod's own reboot-and-diff safety net rather
|
|
709
870
|
# than silently landing wrong.
|
|
710
|
-
# `anchor` is a snapshot of `present`'s position, taken
|
|
871
|
+
# `anchor` is a snapshot of `present`'s position, taken before
|
|
711
872
|
# `attributes.reject!` mutates the array below it — see this
|
|
712
873
|
# method's own header comment for why: a naive append-at-end
|
|
713
874
|
# silently scrambled real corpus field order (Keyword#was/
|
|
@@ -724,7 +885,8 @@ module Hecks
|
|
|
724
885
|
return if self_ref_fields.empty?
|
|
725
886
|
|
|
726
887
|
present = self_ref_fields.filter_map { |field| attributes.find { |attr| attr.name == field } }
|
|
727
|
-
|
|
888
|
+
# Already fully declared — nothing to resolve.
|
|
889
|
+
return if present.size == self_ref_fields.size
|
|
728
890
|
|
|
729
891
|
anchor = present.empty? ? attributes.length : present.map { |attr| attributes.index(attr) }.min
|
|
730
892
|
attributes.reject! { |attr| present.include?(attr) }
|
|
@@ -735,7 +897,7 @@ module Hecks
|
|
|
735
897
|
attributes.insert(anchor, *group)
|
|
736
898
|
end
|
|
737
899
|
|
|
738
|
-
# The owner's own
|
|
900
|
+
# The owner's own list attribute names its element type as text
|
|
739
901
|
# (`Attribute#type`, unwrapped from `list_of(...)` at declare
|
|
740
902
|
# time) — resolved against `@owner_constructs` (the owner's own
|
|
741
903
|
# value objects and entities, the only two kinds an element can
|
|
@@ -749,7 +911,7 @@ module Hecks
|
|
|
749
911
|
@owner_constructs.find { |construct| construct.hecks_name.to_s == list_attr.type.to_s }
|
|
750
912
|
end
|
|
751
913
|
|
|
752
|
-
#
|
|
914
|
+
# Legacy — see `then_set`'s own comment. The original implementation,
|
|
753
915
|
# verbatim: `from:` still a synonym for `to:`, no omittable-`to:`
|
|
754
916
|
# shorthand, no refusal for the redundant `to: target` spelling —
|
|
755
917
|
# frozen era text was minted under this reading, and a legacy
|
|
@@ -780,11 +942,11 @@ module Hecks
|
|
|
780
942
|
@mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
|
|
781
943
|
end
|
|
782
944
|
|
|
783
|
-
# `append:`
|
|
945
|
+
# `append:` normally binds several fields at once (`append: {
|
|
784
946
|
# name: :name, amount: :amount }`) — `Mutation#appended_fields`/
|
|
785
947
|
# `MutationApplier#appended`/the meta-validator Judge's own
|
|
786
948
|
# `mutation_rows` all read `mutation.source` as a Hash
|
|
787
|
-
# unconditionally. A
|
|
949
|
+
# unconditionally. A bare value (`append: :single_field`, or any
|
|
788
950
|
# non-Hash literal) is the one-field shorthand: exactly what an
|
|
789
951
|
# explicit `append: { value: :single_field }` would have meant,
|
|
790
952
|
# named the same way a single-field value object's own implicit
|