hecks 2.8.0 → 2.9.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/exe/hecks +4 -6
- data/lib/hecks/adapters/driven/claude_code.rb +4 -104
- data/lib/hecks/adapters/driven/d1.rb +44 -237
- data/lib/hecks/adapters/driven/folder.rb +17 -76
- data/lib/hecks/adapters/driven/google_authentication.rb +15 -49
- data/lib/hecks/adapters/driven/governance_authorization.rb +23 -70
- data/lib/hecks/adapters/driven/heki/journal.rb +8 -41
- data/lib/hecks/adapters/driven/heki/saga_store.rb +12 -54
- data/lib/hecks/adapters/driven/heki/snapshot.rb +4 -12
- data/lib/hecks/adapters/driven/heki.rb +13 -42
- data/lib/hecks/adapters/driven/identity_registry.rb +3 -14
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +9 -26
- data/lib/hecks/adapters/driven/in_process_key_vault.rb +2 -14
- data/lib/hecks/adapters/driven/lambda/client.rb +10 -51
- data/lib/hecks/adapters/driven/lambda.rb +10 -84
- data/lib/hecks/adapters/driven/local_storage.rb +5 -40
- data/lib/hecks/adapters/driven/memory.rb +17 -98
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +5 -21
- data/lib/hecks/adapters/driven/postgres/codec.rb +6 -28
- data/lib/hecks/adapters/driven/postgres/outbox.rb +3 -12
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +4 -30
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres.rb +145 -134
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +7 -30
- data/lib/hecks/adapters/driven/sql_query_builder.rb +21 -121
- data/lib/hecks/adapters/driven/sqlite/codec.rb +8 -40
- data/lib/hecks/adapters/driven/sqlite/projection.rb +26 -82
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +40 -84
- data/lib/hecks/adapters/driven/sqlite.rb +128 -65
- data/lib/hecks/adapters/driven/tenant_provisioner.rb +5 -48
- data/lib/hecks/adapters/driven.rb +5 -36
- data/lib/hecks/adapters/driving/github_webhook.rb +8 -76
- data/lib/hecks/behaviors/dsl.rb +7 -25
- data/lib/hecks/behaviors/expectations.rb +31 -141
- data/lib/hecks/behaviors/ir.rb +7 -20
- data/lib/hecks/behaviors/rspec.rb +6 -17
- data/lib/hecks/behaviors/runner.rb +10 -34
- data/lib/hecks/behaviors.rb +4 -8
- data/lib/hecks/bluebook/aggregate.rb +9 -75
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +9 -38
- data/lib/hecks/bluebook/assembly/build.rb +4 -15
- data/lib/hecks/bluebook/assembly/contract.rb +15 -136
- data/lib/hecks/bluebook/assembly/contracts.rb +54 -174
- data/lib/hecks/bluebook/assembly/marks.rb +31 -223
- data/lib/hecks/bluebook/assembly/specializer.rb +6 -52
- data/lib/hecks/bluebook/assembly.rb +6 -45
- data/lib/hecks/bluebook/attribute.rb +12 -61
- data/lib/hecks/bluebook/behaviour/aggregate.rb +10 -39
- data/lib/hecks/bluebook/behaviour/attribute.rb +3 -14
- data/lib/hecks/bluebook/behaviour/chapter.rb +14 -42
- data/lib/hecks/bluebook/behaviour/command.rb +14 -66
- data/lib/hecks/bluebook/behaviour/domain_port.rb +4 -20
- data/lib/hecks/bluebook/behaviour/entity.rb +7 -27
- data/lib/hecks/bluebook/behaviour/hexagon.rb +23 -41
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +15 -40
- data/lib/hecks/bluebook/behaviour/policy.rb +23 -52
- data/lib/hecks/bluebook/behaviour/process_manager.rb +30 -57
- data/lib/hecks/bluebook/behaviour/read_model.rb +25 -31
- data/lib/hecks/bluebook/behaviour/traits.rb +27 -64
- data/lib/hecks/bluebook/behaviour/value_object.rb +5 -24
- data/lib/hecks/bluebook/capabilities.rb +17 -49
- data/lib/hecks/bluebook/chapter.rb +10 -48
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +14 -63
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +50 -229
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +50 -377
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +33 -204
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +16 -69
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +86 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +56 -170
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +1 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +74 -546
- data/lib/hecks/bluebook/dsl/const_shim.rb +8 -41
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +12 -87
- data/lib/hecks/bluebook/dsl/entity_builder.rb +44 -330
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +20 -269
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +26 -194
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +13 -88
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +6 -20
- data/lib/hecks/bluebook/dsl/policy_builder.rb +16 -88
- data/lib/hecks/bluebook/dsl/port_builder.rb +2 -6
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +22 -96
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +31 -184
- data/lib/hecks/bluebook/dsl/query_builder.rb +11 -42
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +75 -201
- data/lib/hecks/bluebook/dsl/rule_reference.rb +22 -163
- data/lib/hecks/bluebook/dsl/translation_builder.rb +28 -114
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +10 -124
- data/lib/hecks/bluebook/dsl/word_gate.rb +16 -162
- data/lib/hecks/bluebook/dsl/world_builder.rb +19 -111
- data/lib/hecks/bluebook/entity.rb +13 -71
- data/lib/hecks/bluebook/expression/ast_json.rb +24 -190
- data/lib/hecks/bluebook/expression/ast_reader.rb +12 -54
- data/lib/hecks/bluebook/expression/canonical_form.rb +11 -88
- data/lib/hecks/bluebook/expression/evaluator.rb +47 -295
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +30 -147
- data/lib/hecks/bluebook/expression/resolver.rb +37 -660
- data/lib/hecks/bluebook/hexagon.rb +5 -28
- data/lib/hecks/bluebook/lifecycle.rb +5 -16
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -7
- data/lib/hecks/bluebook/meta_validator/judge.rb +111 -361
- data/lib/hecks/bluebook/meta_validator/plan.rb +33 -162
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -7
- data/lib/hecks/bluebook/meta_validator/readings.rb +56 -342
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +24 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +43 -305
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +79 -349
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +100 -597
- data/lib/hecks/bluebook/pattern_subset.rb +11 -69
- data/lib/hecks/bluebook/process_manager.rb +13 -83
- data/lib/hecks/bluebook/project_loader.rb +3 -39
- data/lib/hecks/bluebook/project_register.rb +12 -60
- data/lib/hecks/bluebook/query.rb +9 -48
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +8 -98
- data/lib/hecks/bluebook/synthesizer.rb +13 -62
- data/lib/hecks/bluebook/translation.rb +26 -62
- data/lib/hecks/bluebook/value_object.rb +11 -36
- data/lib/hecks/bluebook.rb +5 -22
- data/lib/hecks/cache_dir.rb +4 -23
- data/lib/hecks/cli/document.rb +3 -21
- data/lib/hecks/cli/ir.rb +3 -5
- data/lib/hecks/cli/mcp.rb +2 -3
- data/lib/hecks/cli/mcp_door.rb +19 -114
- data/lib/hecks/cli/model_check.rb +18 -85
- data/lib/hecks/cli/project_cli.rb +7 -40
- data/lib/hecks/cli/project_diagrams.rb +0 -7
- data/lib/hecks/cli/run.rb +12 -58
- data/lib/hecks/cli/smoke_test.rb +2 -16
- data/lib/hecks/cli/stores.rb +1 -2
- data/lib/hecks/cli.rb +18 -24
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/deploy/bluebook/deploy.bluebook +33 -142
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +2 -9
- data/lib/hecks/embryonaut_bluebook/lock.rb +3 -35
- data/lib/hecks/embryonaut_bluebook/shape.rb +1 -17
- data/lib/hecks/embryonaut_bluebook/vendor.rb +8 -53
- data/lib/hecks/embryonaut_bluebook/vendor_cli.rb +4 -19
- data/lib/hecks/embryonaut_bluebook.rb +4 -71
- data/lib/hecks/facade/cli_door.rb +24 -106
- data/lib/hecks/facade/cli_runner.rb +28 -172
- data/lib/hecks/facade/command_request.rb +13 -33
- data/lib/hecks/facade/handle.rb +34 -121
- data/lib/hecks/facade/json_door.rb +31 -146
- data/lib/hecks/facade/surface/aggregate_door.rb +39 -104
- data/lib/hecks/facade/surface/chapter.rb +10 -54
- data/lib/hecks/facade/surface.rb +8 -23
- data/lib/hecks/facade.rb +10 -25
- data/lib/hecks/forms/app.rb +15 -70
- data/lib/hecks/forms/command_form_renderer.rb +10 -29
- data/lib/hecks/forms/field_renderer.rb +17 -156
- data/lib/hecks/forms/field_shape.rb +30 -222
- data/lib/hecks/forms/html.rb +12 -47
- data/lib/hecks/forms/index_renderer.rb +1 -4
- data/lib/hecks/forms/page.rb +3 -9
- data/lib/hecks/forms/params.rb +14 -60
- data/lib/hecks/forms/port_argument.rb +2 -19
- data/lib/hecks/forms/query_form_renderer.rb +4 -79
- data/lib/hecks/forms/record_renderer.rb +8 -75
- data/lib/hecks/forms/record_table.rb +7 -50
- data/lib/hecks/forms/reference_options.rb +3 -30
- data/lib/hecks/forms/value_object_shape.rb +8 -35
- data/lib/hecks/forms.rb +5 -33
- data/lib/hecks/fqn.rb +5 -12
- data/lib/hecks/framework/bluebook/compliance.bluebook +16 -65
- data/lib/hecks/framework/bluebook/console_settings.bluebook +47 -134
- data/lib/hecks/framework/bluebook/framework.hecksagon +5 -14
- data/lib/hecks/framework/bluebook/governance.bluebook +13 -34
- data/lib/hecks/framework/bluebook/identity.bluebook +11 -26
- data/lib/hecks/framework/bluebook/privacy.bluebook +8 -33
- data/lib/hecks/framework.rb +11 -80
- data/lib/hecks/freezer.rb +5 -39
- data/lib/hecks/grammar/expression.bluebook +15 -40
- data/lib/hecks/grammar/translation.bluebook +3 -6
- data/lib/hecks/grammar.rb +16 -41
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/language/adapter.bluebook +2 -7
- data/lib/hecks/language/bluebook/aggregate.bluebook +56 -221
- data/lib/hecks/language/bluebook/attaches/paging.bluebook +6 -15
- data/lib/hecks/language/bluebook/bluebook.bluebook +15 -83
- data/lib/hecks/language/bluebook/bluebook.hecksagon +3 -15
- data/lib/hecks/language/bluebook/command.bluebook +27 -144
- data/lib/hecks/language/bluebook/entity.bluebook +24 -97
- data/lib/hecks/language/bluebook/policy.bluebook +16 -67
- data/lib/hecks/language/bluebook/process_manager.bluebook +28 -151
- data/lib/hecks/language/bluebook/projection.bluebook +126 -60
- data/lib/hecks/language/bluebook/query.bluebook +12 -43
- data/lib/hecks/language/bluebook/shape.bluebook +23 -73
- data/lib/hecks/language/bluebook/syntax.bluebook +51 -262
- data/lib/hecks/language/bluebook/vocabulary.bluebook +82 -197
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +4 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +6 -25
- data/lib/hecks/language/hecksagon/port_operation.bluebook +3 -14
- data/lib/hecks/language/oidc.json +35 -0
- data/lib/hecks/language/port.bluebook +4 -17
- data/lib/hecks/language/translation/translation.bluebook +3 -9
- data/lib/hecks/language/translation/translation_aggregate.bluebook +5 -24
- data/lib/hecks/language/world/wiring.bluebook +1 -3
- data/lib/hecks/language/world/world.bluebook +5 -7
- data/lib/hecks/literal.rb +8 -50
- data/lib/hecks/mcp_door_scope.rb +9 -90
- data/lib/hecks/mcp_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/ports/access_control.rb +7 -23
- data/lib/hecks/ports/agent/answers.rb +5 -23
- data/lib/hecks/ports/agent.rb +47 -172
- data/lib/hecks/ports/authentication.rb +12 -39
- data/lib/hecks/ports/authorization.rb +16 -43
- data/lib/hecks/ports/clock.rb +5 -45
- data/lib/hecks/ports/extraction.rb +7 -12
- data/lib/hecks/ports/identity_assignment.rb +8 -29
- data/lib/hecks/ports/identity_generation.rb +6 -29
- data/lib/hecks/ports/identity_resolution.rb +2 -10
- data/lib/hecks/ports/key_vault.rb +9 -45
- data/lib/hecks/ports/persistence/append_only.rb +61 -64
- data/lib/hecks/ports/persistence/binding_policy.rb +4 -14
- data/lib/hecks/ports/persistence/codec_boundary.rb +20 -80
- data/lib/hecks/ports/persistence/null_saga_store.rb +4 -20
- data/lib/hecks/ports/persistence/plugin.rb +13 -42
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +38 -141
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +16 -84
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +3 -22
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +55 -181
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/events.rb +77 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +12 -122
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +28 -128
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +73 -474
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +31 -124
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +45 -219
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +13 -108
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +18 -75
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +9 -49
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +6 -123
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +23 -55
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +14 -72
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +7 -16
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +14 -115
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +127 -554
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +2 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +3 -14
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +13 -58
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +3 -17
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +9 -26
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +9 -25
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -124
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +34 -123
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/renderer.rb +5 -12
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +3 -9
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -14
- data/lib/hecks/ports/persistence/plugins/era.rb +4 -23
- data/lib/hecks/ports/persistence/postgres_dump/connection.rb +7 -23
- data/lib/hecks/ports/persistence/postgres_dump.rb +4 -35
- data/lib/hecks/ports/persistence/remote_runtime.rb +2 -24
- data/lib/hecks/ports/persistence/repository_factory.rb +8 -27
- data/lib/hecks/ports/persistence/state_codec.rb +23 -103
- data/lib/hecks/ports/persistence.rb +7 -14
- data/lib/hecks/ports/projection.rb +42 -84
- data/lib/hecks/ports/query/in_memory.rb +10 -58
- data/lib/hecks/ports/query/ordering.rb +11 -32
- data/lib/hecks/ports/query.rb +14 -37
- data/lib/hecks/ports.rb +2 -8
- data/lib/hecks/projections/bootstrap_table.rb +11 -43
- data/lib/hecks/projections/deploy/fargate/assembly.rb +2 -15
- data/lib/hecks/projections/deploy/fargate/cdn.rb +1 -48
- data/lib/hecks/projections/deploy/fargate/cdn_default.rb +2 -11
- data/lib/hecks/projections/deploy/fargate/check.rb +5 -14
- data/lib/hecks/projections/deploy/fargate/containers.rb +3 -78
- data/lib/hecks/projections/deploy/fargate/extras.rb +3 -44
- data/lib/hecks/projections/deploy/fargate/monitoring.rb +0 -44
- data/lib/hecks/projections/deploy/fargate/settings.rb +2 -54
- data/lib/hecks/projections/deploy/fargate/yaml.rb +13 -58
- data/lib/hecks/projections/deploy/fargate.rb +35 -202
- data/lib/hecks/projections/deploy/lambda.rb +150 -678
- data/lib/hecks/projections/deploy/preview/access_yaml.rb +1 -33
- data/lib/hecks/projections/deploy/preview/containers.rb +6 -111
- data/lib/hecks/projections/deploy/preview/routing_yaml.rb +4 -56
- data/lib/hecks/projections/deploy/preview/script.rb +2 -14
- data/lib/hecks/projections/deploy/preview/settings.rb +3 -33
- data/lib/hecks/projections/deploy/preview/task_yaml.rb +2 -38
- data/lib/hecks/projections/deploy/preview/template.rb +2 -58
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +47 -146
- data/lib/hecks/projections/deploy/smoke.rb +4 -61
- data/lib/hecks/projections/deploy/template_diff/comparer.rb +3 -25
- data/lib/hecks/projections/deploy/template_diff/loader.rb +2 -11
- data/lib/hecks/projections/deploy/template_diff.rb +5 -57
- data/lib/hecks/projections/diagrams.rb +20 -582
- data/lib/hecks/projections/glossary/html.rb +14 -130
- data/lib/hecks/projections/glossary/markdown.rb +8 -69
- data/lib/hecks/projections/glossary/mermaid.rb +9 -66
- data/lib/hecks/projections/glossary/sensitivity.rb +5 -34
- data/lib/hecks/projections/glossary/sentences.rb +14 -159
- data/lib/hecks/projections/glossary.rb +30 -222
- data/lib/hecks/projections/ir.rb +3 -10
- data/lib/hecks/projections/model/deviations.rb +15 -42
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +9 -28
- data/lib/hecks/projections/reference.rb +2 -15
- data/lib/hecks/projections/rust_vocabulary.rb +12 -250
- data/lib/hecks/projections/shape.rb +3 -29
- data/lib/hecks/projections/statements.rb +7 -86
- data/lib/hecks/projections/vocabulary.rb +6 -22
- data/lib/hecks/projections.rb +2 -16
- data/lib/hecks/projector/cli_projector.rb +60 -375
- data/lib/hecks/projector/docs_projector.rb +10 -201
- data/lib/hecks/projector/exporter.rb +42 -134
- data/lib/hecks/projector/ir_projector.rb +2 -9
- data/lib/hecks/projector/narrate_projector.rb +15 -189
- data/lib/hecks/projector/target.rb +13 -76
- data/lib/hecks/projector.rb +30 -121
- data/lib/hecks/query_specification/common/comparators.rb +5 -26
- data/lib/hecks/query_specification/common/comparison.rb +35 -143
- data/lib/hecks/query_specification/common/dsl.rb +8 -24
- data/lib/hecks/query_specification/common/null_policy.rb +20 -70
- data/lib/hecks/query_specification/common/options.rb +2 -6
- data/lib/hecks/query_specification/common/where_clause.rb +2 -10
- data/lib/hecks/query_specification/field_path.rb +37 -77
- data/lib/hecks/query_specification/hop_path.rb +12 -76
- data/lib/hecks/release/gem_pin.rb +2 -26
- data/lib/hecks/release/runner/ci_publisher.rb +3 -6
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/gem_publisher.rb +2 -3
- data/lib/hecks/release/runner/npm_publisher.rb +6 -20
- data/lib/hecks/release/runner/preflight.rb +4 -19
- data/lib/hecks/release/runner/tagger.rb +2 -9
- data/lib/hecks/release/runner.rb +3 -32
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +12 -36
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/aggregate_lock.rb +8 -33
- data/lib/hecks/runtime/boot_gates.rb +3 -18
- data/lib/hecks/runtime/caller.rb +19 -59
- data/lib/hecks/runtime/capability_graph.rb +5 -21
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +16 -78
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +23 -160
- data/lib/hecks/runtime/command_interpreter.rb +124 -306
- data/lib/hecks/runtime/command_rules/admissibility.rb +95 -366
- data/lib/hecks/runtime/command_rules/arithmetic.rb +66 -196
- data/lib/hecks/runtime/command_rules/authorization.rb +13 -45
- data/lib/hecks/runtime/command_rules/emission.rb +8 -21
- data/lib/hecks/runtime/command_rules/references.rb +23 -202
- data/lib/hecks/runtime/dependency_planning.rb +30 -102
- data/lib/hecks/runtime/dispatcher.rb +82 -241
- data/lib/hecks/runtime/entity_element.rb +40 -462
- data/lib/hecks/runtime/entity_interpreter.rb +64 -219
- data/lib/hecks/runtime/errors.rb +32 -115
- data/lib/hecks/runtime/event.rb +3 -25
- data/lib/hecks/runtime/identity.rb +18 -154
- data/lib/hecks/runtime/instance.rb +18 -150
- data/lib/hecks/runtime/interpreting.rb +11 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +48 -186
- data/lib/hecks/runtime/outbox.rb +38 -261
- data/lib/hecks/runtime/policy_interpreter.rb +38 -200
- data/lib/hecks/runtime/port_operation_interpreter.rb +26 -87
- data/lib/hecks/runtime/query_interpreter.rb +45 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/read_model_interpreter.rb +117 -208
- data/lib/hecks/runtime/rebuild_sweep.rb +8 -54
- data/lib/hecks/runtime/reference_hop.rb +8 -81
- data/lib/hecks/runtime/refusal_wording.rb +8 -89
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +52 -225
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +99 -244
- data/lib/hecks/runtime/remote_dispatcher.rb +37 -152
- data/lib/hecks/runtime/routing.rb +4 -15
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +14 -61
- data/lib/hecks/runtime/saga_interpreter.rb +79 -263
- data/lib/hecks/runtime/saga_pending_dispatch.rb +2 -43
- data/lib/hecks/runtime/tenant_check.rb +6 -68
- data/lib/hecks/runtime/tenant_scope.rb +3 -28
- data/lib/hecks/runtime/value/admission.rb +16 -67
- data/lib/hecks/runtime/value/coercion.rb +155 -641
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value.rb +12 -84
- data/lib/hecks/runtime.rb +17 -65
- data/lib/hecks/storehouse.rb +141 -568
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +8 -32
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/vendoring/git_environment.rb +2 -8
- data/lib/hecks/vendoring/git_source.rb +4 -10
- data/lib/hecks/vendoring.rb +7 -46
- data/lib/hecks/version.rb +3 -14
- data/lib/hecks.rb +26 -81
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +8 -46
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +11 -80
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +10 -99
- metadata +3 -2
|
@@ -2,11 +2,8 @@ require_relative "word_gate"
|
|
|
2
2
|
module Hecks
|
|
3
3
|
module Bluebook
|
|
4
4
|
module DSL
|
|
5
|
-
# The `command "Name" do ... end` receiver
|
|
6
|
-
#
|
|
7
|
-
# `corrects` declarations, resolves implicit attributes a bare `sets
|
|
8
|
-
# :field` or `append:` self-reference imports from the owner
|
|
9
|
-
# (`#resolve_implicit_attributes!`), and builds the final `Command`.
|
|
5
|
+
# The `command "Name" do ... end` receiver: collects role/goal/given/ensures/sets/
|
|
6
|
+
# emits/delegates_to/corrects declarations and builds the final `Command`.
|
|
10
7
|
class CommandBuilder
|
|
11
8
|
GRAMMAR_CONTEXT = "Command".freeze
|
|
12
9
|
|
|
@@ -14,34 +11,11 @@ module Hecks
|
|
|
14
11
|
include RuleReference
|
|
15
12
|
include WordGate
|
|
16
13
|
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# from Ruby's own nil/false. `then_set`'s original default (`to:
|
|
20
|
-
# nil`) could not tell "not given" apart from "given, and the
|
|
21
|
-
# value is false" — `to || from` silently treats `to: false` the
|
|
22
|
-
# same as an absent `to:` and falls through to `from` (also
|
|
23
|
-
# absent), so `then_set :accepted, to: false` raised "names no
|
|
24
|
-
# operation" for the one value most likely to be written that way
|
|
25
|
-
# (a boolean flip). Confirmed real, live: an external project's
|
|
26
|
-
# dream_interpretation.bluebook (`then_set :accepted, to: false`)
|
|
27
|
-
# and transparency.bluebook (`then_set :always, to: false`). TODO
|
|
28
|
-
# upstream via bin/evolve.
|
|
14
|
+
# Sentinel for "this keyword was never passed" — distinct from Ruby's own
|
|
15
|
+
# nil/false, so `to: false` doesn't get treated as absent.
|
|
29
16
|
UNSET = Object.new.freeze
|
|
30
17
|
private_constant :UNSET
|
|
31
18
|
|
|
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
|
|
45
19
|
def initialize(name, owner: nil, from: nil, named_givens: {}, owner_attributes: [], owner_constructs: [],
|
|
46
20
|
entity_shared_givens: {})
|
|
47
21
|
@name = name
|
|
@@ -70,24 +44,8 @@ module Hecks
|
|
|
70
44
|
end
|
|
71
45
|
end
|
|
72
46
|
|
|
73
|
-
# A command carries one responsibility role — the language never
|
|
74
|
-
# declared an or between two roles, so a second `role` call would
|
|
75
|
-
# otherwise silently win while the first still looked declared,
|
|
76
|
-
# exactly the failure mode `reference_to`'s own duplicate guard
|
|
77
|
-
# (below) already exists to prevent for a command's root.
|
|
78
|
-
#
|
|
79
47
|
# Sets the command's one responsibility role, refusing a second declaration.
|
|
80
48
|
#
|
|
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`
|
|
84
|
-
# already set), not a pure function of the argument's own value —
|
|
85
|
-
# a genuinely different shape than a plain fill, so it stays
|
|
86
|
-
# hand-written and is reached through `calls:` like `attribute`
|
|
87
|
-
# was in slice 3. Bootstrap-reachable (every self-hosted command
|
|
88
|
-
# declares a role), so also named in
|
|
89
|
-
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
90
|
-
#
|
|
91
49
|
# @param value [String, Symbol] the role's name
|
|
92
50
|
# @return [Object] `value` as stored
|
|
93
51
|
# @raise [Bluebook::DSL::Malformed] if a role is already declared
|
|
@@ -110,14 +68,6 @@ module Hecks
|
|
|
110
68
|
|
|
111
69
|
# Names where a concept adopted from a canonical source came from.
|
|
112
70
|
#
|
|
113
|
-
# See `AggregateBuilder#provenance_impl`'s own comment — identical shape,
|
|
114
|
-
# one level down.
|
|
115
|
-
#
|
|
116
|
-
# Answers the `provenance` word through the table's `calls:`
|
|
117
|
-
# column — item #13's full metaprogrammed
|
|
118
|
-
# dispatch (slice 4c). Bootstrap-reachable, in
|
|
119
|
-
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
120
|
-
#
|
|
121
71
|
# @param from [Object] the canonical source, captured exactly as written
|
|
122
72
|
# @return [Object] `from` as stored
|
|
123
73
|
def provenance_impl(from:) = @provenance = from
|
|
@@ -125,17 +75,6 @@ module Hecks
|
|
|
125
75
|
# Declares the aggregate this command acts on (with no `as:`), or a cross-reference to
|
|
126
76
|
# another aggregate (with `as:`).
|
|
127
77
|
#
|
|
128
|
-
# `optional:` rides here as well as on a plain attribute : `as:` makes a
|
|
129
|
-
# reference into a named argument, and a named argument is exactly the kind
|
|
130
|
-
# of fact that may or may not be given. The meta-domain's Verb.Declare
|
|
131
|
-
# points at the Entity a command belongs to — and most commands belong to no
|
|
132
|
-
# entity at all.
|
|
133
|
-
#
|
|
134
|
-
# Answers the `reference_to` word through the table's `calls:`
|
|
135
|
-
# column — item #13's full metaprogrammed
|
|
136
|
-
# dispatch (slice 4b). Bootstrap-reachable, in
|
|
137
|
-
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
138
|
-
#
|
|
139
78
|
# @param type [Module, Symbol, String] the referenced aggregate, written as a bare
|
|
140
79
|
# constant
|
|
141
80
|
# @param as [Symbol, nil] the attribute's name for a cross-reference; nil declares the
|
|
@@ -147,16 +86,10 @@ module Hecks
|
|
|
147
86
|
# cross-reference) `as` is already declared
|
|
148
87
|
def reference_to_impl(type, as: nil, optional: false)
|
|
149
88
|
demodulised = Naming.demodulise(type)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
# `reference_to Aggregate, as: :points_at` on a command owned by Aggregate
|
|
155
|
-
# read as a second self-reference and was refused as naming two roots,
|
|
156
|
-
# which is how the meta-domain's own Aggregate.Reference could not say the
|
|
157
|
-
# one thing it exists to say. Transfer has said
|
|
158
|
-
# `reference_to Account, as: :source` for as long as banking has existed;
|
|
159
|
-
# this is the same sentence when the target happens to be the owner.
|
|
89
|
+
|
|
90
|
+
# `as:` names an attribute, not "the root I act on" — without this check, a
|
|
91
|
+
# command referencing its own aggregate type via `as:` would misread as a
|
|
92
|
+
# second self-reference and be refused.
|
|
160
93
|
return cross_reference(demodulised, as, optional: optional) if as || demodulised.to_s != @owner.to_s
|
|
161
94
|
|
|
162
95
|
if @references
|
|
@@ -180,21 +113,7 @@ module Hecks
|
|
|
180
113
|
# Declares a precondition this command requires, or references one the owning aggregate
|
|
181
114
|
# (or a sibling piece) already declared.
|
|
182
115
|
#
|
|
183
|
-
# No block
|
|
184
|
-
# 0025 — "a precondition shared across commands is declared
|
|
185
|
-
# once. An aggregate declares it by name and commands
|
|
186
|
-
# reference it"): the same word, the same shape
|
|
187
|
-
# (`AggregateBuilder#given_impl`, block required there), so naming a
|
|
188
|
-
# precondition back is spelled exactly like declaring one would
|
|
189
|
-
# be, minus the block — one idea, one word, never a second
|
|
190
|
-
# spelling ("requires"/"precondition") for "use the one already
|
|
191
|
-
# named". Resolved against whatever the owning aggregate has
|
|
192
|
-
# declared so far — see `AggregateBuilder#command_impl`'s own
|
|
193
|
-
# comment on why that means declaration order matters here.
|
|
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.
|
|
116
|
+
# No block given means "use the one already declared" rather than a fresh rule (ADR 0025).
|
|
198
117
|
#
|
|
199
118
|
# @param description [String] the rule's description; also the name the owning
|
|
200
119
|
# aggregate's rule is referenced by when no block is given
|
|
@@ -206,23 +125,15 @@ module Hecks
|
|
|
206
125
|
def given_impl(description, &predicate)
|
|
207
126
|
return reference_named_given(description) unless predicate
|
|
208
127
|
|
|
209
|
-
# moved to the language: given "a rule says what it means", on Verb.Rule
|
|
210
|
-
|
|
211
128
|
@givens << build_rule(Given, description, predicate, owner_name: @name, word: "given",
|
|
212
129
|
extraction_failure: "its source could not be read, so no other runtime could ever evaluate it")
|
|
213
130
|
end
|
|
214
131
|
|
|
215
132
|
private
|
|
216
133
|
|
|
217
|
-
#
|
|
218
|
-
#
|
|
219
|
-
#
|
|
220
|
-
# declaration under the same aggregate (`@entity_shared_givens`,
|
|
221
|
-
# threaded from `EntityBuilder#given`'s own write-through).
|
|
222
|
-
# "customer is active" declared once on `Visit`, referenced bare
|
|
223
|
-
# by `KeyIssuance.Return` — two different pieces, same aggregate,
|
|
224
|
-
# same predicate — is exactly the shape this second pool exists
|
|
225
|
-
# for.
|
|
134
|
+
# Checks the command's own owner first, then — for a piece-owned command only —
|
|
135
|
+
# a sibling piece's entity-level givens, since two pieces under the same
|
|
136
|
+
# aggregate can share a precondition declared on just one of them.
|
|
226
137
|
def reference_named_given(description)
|
|
227
138
|
verify_resolves_via!("given", "Command", "hash_chain")
|
|
228
139
|
named = resolve_hash_chain([@named_givens, @entity_shared_givens], description) ||
|
|
@@ -239,13 +150,7 @@ module Hecks
|
|
|
239
150
|
public
|
|
240
151
|
|
|
241
152
|
# 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,
|
|
245
|
-
# evaluated against the settled record with `old` naming the state
|
|
246
|
-
# as it stood before them: `ensures("...") { old.balance.cents ==
|
|
247
|
-
# balance.cents + amount.cents }`. Same extraction, same Rule
|
|
248
|
-
# shape, same refusal form; `EnsuresNotMet` instead of `GivenNotMet`.
|
|
153
|
+
# mutations apply; `old` names the pre-mutation state.
|
|
249
154
|
#
|
|
250
155
|
# @param description [String] the rule's description
|
|
251
156
|
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
@@ -256,121 +161,26 @@ module Hecks
|
|
|
256
161
|
extraction_failure: "a postcondition is carried as text, and this one has none")
|
|
257
162
|
end
|
|
258
163
|
|
|
259
|
-
# `sets`
|
|
260
|
-
#
|
|
261
|
-
#
|
|
262
|
-
# era keeps booting, which is the whole point of the rename column.
|
|
263
|
-
#
|
|
264
|
-
# Vendored addition, not (yet) upstream hecks: `then_set
|
|
265
|
-
# :target, from: :source_field` (an external project, found
|
|
266
|
-
# live in body/doctor/bluebook/doctor.bluebook) -- semantically
|
|
267
|
-
# identical to `to:` (copy this argument/field into the target),
|
|
268
|
-
# different word. TODO upstream via bin/evolve (migration plan
|
|
269
|
-
# task 7): decide whether `from:` or `to:` becomes the canonical
|
|
270
|
-
# spelling.
|
|
271
|
-
#
|
|
272
|
-
# Vendored addition, not (yet) upstream hecks (migration plan
|
|
273
|
-
# task 4, i106 in-DSL math): `multiply:`/`clamp:` -- per-tick organ
|
|
274
|
-
# math (an external project's organ bluebook: strength decays ×0.98,
|
|
275
|
-
# weight/strength clamp to [0, 1]), moved into the bluebook itself
|
|
276
|
-
# from shell-side awk. `multiply:` mirrors
|
|
277
|
-
# increment/decrement's shape exactly (a Numeric amount, applied
|
|
278
|
-
# by CommandRules::Arithmetic -- see that file's own comment on
|
|
279
|
-
# the matching Float-support widening this required). `clamp:`
|
|
280
|
-
# is a genuinely different shape -- its source is always a literal
|
|
281
|
-
# `[min, max]` pair, never an argument reference, and it bounds
|
|
282
|
-
# the current value rather than combining it with an amount -- so
|
|
283
|
-
# it does not reuse `arithmetic`/`arithmetic_value_object` at all;
|
|
284
|
-
# see MutationApplier#apply's own `:clamp` branch.
|
|
285
|
-
#
|
|
286
|
-
# `remove:` -- vendored addition, not (yet) upstream hecks
|
|
287
|
-
# (migration plan task 4): the list-removal counterpart to
|
|
288
|
-
# `append:` (plan.bluebook's own RemoveDependency/DeactivateSprint
|
|
289
|
-
# commands: "the runtime list-remove primitive (then_set remove:)
|
|
290
|
-
# drops it from the list element-wise, with no read-modify-write
|
|
291
|
-
# -- so a concurrent Add can never be lost"). Matches an element
|
|
292
|
-
# by value equality against `mutation.source` (resolved and
|
|
293
|
-
# Value-coerced the same way increment/decrement/multiply already
|
|
294
|
-
# coerce their own amount -- see MutationApplier#removed).
|
|
295
|
-
#
|
|
296
|
-
# `sets :field, true` -- vendored addition, not (yet) upstream
|
|
297
|
-
# hecks (migration plan task 8): a bare positional literal
|
|
298
|
-
# instead of `to:` -- 14 occurrences across hecks_nursury
|
|
299
|
-
# (oceanography.bluebook/volcanology.bluebook and others),
|
|
300
|
-
# always a boolean shorthand (`sets :deployed, true`, never
|
|
301
|
-
# a string/number positional -- checked directly, zero non-
|
|
302
|
-
# boolean occurrences of the bare-positional-second-arg shape
|
|
303
|
-
# anywhere in the corpus). Folded into `to:` itself rather than
|
|
304
|
-
# given its own mutation op -- semantically identical, same
|
|
305
|
-
# `UNSET`-sentinel discipline the `to: false` fix already
|
|
306
|
-
# established (a positional `false` must read as "set to
|
|
307
|
-
# false," not "absent," same as the keyword form). Only applied
|
|
308
|
-
# when `to:` itself was not also given, so an explicit `to:`
|
|
309
|
-
# keyword always wins over a stray positional.
|
|
310
|
-
#
|
|
311
|
-
# `sets` is the word (ADR 0025 reverts `then_set` — the grammar
|
|
312
|
-
# already declared `sets`, `was: "then_set"`, and 143 of 143 live
|
|
313
|
-
# call sites are `then_set`, so this method was the one thing
|
|
314
|
-
# still backwards). `to:` is omittable when it would only repeat
|
|
315
|
-
# the target — `sets :number` alone already means `to: :number`
|
|
316
|
-
# — and the redundant explicit spelling is refused outright
|
|
317
|
-
# (principle 1, "one idea, one spelling": `sets :number, to:
|
|
318
|
-
# :number` says nothing `sets :number` doesn't). `from:` — a
|
|
319
|
-
# pure synonym for `to:` the language's own refusal message had
|
|
320
|
-
# already forgotten about — is gone; write `to:`.
|
|
321
|
-
# The op each kwarg selects — `spec/syntax_conformance_spec.rb`'s own
|
|
322
|
-
# "selects the same op..." check holds this constant to the self-
|
|
323
|
-
# hosted table's own `Argument#selects` column (`"op=set"`,
|
|
324
|
-
# `"op=append"`, ...; whole-project table-unification survey, item
|
|
325
|
-
# #1), the same field `rust/parser/src/keywords.rs`'s `ArgumentRow.
|
|
326
|
-
# selects` already carries. `to:` is the one kwarg whose own name
|
|
327
|
-
# differs from the op it selects — every other kwarg selects the op
|
|
328
|
-
# of its own name.
|
|
164
|
+
# Maps each `sets` kwarg to the mutation op it selects. `to:` is the one kwarg
|
|
165
|
+
# whose own name differs from the op it selects (`:set`); every other kwarg
|
|
166
|
+
# selects the op of its own name.
|
|
329
167
|
KWARG_TO_OP = { to: :set, append: :append, increment: :increment, decrement: :decrement,
|
|
330
168
|
multiply: :multiply, clamp: :clamp, remove: :remove }.freeze
|
|
331
169
|
|
|
332
|
-
# Declares one mutation this command applies to `target`, its op selected by
|
|
333
|
-
# single keyword
|
|
334
|
-
#
|
|
335
|
-
# Answers the `sets` word through the table's `calls:` column —
|
|
336
|
-
# item #13's full metaprogrammed dispatch
|
|
337
|
-
# (slice 4c). The `KWARG_TO_OP` op-selection mapping is already
|
|
338
|
-
# table-verified (`Argument#selects`), but the REST (`UNSET`-
|
|
339
|
-
# sentinel discipline, redundant-spelling refusal, omittable-
|
|
340
|
-
# `to:` fallback, one-mutation-only refusal, the position-
|
|
341
|
-
# preserving `resolve_*!` reinsertion) is keyed off runtime
|
|
342
|
-
# state, not a pure function of a static row — stays hand-
|
|
343
|
-
# written, reached through `calls:` like everything else here.
|
|
344
|
-
# Bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
170
|
+
# Declares one mutation this command applies to `target`, its op selected by
|
|
171
|
+
# whichever single keyword names a source; omitting all of them means `to: target`.
|
|
345
172
|
#
|
|
346
|
-
# @param
|
|
347
|
-
# @param
|
|
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
|
|
173
|
+
# @param to [Object] a Symbol equal to `target` is redundant and refused
|
|
174
|
+
# @param clamp [Array(Object, Object)] the `[min, max]` pair to bound the current value to
|
|
360
175
|
# @return [void]
|
|
361
176
|
# @raise [Bluebook::DSL::Malformed] if `to:` redundantly repeats `target`, or more than
|
|
362
|
-
# one
|
|
363
|
-
# the folded `positional_to`) is given
|
|
177
|
+
# one op-selecting keyword is given
|
|
364
178
|
def sets_impl(target, positional_to = UNSET, to: UNSET, append: UNSET,
|
|
365
179
|
increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
|
|
366
|
-
# moved to the language: given "a mutation names a target", on Verb.Change
|
|
367
|
-
|
|
368
180
|
to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
|
|
369
181
|
|
|
370
|
-
#
|
|
371
|
-
#
|
|
372
|
-
# shorthand, a String, ...) is a value, never a redundant name,
|
|
373
|
-
# so it never has `.to_sym` to compare in the first place.
|
|
182
|
+
# Only a Symbol can repeat the target's name; a literal value (`to: false`,
|
|
183
|
+
# a String, ...) never has `.to_sym` to compare in the first place.
|
|
374
184
|
if to.is_a?(Symbol) && to == target.to_sym
|
|
375
185
|
raise Malformed,
|
|
376
186
|
"#{@name}'s sets :#{target}, to: :#{target} repeats the target — " \
|
|
@@ -382,9 +192,7 @@ module Hecks
|
|
|
382
192
|
.reject { |_, source| source.equal?(UNSET) }
|
|
383
193
|
named = given.to_h { |kwarg, source| [KWARG_TO_OP.fetch(kwarg), source] }
|
|
384
194
|
|
|
385
|
-
#
|
|
386
|
-
# bare `to:` — so this is `sets :field` alone, which means
|
|
387
|
-
# exactly what the redundant, refused spelling above would have.
|
|
195
|
+
# No operation named at all (not even bare `to:`) means `sets :field` alone.
|
|
388
196
|
named = { set: target } if named.empty?
|
|
389
197
|
|
|
390
198
|
if named.size > 1
|
|
@@ -397,23 +205,10 @@ module Hecks
|
|
|
397
205
|
@mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
|
|
398
206
|
end
|
|
399
207
|
|
|
400
|
-
# Refuses the
|
|
401
|
-
#
|
|
208
|
+
# Refuses the `then_set` spelling outside shadow-parsing; while shadow-parsing frozen
|
|
209
|
+
# era text, reads it via `legacy_then_set` instead.
|
|
402
210
|
#
|
|
403
|
-
#
|
|
404
|
-
# text still parses under it; live source refuses it,
|
|
405
|
-
# naming the replacement.
|
|
406
|
-
#
|
|
407
|
-
# Answers the `then_set` word — item #13's full metaprogrammed
|
|
408
|
-
# dispatch (slice 5). Not bootstrap-reachable. Has its own
|
|
409
|
-
# dedicated, `status: "deprecated"` Keyword row (syntax.bluebook)
|
|
410
|
-
# rather than living only as `sets`'s own `was:` — see that
|
|
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
|
|
211
|
+
# @param target [Symbol, String] the field this mutation writes
|
|
417
212
|
# @return [void]
|
|
418
213
|
# @raise [Bluebook::DSL::Malformed] outside shadow-parsing, always; under
|
|
419
214
|
# shadow-parsing, if `legacy_then_set` names no operation or more than one
|
|
@@ -423,124 +218,26 @@ module Hecks
|
|
|
423
218
|
raise Malformed, "#{@name}'s then_set is gone — sets is the word now"
|
|
424
219
|
end
|
|
425
220
|
|
|
426
|
-
# Declares one event this command announces to the outside.
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
# language/bluebook/behavior.bluebook, on Command.Announce — and MetaValidator is what
|
|
430
|
-
# enforces it. This is the first rule to move across rather than be
|
|
431
|
-
# duplicated : delete the declaration and an unnamed event is accepted,
|
|
432
|
-
# which is what makes the meta-domain load-bearing rather than decorative.
|
|
433
|
-
#
|
|
434
|
-
# Bare constant accepted (ADR 0025, S6 — "events first-class"),
|
|
435
|
-
# `emits Account::AccountFrozen`, resolved through `ConstShim` the
|
|
436
|
-
# same way `trigger`/`dispatch` already resolve a command
|
|
437
|
-
# reference (`Naming.event_ref`, that method's own header). Not
|
|
438
|
-
# yet a required spelling, deliberately, unlike `trigger`/
|
|
439
|
-
# `dispatch`'s own quoted-text refusal: those were safe to refuse
|
|
440
|
-
# only because command references are already 100% migrated
|
|
441
|
-
# across the live corpus (verified 2026-08-27) — `emits`/`on`
|
|
442
|
-
# are not, so refusing the quoted form here would break every
|
|
443
|
-
# live `.bluebook` site this pass didn't touch, not just frozen
|
|
444
|
-
# era text `shadow_parse` exists to keep readable. Both forms
|
|
445
|
-
# are accepted in live source until a full corpus migration
|
|
446
|
-
# lands and the same refusal this file's `reference_to`/
|
|
447
|
-
# `trigger_impl` already carry can be added here safely.
|
|
221
|
+
# Declares one event this command announces to the outside. Accepts both quoted
|
|
222
|
+
# text and a bare constant (`emits Account::AccountFrozen`); refusing the quoted
|
|
223
|
+
# form is not yet safe since not every live bluebook has migrated off it.
|
|
448
224
|
#
|
|
449
225
|
# @param event_name [String, Symbol, Module] the event, quoted text or a bare constant
|
|
450
|
-
# such as `Account::AccountFrozen`
|
|
451
226
|
# @return [Array<String>] every event declared so far, this one last
|
|
452
227
|
def emits(event_name)
|
|
453
228
|
@emits << Naming.event_ref(event_name)
|
|
454
229
|
end
|
|
455
230
|
|
|
456
|
-
# The record's own value as a mutation source — `sets :positions,
|
|
457
|
-
# append: { ply: state(:ply), knights: state(:knights) }` copies
|
|
458
|
-
# what the record holds now into the new element; `sets :last,
|
|
459
|
-
# to: state(:current)` copies one field onto another. A bare
|
|
460
|
-
# Symbol always names an argument (`resolve_append_fields!`'s own
|
|
461
|
-
# comment has the full account),
|
|
462
|
-
# so without this a command could not snapshot its own state at
|
|
463
|
-
# all. `Literal::StateRef`'s own comment has the wire spelling.
|
|
464
|
-
#
|
|
465
231
|
# References the record's own current field as a mutation source, as opposed to a
|
|
466
|
-
# bare Symbol, which names an argument.
|
|
232
|
+
# bare Symbol, which names an argument, e.g. `sets :last, to: state(:current)`.
|
|
467
233
|
#
|
|
468
234
|
# @param name [Symbol, String] the field to read from the pre-dispatch record
|
|
469
235
|
# @return [Literal::StateRef] the wrapped reference
|
|
470
236
|
def state(name) = StateRef.new(name.to_sym)
|
|
471
237
|
|
|
472
|
-
# Declares a synchronous, atomic delegation of this command's
|
|
473
|
-
#
|
|
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
|
|
478
|
-
# one. Built because `trigger`/`saga`'s own dispatch (`Dispatcher
|
|
479
|
-
# #reenter`) is a reaction — the triggering command has already
|
|
480
|
-
# committed by the time it runs, and both `PolicyInterpreter#deliver`
|
|
481
|
-
# and `SagaInterpreter#deliver_saga_dispatch` rescue a target's own
|
|
482
|
-
# refusal and record it rather than raising it back to the original
|
|
483
|
-
# caller. That is correct for what those two exist for (an
|
|
484
|
-
# eventually-consistent process that can compensate), and wrong for
|
|
485
|
-
# a caller who needs a synchronous yes/no on whether the thing they
|
|
486
|
-
# asked for actually happened — a chess move's own legality, for
|
|
487
|
-
# instance, checked live building `domain/chess` in a downstream
|
|
488
|
-
# project. `delegates_to` fills exactly that gap: the target
|
|
489
|
-
# entity command's own `given`/`ensures` are enforced as real,
|
|
490
|
-
# unrescued Ruby exceptions, so a refusal deep in the entity's own
|
|
491
|
-
# rules is the delegating command's own refusal too, and nothing
|
|
492
|
-
# from either side is saved unless both sides pass.
|
|
493
|
-
#
|
|
494
|
-
# `target` is always one hop, `"Entity.Command"` — an aggregate
|
|
495
|
-
# names the entity it owns directly, same reach a bare `given`
|
|
496
|
-
# reference already has (see `Knight`'s own comment on this
|
|
497
|
-
# domain's shared givens), not a multi-segment dispatch chain.
|
|
498
|
-
# `with:` resolves the same way `sets ..., append: {...}`'s own
|
|
499
|
-
# field map and a policy's own `trigger ..., with: {...}` already
|
|
500
|
-
# do: each value names one of this command's own declared/implicit
|
|
501
|
-
# arguments, read at dispatch time and handed to the target under
|
|
502
|
-
# its own key.
|
|
503
|
-
#
|
|
504
|
-
# Mutually exclusive with `sets`/`emits` on the same command — a
|
|
505
|
-
# delegating command is a pure passthrough by design (see this
|
|
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
|
|
508
|
-
# `sets`/`emits` produced. Enforced in `build`, once every builder
|
|
509
|
-
# call has already run, so declaration order does not matter.
|
|
510
|
-
#
|
|
511
|
-
# Stored as a mutation, not a new Command field — a real, deliberate
|
|
512
|
-
# choice, not a shortcut. `Command`'s own shape (givens/ensures/
|
|
513
|
-
# mutations/emits/...) is not just Ruby: it round-trips through this
|
|
514
|
-
# language's own self-hosted meta-domain (`Bluebook::MetaValidator`
|
|
515
|
-
# dispatches every declaration into a "Bluebook" domain describing
|
|
516
|
-
# itself, then rebuilds the real runtime graph from what that domain
|
|
517
|
-
# holds — `Hecks.bluebook` registers what `MetaValidator.call`
|
|
518
|
-
# returns, never the builder's own object graph directly, confirmed
|
|
519
|
-
# by reading `meta_validator.rb`'s own `self.call`/`self.hold`).
|
|
520
|
-
# A genuinely new top-level Command field needs the meta-domain's
|
|
521
|
-
# own grammar (`language/bluebook/behavior.bluebook` or wherever
|
|
522
|
-
# Verb.Rule/Ensure/Change live) taught to carry it too — the same
|
|
523
|
-
# scale of change as the real "item #13" migration this file's own
|
|
524
|
-
# comments document throughout. A new mutation op does not: `sets`'s
|
|
525
|
-
# own `mutations:` field is already a fully round-tripped part of
|
|
526
|
-
# that contract (`Assembly::CONTRACTS["Command"].fields[:mutations]`),
|
|
527
|
-
# and an append-shaped mutation already carries a multi-key `fields:`
|
|
528
|
-
# hash the exact shape `with:` needs — so `delegates_to` rides that
|
|
529
|
-
# existing, already-correct wire format under a new `op: :delegate`
|
|
530
|
-
# instead of inventing a parallel one. `MutationOp`'s own closed set
|
|
531
|
-
# (vocabulary.bluebook) gained `"delegate"` alongside `"append"`
|
|
532
|
-
# for exactly this reason, and the three meta-domain touch points
|
|
533
|
-
# that hard-coded `op == "append"` for the multi-binding shape
|
|
534
|
-
# (`meta_validator/readings.rb#mutation_rows`,
|
|
535
|
-
# `meta_validator/shapes.rb#mutation`, `assembly/marks.rb#mutation`)
|
|
536
|
-
# now check for `:delegate` alongside it, each with a comment
|
|
537
|
-
# pointing back here.
|
|
538
|
-
# Answers the `delegates_to` word through the table's `calls:`
|
|
539
|
-
# column — matches `sets_impl`/`given_impl`/`reference_to_impl`'s own
|
|
540
|
-
# convention (language/bluebook/syntax.bluebook's own Keyword row
|
|
541
|
-
# for this word names `calls: "delegates_to_impl"`), the same
|
|
542
|
-
# `word`-vs-`_impl` split every hand-written (not yet item-#13-
|
|
543
|
-
# generic-dispatch-migrated) DSL word here already follows.
|
|
238
|
+
# Declares a synchronous, atomic delegation of this command's dispatch to one nested
|
|
239
|
+
# entity command; unlike `trigger`/`saga`, the target's given/ensures are enforced as
|
|
240
|
+
# real exceptions, so its refusal is the delegating command's own refusal too.
|
|
544
241
|
#
|
|
545
242
|
# @param target [String, Symbol] the delegated command, dotted `"Entity.Command"`
|
|
546
243
|
# @param with [Hash{Symbol => Symbol, Object}] projects this command's own arguments
|
|
@@ -559,57 +256,14 @@ module Hecks
|
|
|
559
256
|
@mutations << Mutation.new(target: target.to_s, op: :delegate, source: with)
|
|
560
257
|
end
|
|
561
258
|
|
|
562
|
-
# Declares that this command amends a past event, rather than rewriting it.
|
|
563
|
-
#
|
|
564
|
-
#
|
|
565
|
-
# answer to "what if this record's history turns out to have been
|
|
566
|
-
# wrong": never rewrite the original event (the log stays exactly
|
|
567
|
-
# what it was), always append a new fact on top. `event` names the
|
|
568
|
-
# event this command corrects; `as:` optionally binds the located
|
|
569
|
-
# instance for a `given`/`ensures` to reference, the same shape
|
|
570
|
-
# `ensures`'s own `old` binding already has; `reason:` is not
|
|
571
|
-
# descriptive-only the way `goal` is — it is carried as data, the
|
|
572
|
-
# one thing an audit trail actually needs ("we corrected this, and
|
|
573
|
-
# here is why"), refused when blank the same way a `given`'s own
|
|
574
|
-
# description is required to say something.
|
|
575
|
-
#
|
|
576
|
-
# Stored as a mutation, not a new Command field — see the
|
|
577
|
-
# KeywordSeed row's own comment (command.bluebook) for why: this
|
|
578
|
-
# is the exact same choice `delegates_to` already made, for the
|
|
579
|
-
# exact same reason. Rides the same multi-binding wire shape
|
|
580
|
-
# `append`/`delegate` use — `as:`/`reason:`/`reverses:` assembled
|
|
581
|
-
# by hand into one `source` hash, the way `sets_impl` assembles up
|
|
582
|
-
# to seven kwargs into one `named` hash above.
|
|
583
|
-
#
|
|
584
|
-
# `reverses: true` names an intent to auto-derive the corrective
|
|
585
|
-
# `sets` from the original event's own mutations, rather than the
|
|
586
|
-
# author writing it — see `AggregateBuilder#seal_correction_targets`,
|
|
587
|
-
# where that derivation actually happens (it needs every sibling
|
|
588
|
-
# command in the aggregate already known, which this builder alone
|
|
589
|
-
# cannot see). Mutually exclusive with an explicit `sets` on the
|
|
590
|
-
# same command — two ways of saying the same thing is exactly the
|
|
591
|
-
# redundancy `sets`'s own omittable-`to:` rule refuses elsewhere.
|
|
592
|
-
#
|
|
593
|
-
# `as:` is always stored as text, never left a bare Symbol —
|
|
594
|
-
# `Mutation#classified_source`/`#appended_fields` (Behaviour::
|
|
595
|
-
# Mutation) classify any bare Symbol field as `kind: "argument"`,
|
|
596
|
-
# meaning "resolve this against one of this command's own declared
|
|
597
|
-
# attributes at dispatch time" (append/delegate's own meaning for a
|
|
598
|
-
# Symbol). `as:` names no such thing — it is a plain label, not yet
|
|
599
|
-
# wired into the expression evaluator (a future round's work, once
|
|
600
|
-
# a real runtime consumer exists) — so coercing it to a String here
|
|
601
|
-
# keeps it out of that machinery entirely rather than silently
|
|
602
|
-
# miscategorised as an unresolvable argument reference.
|
|
259
|
+
# Declares that this command amends a past event, rather than rewriting it. `reverses:
|
|
260
|
+
# true` auto-derives the corrective `sets` from the original event's own mutations,
|
|
261
|
+
# and is mutually exclusive with an explicit `sets` on the same command.
|
|
603
262
|
#
|
|
604
|
-
# @param event [String, Symbol, Module] the event this command corrects
|
|
605
|
-
#
|
|
606
|
-
# @param
|
|
607
|
-
#
|
|
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
|
|
263
|
+
# @param event [String, Symbol, Module] the event this command corrects
|
|
264
|
+
# @param as [Symbol, nil] binds the located instance for a `given`/`ensures` to reference
|
|
265
|
+
# @param reason [String, nil] why the correction is made, carried as audit data; required
|
|
266
|
+
# @param reverses [Boolean] auto-derive the corrective `sets` from the original mutations
|
|
613
267
|
# @return [Array<Bluebook::Mutation>] every mutation declared so far, this one last
|
|
614
268
|
# @raise [Bluebook::DSL::Malformed] if `reason` is nil or blank
|
|
615
269
|
def corrects_impl(event, as: nil, reason: nil, reverses: false)
|
|
@@ -665,15 +319,8 @@ module Hecks
|
|
|
665
319
|
# Evaluates a `command` block against a fresh builder and returns what it built.
|
|
666
320
|
#
|
|
667
321
|
# @param name [String] the command's name
|
|
668
|
-
# @param owner [String, nil] name of the aggregate or entity this command belongs to
|
|
669
322
|
# @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
|
|
670
323
|
# 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
324
|
# @yield the command body, evaluated with the builder as `self`; may be omitted
|
|
678
325
|
# @return [Bluebook::Command] the built command
|
|
679
326
|
# @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
|
|
@@ -688,11 +335,8 @@ module Hecks
|
|
|
688
335
|
|
|
689
336
|
private
|
|
690
337
|
|
|
691
|
-
#
|
|
692
|
-
#
|
|
693
|
-
# written twice has no meaning to give: last-wins would make
|
|
694
|
-
# declaration order significant, which the update set says it is
|
|
695
|
-
# not. Refused here, where the declaration can still be read whole.
|
|
338
|
+
# A command's effects are one update set over the pre-dispatch state — a field
|
|
339
|
+
# written twice would make declaration order silently significant.
|
|
696
340
|
def refuse_duplicate_targets!
|
|
697
341
|
return if MetaValidator.shadow_parsing? # frozen era text is history
|
|
698
342
|
|
|
@@ -709,37 +353,10 @@ module Hecks
|
|
|
709
353
|
end
|
|
710
354
|
end
|
|
711
355
|
|
|
712
|
-
#
|
|
713
|
-
#
|
|
714
|
-
# `
|
|
715
|
-
#
|
|
716
|
-
# written from, not inferred from this comment.
|
|
717
|
-
#
|
|
718
|
-
# `sets :field` alone (S5's own bare form — no `to:`, meaning
|
|
719
|
-
# `to: :field`) already says the command accepts an argument
|
|
720
|
-
# named `:field`; requiring a separate `attribute :field, Type`
|
|
721
|
-
# line that retypes what the owning aggregate/entity already
|
|
722
|
-
# declared is the same redundancy S10's `given` reference already
|
|
723
|
-
# killed for preconditions ("a precondition shared across
|
|
724
|
-
# commands is declared once... a command references it by
|
|
725
|
-
# name"). Same move here, one level down: when the command
|
|
726
|
-
# hasn't declared its own `:field`, import the owner's
|
|
727
|
-
# already-built `Attribute` verbatim (same type, pattern,
|
|
728
|
-
# optional, admits) instead of retyping it.
|
|
729
|
-
#
|
|
730
|
-
# Only the exact self-referential shape qualifies — `sets
|
|
731
|
-
# :field, to: :other` names a genuinely different source and
|
|
732
|
-
# stays exactly as explicit as it always was; `sets :field, to:
|
|
733
|
-
# false` (or any other literal) isn't naming an argument at all.
|
|
734
|
-
# Comparing as text rather than identity handles both sides
|
|
735
|
-
# (`source` carries whatever `target` was passed as; `target`
|
|
736
|
-
# itself is always symbolized) without caring which.
|
|
737
|
-
#
|
|
738
|
-
# Declaration order matters here the same way it already does
|
|
739
|
-
# for `identified_by`/`given` — the owner's own attribute must
|
|
740
|
-
# exist by the time this builder's `build` runs, which every
|
|
741
|
-
# real bluebook already satisfies (the aggregate/entity always
|
|
742
|
-
# declares its attributes before the commands that act on them).
|
|
356
|
+
# `sets :field` alone already means the command accepts an argument named `:field` —
|
|
357
|
+
# when the command hasn't declared its own `:field`, imports the owner's already-built
|
|
358
|
+
# `Attribute` verbatim instead of requiring a redundant re-declaration (ADR 0025).
|
|
359
|
+
# Requires the owner's own attributes to already exist by the time `build` runs.
|
|
743
360
|
def resolve_implicit_attributes!
|
|
744
361
|
@mutations.each do |mutation|
|
|
745
362
|
case mutation.op
|
|
@@ -748,51 +365,25 @@ module Hecks
|
|
|
748
365
|
end
|
|
749
366
|
refuse_unknown_state_sources!(mutation)
|
|
750
367
|
end
|
|
751
|
-
# A second, separate pass
|
|
752
|
-
#
|
|
753
|
-
#
|
|
754
|
-
# against the final `attributes` list. `sets :a, to: :b` declared
|
|
755
|
-
# before `sets :b` (bare, importing :b from the owner) is real
|
|
756
|
-
# and legal; checking inline, mutation by mutation, would refuse
|
|
757
|
-
# it for an ordering accident this language has never required
|
|
758
|
-
# authors to avoid. rubocop's Style/CombinableLoops can't see
|
|
759
|
-
# that dependency — it only sees two `@mutations.each` calls
|
|
760
|
-
# back to back — which is exactly why it's disabled repo-wide
|
|
761
|
-
# (see .rubocop.yml) rather than satisfied.
|
|
368
|
+
# A second, separate pass, not folded into the loop above: every mutation's own
|
|
369
|
+
# self-referential import must land before any source is checked against the
|
|
370
|
+
# final `attributes` list, or a legal declaration order gets refused.
|
|
762
371
|
@mutations.each { |mutation| refuse_unknown_argument_sources!(mutation) }
|
|
763
372
|
end
|
|
764
373
|
|
|
765
|
-
#
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
#
|
|
769
|
-
# [source]` fallback, a real, intentional second meaning this
|
|
770
|
-
# check must not foreclose). For exactly these five ops, a bare
|
|
771
|
-
# Symbol source has exactly one legitimate reading — the name of
|
|
772
|
-
# a declared argument — so anything else is unreachable at
|
|
773
|
-
# runtime, not merely optional: no caller can ever supply a value
|
|
774
|
-
# under a name the command never declared (ArgumentGate's own
|
|
775
|
-
# `refuse_unknown_arguments` already refuses that), so the source
|
|
776
|
-
# resolves to nil forever, indistinguishable from a legitimately
|
|
777
|
-
# absent optional argument until this check existed to tell them
|
|
778
|
-
# apart. Mirrors `AggregateBuilder#seal_query_argument`'s
|
|
779
|
-
# identical shape for a query's own where-clause argument —
|
|
780
|
-
# same mistake, one construct over.
|
|
374
|
+
# For exactly these ops, a bare Symbol source has exactly one legitimate reading —
|
|
375
|
+
# a declared argument's name — since `append`'s record-state fallback doesn't apply
|
|
376
|
+
# here; anything else would resolve to nil forever, indistinguishable from an
|
|
377
|
+
# absent optional argument, unless refused explicitly.
|
|
781
378
|
CHECKED_SYMBOL_SOURCE_OPS = %i[set increment decrement multiply remove].freeze
|
|
782
379
|
private_constant :CHECKED_SYMBOL_SOURCE_OPS
|
|
783
380
|
|
|
784
381
|
def refuse_unknown_argument_sources!(mutation)
|
|
785
382
|
return unless CHECKED_SYMBOL_SOURCE_OPS.include?(mutation.op)
|
|
786
383
|
return unless mutation.source.is_a?(Symbol)
|
|
787
|
-
# The bare self-referential shape (`
|
|
788
|
-
#
|
|
789
|
-
#
|
|
790
|
-
# that name, the more specific, pre-existing refusal one level
|
|
791
|
-
# up (`AggregateBuilder#seal_mutation_targets`, checking the
|
|
792
|
-
# mutation's target against the aggregate's own fields) is the
|
|
793
|
-
# one that should fire, naming the field as a target problem,
|
|
794
|
-
# not — confusingly — as a source problem this check would
|
|
795
|
-
# otherwise misreport it as.
|
|
384
|
+
# The bare self-referential shape (`source == target`) is `resolve_bare_set!`'s
|
|
385
|
+
# own territory — skipped here so an undeclared field is refused as a target
|
|
386
|
+
# problem, not misreported as a source problem.
|
|
796
387
|
return if mutation.source.to_s == mutation.target.to_s
|
|
797
388
|
return if attributes.any? { |attr| attr.name == mutation.source }
|
|
798
389
|
|
|
@@ -803,10 +394,8 @@ module Hecks
|
|
|
803
394
|
"caller actually sent"
|
|
804
395
|
end
|
|
805
396
|
|
|
806
|
-
# `state(:name)`
|
|
807
|
-
#
|
|
808
|
-
# name, the way an unknown `given` reference is; nothing here can
|
|
809
|
-
# read a field the aggregate never declared.
|
|
397
|
+
# `state(:name)` snapshots one of the owner's own fields; refused at build, the
|
|
398
|
+
# same way an unknown `given` reference is.
|
|
810
399
|
def refuse_unknown_state_sources!(mutation)
|
|
811
400
|
sources = mutation.source.is_a?(Hash) ? mutation.source.values : [mutation.source]
|
|
812
401
|
sources.grep(StateRef).each do |ref|
|
|
@@ -818,15 +407,8 @@ module Hecks
|
|
|
818
407
|
end
|
|
819
408
|
|
|
820
409
|
def resolve_bare_set!(mutation)
|
|
821
|
-
#
|
|
822
|
-
#
|
|
823
|
-
# recording that it has moved, into a closed set whose member is
|
|
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
|
|
826
|
-
# argument nothing ever passes, harmless at runtime only
|
|
827
|
-
# because the owner's default filled it, and a real, silent
|
|
828
|
-
# divergence for every projection that reads the command's
|
|
829
|
-
# declared arguments.
|
|
410
|
+
# Only a Symbol naming its own target qualifies — a literal that merely spells the
|
|
411
|
+
# same word (`sets :moved, to: "moved"`) must not import a phantom argument.
|
|
830
412
|
return unless mutation.source.is_a?(Symbol) && mutation.source.to_s == mutation.target.to_s
|
|
831
413
|
return if attributes.any? { |attr| attr.name == mutation.target }
|
|
832
414
|
|
|
@@ -834,46 +416,13 @@ module Hecks
|
|
|
834
416
|
attributes << owner_attr if owner_attr
|
|
835
417
|
end
|
|
836
418
|
|
|
837
|
-
# One hop deeper than `resolve_bare_set
|
|
838
|
-
#
|
|
839
|
-
#
|
|
840
|
-
# record, so a bare self-referential field inside it (the hash
|
|
841
|
-
# key equals its own value, same shorthand `resolve_bare_set!`
|
|
842
|
-
# already reads) can't resolve against `@owner_attributes` — the
|
|
843
|
-
# aggregate itself never stores `:narrative`, only the list
|
|
844
|
-
# element's own construct does (`attribute :ledger,
|
|
845
|
-
# list_of(LedgerEntry)`, and `LedgerEntry` is what actually
|
|
846
|
-
# declares `:narrative`). Resolves the list field's own element
|
|
847
|
-
# type first (`element_type_for`), then that construct's own
|
|
848
|
-
# attribute of the same name — same verbatim-import, one level
|
|
849
|
-
# further down the same reasoning `resolve_bare_set!`'s own
|
|
850
|
-
# comment already gives.
|
|
851
|
-
#
|
|
852
|
-
# A non-self-referential value (`direction: { value: "credit" }`,
|
|
853
|
-
# a nested literal) is untouched — only a bare symbol equal to
|
|
854
|
-
# its own key ever qualifies, identical to `resolve_bare_set!`'s
|
|
855
|
-
# own target/source text comparison.
|
|
419
|
+
# One hop deeper than `resolve_bare_set!`: an `append:` mutation builds a new list
|
|
420
|
+
# element, so a bare self-referential field inside it resolves against the list's
|
|
421
|
+
# own element type (`element_type_for`), not `@owner_attributes`.
|
|
856
422
|
#
|
|
857
|
-
# Position-preserving, not appended at the end — the exported IR
|
|
858
|
-
#
|
|
859
|
-
#
|
|
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
|
|
863
|
-
# of the group is resolved). A plain `attributes << owner_attr`
|
|
864
|
-
# here would only ever reproduce the original order when the
|
|
865
|
-
# missing field happened to already be last — real, live
|
|
866
|
-
# evidence: `Keyword#was`/`Argument#variadic` (both genuinely
|
|
867
|
-
# last in their own append hash) round-tripped correctly under
|
|
868
|
-
# the naive append; every other field in the same hash did not,
|
|
869
|
-
# caught by this codemod's own reboot-and-diff safety net rather
|
|
870
|
-
# than silently landing wrong.
|
|
871
|
-
# `anchor` is a snapshot of `present`'s position, taken before
|
|
872
|
-
# `attributes.reject!` mutates the array below it — see this
|
|
873
|
-
# method's own header comment for why: a naive append-at-end
|
|
874
|
-
# silently scrambled real corpus field order (Keyword#was/
|
|
875
|
-
# Argument#variadic). Splitting the snapshot from the mutation it
|
|
876
|
-
# guards is exactly the ordering invariant that must not move.
|
|
423
|
+
# Position-preserving, not appended at the end — the exported IR is array-order-
|
|
424
|
+
# sensitive, so resolved fields are reinserted at the position the group's leftmost
|
|
425
|
+
# still-declared member already occupied.
|
|
877
426
|
# rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
878
427
|
def resolve_append_fields!(mutation)
|
|
879
428
|
return unless mutation.source.is_a?(Hash)
|
|
@@ -897,13 +446,8 @@ module Hecks
|
|
|
897
446
|
attributes.insert(anchor, *group)
|
|
898
447
|
end
|
|
899
448
|
|
|
900
|
-
# The owner's own list attribute names its element type as text
|
|
901
|
-
#
|
|
902
|
-
# time) — resolved against `@owner_constructs` (the owner's own
|
|
903
|
-
# value objects and entities, the only two kinds an element can
|
|
904
|
-
# be) by `hecks_name`, the same lookup
|
|
905
|
-
# `AttributeCollector#resolve_identity_field!` already uses for
|
|
906
|
-
# a value object's own name.
|
|
449
|
+
# The owner's own list attribute names its element type as text; resolved against
|
|
450
|
+
# `@owner_constructs` (the only two kinds an element can be) by `hecks_name`.
|
|
907
451
|
def element_type_for(list_field)
|
|
908
452
|
list_attr = @owner_attributes.find { |attr| attr.name == list_field && attr.list? }
|
|
909
453
|
return nil unless list_attr
|
|
@@ -911,12 +455,8 @@ module Hecks
|
|
|
911
455
|
@owner_constructs.find { |construct| construct.hecks_name.to_s == list_attr.type.to_s }
|
|
912
456
|
end
|
|
913
457
|
|
|
914
|
-
#
|
|
915
|
-
#
|
|
916
|
-
# shorthand, no refusal for the redundant `to: target` spelling —
|
|
917
|
-
# frozen era text was minted under this reading, and a legacy
|
|
918
|
-
# grammar exists precisely so re-parsing it never silently changes
|
|
919
|
-
# what it meant.
|
|
458
|
+
# Preserves `then_set`'s exact prior reading (`from:` a synonym for `to:`, no
|
|
459
|
+
# omittable-`to:` shorthand) so frozen era text always re-parses to the same meaning.
|
|
920
460
|
def legacy_then_set(target, positional_to = UNSET, to: UNSET, from: UNSET, append: UNSET,
|
|
921
461
|
increment: UNSET, decrement: UNSET, multiply: UNSET, clamp: UNSET, remove: UNSET)
|
|
922
462
|
to = positional_to if to.equal?(UNSET) && !positional_to.equal?(UNSET)
|
|
@@ -942,21 +482,9 @@ module Hecks
|
|
|
942
482
|
@mutations << Mutation.new(target: target.to_sym, op: op, source: normalize_append_source(op, source))
|
|
943
483
|
end
|
|
944
484
|
|
|
945
|
-
# `append:` normally binds
|
|
946
|
-
#
|
|
947
|
-
# `
|
|
948
|
-
# `mutation_rows` all read `mutation.source` as a Hash
|
|
949
|
-
# unconditionally. A bare value (`append: :single_field`, or any
|
|
950
|
-
# non-Hash literal) is the one-field shorthand: exactly what an
|
|
951
|
-
# explicit `append: { value: :single_field }` would have meant,
|
|
952
|
-
# named the same way a single-field value object's own implicit
|
|
953
|
-
# member already is (`MutationApplier#appended`'s own `:value`
|
|
954
|
-
# scalar-unwrap). Without this, that shorthand built a Mutation
|
|
955
|
-
# whose `source` was a bare Symbol, which crashed with a raw
|
|
956
|
-
# `NoMethodError` on `#transform_values` the moment anything
|
|
957
|
-
# downstream read it — at dispatch (`MutationApplier#appended`),
|
|
958
|
-
# at IR emission (`Mutation#appended_fields`), and in the
|
|
959
|
-
# meta-validator's own Judge (`Readings#mutation_rows`). #138.
|
|
485
|
+
# `append:` normally binds a Hash of fields; a bare value (`append: :single_field`)
|
|
486
|
+
# is the one-field shorthand for `append: { value: :single_field }` — without this,
|
|
487
|
+
# downstream code that reads `mutation.source` as a Hash unconditionally would crash.
|
|
960
488
|
def normalize_append_source(oper, source)
|
|
961
489
|
return source unless oper == :append
|
|
962
490
|
return source if source.is_a?(::Hash)
|