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
|
@@ -8,7 +8,7 @@ module Hecks
|
|
|
8
8
|
# (attributes, value objects, entities, commands, queries, policies,
|
|
9
9
|
# invariants, preconditions, projected fields, its lifecycle and
|
|
10
10
|
# identity) and assembling the final `Aggregate` IR in `#build`.
|
|
11
|
-
# `entity`/`command`/`query` only
|
|
11
|
+
# `entity`/`command`/`query` only queue a descriptor during
|
|
12
12
|
# `instance_eval` (`#drain_pending!` builds them, in order, once the
|
|
13
13
|
# whole block has run) so a later-declared piece can still be
|
|
14
14
|
# referenced by an earlier line — see `#drain_pending!`'s own header.
|
|
@@ -21,6 +21,15 @@ module Hecks
|
|
|
21
21
|
include WordGate
|
|
22
22
|
include Sealing
|
|
23
23
|
|
|
24
|
+
# @param name [String] the aggregate's name, as written after `aggregate`
|
|
25
|
+
# @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
|
|
26
|
+
# chapter-wide given pool, shared and written through by `given_impl`
|
|
27
|
+
# @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
|
|
28
|
+
# references, appended to when this aggregate's own reference cannot resolve yet
|
|
29
|
+
# @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
|
|
30
|
+
# the chapter-wide, entity-scoped given pool, threaded unchanged to every entity
|
|
31
|
+
# @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
|
|
32
|
+
# entity-scoped bare given references, threaded unchanged to every entity
|
|
24
33
|
def initialize(name, chapter_named_givens: {}, chapter_pending_givens: [],
|
|
25
34
|
chapter_entity_named_givens: {}, chapter_entity_pending_givens: [])
|
|
26
35
|
@name = name
|
|
@@ -34,23 +43,23 @@ module Hecks
|
|
|
34
43
|
@queries = []
|
|
35
44
|
@policies = []
|
|
36
45
|
@reference_targets = []
|
|
37
|
-
#
|
|
38
|
-
# comment.
|
|
46
|
+
# The root of the cross-entity given pool — see `#entity`'s own
|
|
47
|
+
# comment. One hash for the whole aggregate, threaded unchanged
|
|
39
48
|
# into every piece nested under it, however deep.
|
|
40
49
|
@entity_named_givens = {}
|
|
41
|
-
#
|
|
42
|
-
# from `BluebookBuilder#aggregate`, shared with every
|
|
50
|
+
# **One level wider still** — the chapter's own pool, threaded in
|
|
51
|
+
# from `BluebookBuilder#aggregate`, shared with every other
|
|
43
52
|
# aggregate the same chapter builds. See `#given`'s own
|
|
44
53
|
# comment for what this closes.
|
|
45
54
|
@chapter_named_givens = chapter_named_givens
|
|
46
|
-
# A
|
|
55
|
+
# A chapter may be split across files — threaded in the same
|
|
47
56
|
# way as `@chapter_named_givens`, one Array shared chapter-wide.
|
|
48
57
|
# See `#pending_chapter_given`'s own comment for what queues
|
|
49
58
|
# here and `BluebookBuilder#resolve_pending_chapter_givens!`
|
|
50
59
|
# for where it drains.
|
|
51
60
|
@chapter_pending_givens = chapter_pending_givens
|
|
52
|
-
#
|
|
53
|
-
#
|
|
61
|
+
# One level wider still, past the chapter's own aggregate-level
|
|
62
|
+
# pool — the chapter's own entity-scoped pool, threaded from
|
|
54
63
|
# `BluebookBuilder#aggregate_impl` the same way
|
|
55
64
|
# `@chapter_named_givens` is, and passed straight through
|
|
56
65
|
# (unchanged) to every top-level piece this aggregate builds
|
|
@@ -58,7 +67,7 @@ module Hecks
|
|
|
58
67
|
# comment for what this closes.
|
|
59
68
|
@chapter_entity_named_givens = chapter_entity_named_givens
|
|
60
69
|
@chapter_entity_pending_givens = chapter_entity_pending_givens
|
|
61
|
-
#
|
|
70
|
+
# **Deferred construction** — `entity`/`command`/`query` push a
|
|
62
71
|
# pending descriptor here instead of building immediately; see
|
|
63
72
|
# `#drain_pending!`'s own comment for why.
|
|
64
73
|
@pending_entities = []
|
|
@@ -66,44 +75,65 @@ module Hecks
|
|
|
66
75
|
@pending_queries = []
|
|
67
76
|
end
|
|
68
77
|
|
|
78
|
+
# Sets the human-readable description shown for this aggregate.
|
|
79
|
+
#
|
|
80
|
+
# @param value [String] the description text
|
|
81
|
+
# @return [String] the description as stored
|
|
69
82
|
def description(value)
|
|
70
83
|
# moved to the language: Description invariant, on Root.Declare
|
|
71
84
|
|
|
72
85
|
@description = value
|
|
73
86
|
end
|
|
74
87
|
|
|
75
|
-
#
|
|
88
|
+
# Names where a concept adopted from a canonical source came from.
|
|
89
|
+
#
|
|
90
|
+
# Origin, not runtime identity — a concept adopted from a canonical
|
|
76
91
|
# source (§28) names where it came from without that fact ever
|
|
77
92
|
# touching `hecks_fqn`/dispatch. Captured raw, the same way
|
|
78
93
|
# `attribute ..., default: { value: "small" }` captures a literal
|
|
79
94
|
# Hash untouched — no re-parsing, no structure imposed beyond
|
|
80
95
|
# "whatever the author wrote."
|
|
81
|
-
#
|
|
82
|
-
# `
|
|
96
|
+
#
|
|
97
|
+
# Answers the `provenance` word (and, via the same table rows,
|
|
98
|
+
# its siblings `projects`/`lifecycle`/`entity`/
|
|
99
|
+
# `query`/`policy`/`command` below) through the table's `calls:`
|
|
100
|
+
# column — item #13's full
|
|
83
101
|
# metaprogrammed dispatch (slice 4c). All bootstrap-reachable
|
|
84
102
|
# (used throughout the core/attached chapters), all in
|
|
85
|
-
# GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
103
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
104
|
+
#
|
|
105
|
+
# @param from [Object] the canonical source, captured exactly as written
|
|
106
|
+
# @return [Object] `from` as stored
|
|
86
107
|
def provenance_impl(from:)
|
|
87
108
|
@provenance = from
|
|
88
109
|
end
|
|
89
110
|
|
|
111
|
+
# Declares a reference from this aggregate's own head to another aggregate's identity.
|
|
112
|
+
#
|
|
90
113
|
# `optional:` — matching `CommandBuilder#reference_to`'s own
|
|
91
|
-
# signature, which already had it
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
# It was a real gap because an aggregate that can point at ONE
|
|
96
|
-
# OF several targets (Item's own `personal_list_id`/
|
|
114
|
+
# signature, which already had it, and forwarded here to
|
|
115
|
+
# `attribute_impl()`/`relationship_attribute` (`optional: optional`, below). A real
|
|
116
|
+
# need: an aggregate that can point at one
|
|
117
|
+
# of several targets (Item's own `personal_list_id`/
|
|
97
118
|
# `camping_list_id`, never both) needs each reference optional
|
|
98
119
|
# on the aggregate's own persisted schema, not just as a
|
|
99
120
|
# command's input — real corpus use:
|
|
100
121
|
# `spec/fixtures/hop_chain.bluebook`'s own `Proposal` aggregate
|
|
101
122
|
# declares `reference_to Engagement, optional: true` at the
|
|
102
123
|
# aggregate head.
|
|
103
|
-
#
|
|
124
|
+
#
|
|
125
|
+
# Answers the `reference_to` word through the table's `calls:`
|
|
126
|
+
# column — item #13's full metaprogrammed
|
|
104
127
|
# dispatch (slice 4b). Bootstrap-reachable (every core/attached
|
|
105
128
|
# grammar chapter uses reference_to to describe itself), so also
|
|
106
|
-
# named in GenericDispatch::BOOTSTRAP_CALLS_FALLBACK
|
|
129
|
+
# named in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
130
|
+
#
|
|
131
|
+
# @param type [Module, Symbol, String] the referenced aggregate, written as a bare
|
|
132
|
+
# constant
|
|
133
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
134
|
+
# @param optional [Boolean] whether the reference may be absent
|
|
135
|
+
# @return [void]
|
|
136
|
+
# @raise [Bluebook::DSL::Malformed] if `as` (or the derived name) is already declared
|
|
107
137
|
def reference_to_impl(type, as: nil, optional: false)
|
|
108
138
|
target = Naming.demodulise(type)
|
|
109
139
|
@reference_targets << target
|
|
@@ -111,32 +141,42 @@ module Hecks
|
|
|
111
141
|
as || default_reference_name(target), optional: optional)
|
|
112
142
|
end
|
|
113
143
|
|
|
114
|
-
#
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
#
|
|
144
|
+
# Declares that this aggregate holds its own kept-fresh copy of a field reached through
|
|
145
|
+
# a reference, so a rule can read it locally instead of reaching across the boundary.
|
|
146
|
+
#
|
|
147
|
+
# A rule may only read within its own aggregate boundary (S12,
|
|
148
|
+
# ADR 0025 — "Consistency across aggregate boundaries"). Reaching through a
|
|
149
|
+
# `reference_to` at rule-evaluation time (`References#
|
|
118
150
|
# dereference`, a live query against another aggregate's own
|
|
119
|
-
# repository
|
|
120
|
-
# only this record" model everywhere else
|
|
151
|
+
# repository) would be unbounded and inconsistent with the "a rule reads
|
|
152
|
+
# only this record" model everywhere else — `projects` is what
|
|
121
153
|
# replaces that: `projects :customer_status, from: :"customer.
|
|
122
|
-
# status"` declares that
|
|
123
|
-
# `Customer`'s own `:status`, kept fresh by a
|
|
154
|
+
# status"` declares that this aggregate holds its own copy of
|
|
155
|
+
# `Customer`'s own `:status`, kept fresh by a rebuild sweep
|
|
124
156
|
# (`Runtime::ProjectionRebuild`) rather than read live. A rule
|
|
125
157
|
# then reads `customer_status` the same way it reads any other
|
|
126
158
|
# local field — no dot, no reference walk.
|
|
127
159
|
#
|
|
128
|
-
# `from:`
|
|
129
|
-
# `customer`, the attribute
|
|
160
|
+
# `from:` names the local reference, not the target aggregate —
|
|
161
|
+
# `customer`, the attribute this aggregate's own `reference_to
|
|
130
162
|
# Customer` already minted, not `Customer` the type — so two
|
|
131
163
|
# references to the same aggregate (aliased differently) can
|
|
132
|
-
# each carry their own projection without ambiguity. The
|
|
164
|
+
# each carry their own projection without ambiguity. The target
|
|
133
165
|
# field's own existence cannot be checked here: the target
|
|
134
|
-
# aggregate does not exist yet while
|
|
166
|
+
# aggregate does not exist yet while this one is still being
|
|
135
167
|
# declared (the same reason a query's own hop tail is checked
|
|
136
168
|
# by `BluebookBuilder#validate_query_hops!`, once every
|
|
137
169
|
# aggregate in the chapter is real, not by `AggregateBuilder`
|
|
138
170
|
# itself) — `validate_projected_fields!` is where that half
|
|
139
171
|
# happens.
|
|
172
|
+
#
|
|
173
|
+
# @param name [Symbol, String] the local field's name this aggregate projects the
|
|
174
|
+
# remote value into
|
|
175
|
+
# @param from [Symbol, String] the local reference and remote field, dotted, such as
|
|
176
|
+
# `:"customer.status"`
|
|
177
|
+
# @return [Array<Bluebook::ProjectedField>] every projected field declared so far, this
|
|
178
|
+
# one last
|
|
179
|
+
# @raise [Bluebook::DSL::Malformed] if `from` is not `reference.field` shaped
|
|
140
180
|
def projects_impl(name, from:)
|
|
141
181
|
reference, _, remote_field = from.to_s.rpartition(".")
|
|
142
182
|
|
|
@@ -151,9 +191,9 @@ module Hecks
|
|
|
151
191
|
remote_field: remote_field.to_sym)
|
|
152
192
|
end
|
|
153
193
|
|
|
154
|
-
# `has_many`/`has_one`/`belongs_to` were
|
|
194
|
+
# `has_many`/`has_one`/`belongs_to` were legacy (ADR 0025,
|
|
155
195
|
# "References") — sugar over `reference_to` that collapsed to an
|
|
156
|
-
# anonymous reference and, for `has_many`,
|
|
196
|
+
# anonymous reference and, for `has_many`, lied (singularised its
|
|
157
197
|
# target and minted one scalar, so `film.backers` read `nil` and
|
|
158
198
|
# never `[]`). Wave 6 (identity-and-relationships arc) un-deprecates
|
|
159
199
|
# all three for real: a relationship word now retains the author's
|
|
@@ -161,16 +201,31 @@ module Hecks
|
|
|
161
201
|
# identities, but no longer collapsed to a bare `reference_to`
|
|
162
202
|
# during assembly. `MetaValidator.shadow_parsing?` still routes to
|
|
163
203
|
# `legacy_has_many`/`legacy_has_one` so frozen era text written
|
|
164
|
-
# under the
|
|
204
|
+
# under the old (lying/collapsing) meaning still parses the way it
|
|
165
205
|
# did when it was written — real, if rare corpus: "Combined corpus
|
|
166
206
|
# uses: one."
|
|
167
207
|
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
# `
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
# `
|
|
208
|
+
# Declares a list-typed relationship to another aggregate, referenced by its plural name.
|
|
209
|
+
#
|
|
210
|
+
# Answers the `has_many` word (and, via the same table rows, its
|
|
211
|
+
# siblings `has_one`/`belongs_to` below) through the table's
|
|
212
|
+
# `calls:` column — item #13's full metaprogrammed dispatch
|
|
213
|
+
# (slice 4). Each Keyword row's own `calls:` names the matching
|
|
214
|
+
# `_impl`, so all three are carried in
|
|
215
|
+
# `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK` like every other
|
|
216
|
+
# `calls:`-routed word, but never actually exercised during real
|
|
217
|
+
# bootstrap (no core/attached chapter uses one of these to
|
|
218
|
+
# describe itself), unlike `attribute`/`role`.
|
|
219
|
+
#
|
|
220
|
+
# @param type [Module, Symbol, String] the related aggregate's plural name, a bare
|
|
221
|
+
# constant
|
|
222
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
223
|
+
# @param legacy_options [Hash] must be empty outside shadow-parsing; under
|
|
224
|
+
# shadow-parsing, `:optional` is read for the legacy single-reference form
|
|
225
|
+
# @return [void]
|
|
226
|
+
# @raise [Bluebook::DSL::Malformed] outside shadow-parsing, if `legacy_options` is
|
|
227
|
+
# non-empty; may also raise from `relationship_attribute` if the derived name is
|
|
228
|
+
# already declared
|
|
174
229
|
def has_many_impl(type, as: nil, **legacy_options)
|
|
175
230
|
return legacy_has_many(type, as: as, optional: legacy_options.fetch(:optional, false)) if MetaValidator.shadow_parsing?
|
|
176
231
|
|
|
@@ -186,6 +241,12 @@ module Hecks
|
|
|
186
241
|
list: true)
|
|
187
242
|
end
|
|
188
243
|
|
|
244
|
+
# Declares a single-valued relationship this aggregate holds toward another.
|
|
245
|
+
#
|
|
246
|
+
# @param type [Module, Symbol, String] the related aggregate, a bare constant
|
|
247
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
248
|
+
# @param optional [Boolean] whether the relationship may be absent
|
|
249
|
+
# @return [void]
|
|
189
250
|
def has_one_impl(type, as: nil, optional: false)
|
|
190
251
|
return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
|
|
191
252
|
|
|
@@ -195,6 +256,12 @@ module Hecks
|
|
|
195
256
|
optional: optional)
|
|
196
257
|
end
|
|
197
258
|
|
|
259
|
+
# Declares a single-valued relationship toward the aggregate that owns this one.
|
|
260
|
+
#
|
|
261
|
+
# @param type [Module, Symbol, String] the owning aggregate, a bare constant
|
|
262
|
+
# @param as [Symbol, nil] the attribute's name; nil derives it from `type`
|
|
263
|
+
# @param optional [Boolean] whether the relationship may be absent
|
|
264
|
+
# @return [void]
|
|
198
265
|
def belongs_to_impl(type, as: nil, optional: false)
|
|
199
266
|
return legacy_has_one(type, as: as, optional: optional) if MetaValidator.shadow_parsing?
|
|
200
267
|
|
|
@@ -204,80 +271,116 @@ module Hecks
|
|
|
204
271
|
optional: optional)
|
|
205
272
|
end
|
|
206
273
|
|
|
274
|
+
# Declares this aggregate's own state machine.
|
|
275
|
+
#
|
|
276
|
+
# @param field [Symbol, String] the attribute the state machine lives on
|
|
277
|
+
# @param default [String, Symbol] the state a new record starts in
|
|
278
|
+
# @yield the lifecycle body of `transition` rows, evaluated against a `LifecycleBuilder`
|
|
279
|
+
# @return [Bluebook::Lifecycle] the built state machine
|
|
280
|
+
# @raise [Bluebook::DSL::Malformed] if two transitions for one command overlap
|
|
207
281
|
def lifecycle_impl(field, default:, &)
|
|
208
282
|
@lifecycle = LifecycleBuilder.build(field, default: default, &)
|
|
209
283
|
end
|
|
210
284
|
|
|
211
|
-
#
|
|
285
|
+
# Queues a piece nested in this aggregate, built later once every sibling has been seen.
|
|
286
|
+
#
|
|
287
|
+
# A piece is declared in this aggregate — its owner is stamped by
|
|
212
288
|
# `Aggregate#initialize`, once the aggregate exists. Its own
|
|
213
289
|
# commands were given the piece as their owner when it was declared,
|
|
214
290
|
# so the chain closes as chapter -> aggregate -> entity -> command.
|
|
215
|
-
#
|
|
291
|
+
# Not built here — see `#drain_pending!`'s own comment for why
|
|
216
292
|
# this only queues a descriptor.
|
|
217
293
|
#
|
|
218
|
-
# A
|
|
294
|
+
# A precondition shared across sibling pieces, declared once — one
|
|
219
295
|
# level wider than round 4's own `EntityBuilder#given` (shared
|
|
220
|
-
# across
|
|
221
|
-
#
|
|
296
|
+
# across one piece's own commands): `@entity_named_givens` is the
|
|
297
|
+
# same hash threaded into every piece this aggregate builds, so a
|
|
222
298
|
# piece's own entity-level `given(desc) { block }` write-throughs
|
|
223
|
-
# into it, and any
|
|
224
|
-
# bare, the identical description/canonical, evaluated in
|
|
299
|
+
# into it, and any other piece's own command can reference it back
|
|
300
|
+
# bare, the identical description/canonical, evaluated in its own
|
|
225
301
|
# `parent`-relative context. Real, live corpus this closes:
|
|
226
|
-
# `SafeDepositBox`'s `Visit`/`KeyIssuance` — two
|
|
302
|
+
# `SafeDepositBox`'s `Visit`/`KeyIssuance` — two different pieces
|
|
227
303
|
# under one head, each independently typing `given("customer is
|
|
228
304
|
# active") { parent.customer.status == "active" }` byte for byte,
|
|
229
|
-
# which neither the aggregate's
|
|
230
|
-
#
|
|
305
|
+
# which neither the aggregate's own "customer is active" (a
|
|
306
|
+
# different canonical — bare `customer.status`, not
|
|
231
307
|
# `parent.customer.status`, wrong scope for a piece's own command
|
|
232
308
|
# to evaluate) nor round 4's single-piece `given` could reach.
|
|
309
|
+
# @param name [String] the nested piece's name
|
|
310
|
+
# @yield the piece body, evaluated against an `EntityBuilder` once drained
|
|
311
|
+
# @return [Array<Array>] every pending piece queued so far, this one last
|
|
233
312
|
def entity_impl(name, &block)
|
|
234
313
|
@pending_entities << [name, block]
|
|
235
314
|
end
|
|
236
315
|
|
|
316
|
+
# Queues a query declared on this aggregate, built later once every sibling has been seen.
|
|
317
|
+
#
|
|
318
|
+
# @param name [String] the query's name
|
|
319
|
+
# @yield the query body, evaluated against a `QueryBuilder` once drained
|
|
320
|
+
# @return [Array<Array>] every pending query queued so far, this one last
|
|
237
321
|
def query_impl(name, &block)
|
|
238
322
|
@pending_queries << [name, block]
|
|
239
323
|
end
|
|
240
324
|
|
|
325
|
+
# Declares a policy scoped to this aggregate, stamping it with the aggregate's own name.
|
|
326
|
+
#
|
|
327
|
+
# @param name [String] the policy's name
|
|
328
|
+
# @yield the policy body, evaluated against a `PolicyBuilder`
|
|
329
|
+
# @return [Array<Bluebook::Policy>] every policy declared so far, this one last
|
|
330
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check the policy builder raises
|
|
241
331
|
def policy_impl(name, &)
|
|
242
332
|
reaction = PolicyBuilder.build(name, &)
|
|
243
333
|
reaction.aggregate = @name
|
|
244
334
|
@policies << reaction
|
|
245
335
|
end
|
|
246
336
|
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
252
|
-
#
|
|
337
|
+
# Declares a value object on this aggregate, either as a block of `attribute` lines or
|
|
338
|
+
# (the `type` shorthand) as a single `:value` attribute.
|
|
339
|
+
#
|
|
340
|
+
# `builder.closed_sets` too, not only `builder.build` — a real
|
|
341
|
+
# gap this exact fix closes: a
|
|
342
|
+
# value_object's own inline `attribute :x, one_of(...)` (legal
|
|
343
|
+
# since S3, ADR 0025 removed the wrong-arity collision that would
|
|
344
|
+
# otherwise crash it) synthesises its own
|
|
345
|
+
# anonymous value object via the same `AttributeCollector#closed_
|
|
253
346
|
# sets` mechanism an aggregate's own attributes already use — and
|
|
254
347
|
# nothing installed it anywhere. `Box.attributes` said `size:
|
|
255
348
|
# "Size"` while no "Size" value object existed in the whole
|
|
256
349
|
# domain: a dangling type name, not a working closed set. Flattened
|
|
257
|
-
# into
|
|
350
|
+
# into this aggregate's own `@value_objects`, the identical move
|
|
258
351
|
# `@value_objects + closed_sets` already makes for the aggregate's
|
|
259
352
|
# own direct attributes (see this file's other 5 call sites).
|
|
260
|
-
# `type` —
|
|
353
|
+
# `type` — the bare shorthand (single-attribute value objects):
|
|
261
354
|
# `value_object :Price, Integer` declares a value object with
|
|
262
|
-
# exactly one attribute,
|
|
355
|
+
# exactly one attribute, named `value`, of that type — pure sugar
|
|
263
356
|
# for `value_object("Price") { attribute :value, Integer }`,
|
|
264
|
-
# routed through the
|
|
357
|
+
# routed through the same `attribute_impl` the block form's own
|
|
265
358
|
# `attribute` line reaches (so the quoted-text-type refusal,
|
|
266
359
|
# `one_of(...)`/`list_of(...)` synthesis, everything an attribute
|
|
267
360
|
# line already does, applies unchanged rather than being
|
|
268
361
|
# re-derived here). The name `value` is not arbitrary: a
|
|
269
|
-
# single-attribute value object is a
|
|
362
|
+
# single-attribute value object is a name for a scalar, not a
|
|
270
363
|
# genuine group ([[feedback_name_the_scalar_field]], `Behaviour::
|
|
271
364
|
# ValueObject#sole_attribute`), and `value` is what the language
|
|
272
|
-
# guarantees
|
|
365
|
+
# guarantees every sole field answers to at runtime regardless of
|
|
273
366
|
# its declared name (`Runtime::Value#method_missing`'s alias) —
|
|
274
367
|
# so the shorthand simply declares it under the canonical name
|
|
275
|
-
# directly. Type
|
|
368
|
+
# directly. Type and block together are refused: the block exists
|
|
276
369
|
# to say what the fields are, and the type just said it — two
|
|
277
370
|
# answers to one question is an authoring error, never a merge.
|
|
278
|
-
#
|
|
371
|
+
# Neither type nor block keeps its historical behavior untouched
|
|
279
372
|
# (an empty attribute list — judged, or not, by the language
|
|
280
373
|
# downstream, the same as before this parameter existed).
|
|
374
|
+
#
|
|
375
|
+
# @param name [String] the value object's name
|
|
376
|
+
# @param type [Module, nil] the bare shorthand's single attribute type; mutually
|
|
377
|
+
# exclusive with `block`
|
|
378
|
+
# @yield the value object body of `attribute`/`invariant`/`one_of` lines; mutually
|
|
379
|
+
# exclusive with `type`
|
|
380
|
+
# @return [Array<Bluebook::ValueObject>] this aggregate's own value objects, including
|
|
381
|
+
# this one and any closed sets its attributes synthesised
|
|
382
|
+
# @raise [Bluebook::DSL::Malformed] if both `type` and a block are given, or the body
|
|
383
|
+
# fails any check the value object language or its builder raises
|
|
281
384
|
def value_object(name, type = nil, &block)
|
|
282
385
|
if type && block
|
|
283
386
|
raise Malformed,
|
|
@@ -293,37 +396,49 @@ module Hecks
|
|
|
293
396
|
@value_objects.concat(builder.closed_sets)
|
|
294
397
|
end
|
|
295
398
|
|
|
296
|
-
#
|
|
399
|
+
# Queues a command declared on this aggregate, built later once every sibling has been
|
|
400
|
+
# seen.
|
|
401
|
+
#
|
|
402
|
+
# `from:` — lifecycle state becomes a command guard (S10, ADR
|
|
297
403
|
# 0025) — `command "Debit", from: "open"` replaces `given
|
|
298
404
|
# ("account is open") { status == "open" }`, written 35 times
|
|
299
|
-
# in two wordings across the corpus. Checked against
|
|
405
|
+
# in two wordings across the corpus. Checked against this
|
|
300
406
|
# aggregate's own lifecycle field (`Admissibility#enforce_
|
|
301
407
|
# lifecycle_guard`) — never a target state, never a transition:
|
|
302
408
|
# the lifecycle already declares which states exist, so naming
|
|
303
409
|
# the legal ones is checkable against it, where a free-text
|
|
304
410
|
# given could drift out of sync with the state machine and did.
|
|
411
|
+
#
|
|
412
|
+
# @param name [String] the command's name
|
|
413
|
+
# @param from [String, Symbol, Array<String, Symbol>, nil] the lifecycle state(s) this
|
|
414
|
+
# command guards from; nil admits from any state
|
|
415
|
+
# @yield the command body, evaluated against a `CommandBuilder` once drained
|
|
416
|
+
# @return [Array<Array>] every pending command queued so far, this one last
|
|
305
417
|
def command_impl(name, from: nil, &block)
|
|
306
|
-
# The verb is declared
|
|
418
|
+
# The verb is declared on this aggregate — the owner `acts_on` answers
|
|
307
419
|
# with — stamped by `Aggregate#initialize` once the aggregate
|
|
308
|
-
# exists. An
|
|
309
|
-
# at the entity's own declaration.
|
|
420
|
+
# exists. An entity's commands take the entity as their owner instead,
|
|
421
|
+
# at the entity's own declaration. Not built here — see
|
|
310
422
|
# `#drain_pending!`'s own comment for why this only queues a
|
|
311
423
|
# descriptor.
|
|
312
424
|
@pending_commands << [name, from, block]
|
|
313
425
|
end
|
|
314
426
|
|
|
315
|
-
#
|
|
427
|
+
# Declares a rule this aggregate's own commands must satisfy, or references one a
|
|
428
|
+
# sibling aggregate in the chapter already declared.
|
|
429
|
+
#
|
|
430
|
+
# A precondition shared across commands, declared once (S10, ADR
|
|
316
431
|
# 0025) — an aggregate-level `given`, block required, stored by
|
|
317
432
|
# its own description rather than appended anywhere: a command
|
|
318
433
|
# names it back (`given("customer is active")`, no block of its
|
|
319
434
|
# own) rather than re-typing the predicate, so there is one
|
|
320
435
|
# description and therefore one refusal message no matter which
|
|
321
|
-
# command a caller hits.
|
|
322
|
-
#
|
|
323
|
-
#
|
|
324
|
-
# aggregate has declared
|
|
436
|
+
# command a caller hits. Declare before the commands that
|
|
437
|
+
# reference it — resolution happens at the referencing command's
|
|
438
|
+
# own build time (`CommandBuilder#given`), against whatever this
|
|
439
|
+
# aggregate has declared so far, the one ordering constraint this
|
|
325
440
|
# word carries that `identified_by`/`attribute` do not.
|
|
326
|
-
#
|
|
441
|
+
# Bare — no block — references a sibling aggregate's own
|
|
327
442
|
# already-declared precondition, one level wider than the
|
|
328
443
|
# existing bare-command-references-its-own-aggregate shape
|
|
329
444
|
# (`CommandBuilder#reference_named_given`): `SafeDepositBox`/
|
|
@@ -333,40 +448,54 @@ module Hecks
|
|
|
333
448
|
# — see `BluebookBuilder#aggregate`'s own comment for how that
|
|
334
449
|
# pool is threaded, and `docs/implemented/resolution-rules/chapter-given.md`
|
|
335
450
|
# for the full algorithm and its known limitations (a bare
|
|
336
|
-
# reference trusts its own author to have verified the
|
|
451
|
+
# reference trusts its own author to have verified the same
|
|
337
452
|
# canonical predicate applies — this mechanism does not, and
|
|
338
453
|
# cannot, check that itself; see that doc for which real corpus
|
|
339
454
|
# cases do and do not qualify).
|
|
340
455
|
#
|
|
341
|
-
# `declared_by:`
|
|
456
|
+
# `declared_by:` disambiguates the same description meaning two
|
|
342
457
|
# genuinely different predicates chapter-wide — real, live:
|
|
343
458
|
# `Account`'s own "customer is active" reads bare
|
|
344
|
-
# `customer.status` (a
|
|
459
|
+
# `customer.status` (a direct `reference_to Customer`); `ATMCard`'s
|
|
345
460
|
# own (shared onward with `CardPayment`/`ExternalTransfer`/
|
|
346
461
|
# `ScheduledPayment`/`Statement`) reads `account.customer.status`
|
|
347
|
-
# (reached
|
|
348
|
-
# genuinely different runtime path, correctly kept as the
|
|
462
|
+
# (reached through `Account`) — the identical business fact, a
|
|
463
|
+
# genuinely different runtime path, correctly kept as the same
|
|
349
464
|
# domain wording rather than invented a second spelling for "the
|
|
350
465
|
# same idea, one more hop away" (S10, ADR 0025's own "one idea,
|
|
351
466
|
# one spelling"). Omit it when the description is unambiguous
|
|
352
|
-
# chapter-wide (the common case, and the
|
|
353
|
-
# before this parameter existed) — required only once a
|
|
467
|
+
# chapter-wide (the common case, and the only case this took
|
|
468
|
+
# before this parameter existed) — required only once a second,
|
|
354
469
|
# textually-different canonical registers under the same
|
|
355
470
|
# description; see `reference_named_chapter_given`'s own
|
|
356
471
|
# ambiguity error for how that surfaces.
|
|
357
|
-
#
|
|
358
|
-
#
|
|
359
|
-
#
|
|
472
|
+
# Answers the `given` word through the table's `calls:` column —
|
|
473
|
+
# item #13's full metaprogrammed dispatch
|
|
474
|
+
# (slice 4b), same reasoning as `reference_to_impl` above:
|
|
475
|
+
# bootstrap-reachable, in `GenericDispatch::BOOTSTRAP_CALLS_FALLBACK`.
|
|
476
|
+
#
|
|
477
|
+
# @param description [String] the rule's description; also the name a sibling aggregate
|
|
478
|
+
# references it by when no block is given
|
|
479
|
+
# @param declared_by [Module, Symbol, String, nil] disambiguates which aggregate's own
|
|
480
|
+
# rule to reference, a bare constant, when more than one shares `description`; only
|
|
481
|
+
# meaningful with no block
|
|
482
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
483
|
+
# @return [void]
|
|
484
|
+
# @raise [Bluebook::DSL::Malformed] if given a block whose source cannot be extracted;
|
|
485
|
+
# given no block, the description is immediately ambiguous between more than one
|
|
486
|
+
# already-loaded aggregate with no `declared_by` to disambiguate; an unresolved
|
|
487
|
+
# reference defers instead, and may still raise once the whole chapter has loaded, if
|
|
488
|
+
# it then resolves to none or more than one candidate
|
|
360
489
|
def given_impl(description, declared_by: nil, &predicate)
|
|
361
490
|
return reference_named_chapter_given(description, declared_by: declared_by) unless predicate
|
|
362
491
|
|
|
363
492
|
named = build_rule(Given, description, predicate, owner_name: @name, word: "given",
|
|
364
493
|
extraction_failure: "its source could not be read, so no other runtime could ever evaluate it")
|
|
365
494
|
@named_givens[description] = named
|
|
366
|
-
#
|
|
495
|
+
# Write-through, first-declared-wins per owner — keyed by
|
|
367
496
|
# [description, this aggregate's own name], not description
|
|
368
|
-
# alone: two
|
|
369
|
-
#
|
|
497
|
+
# alone: two different aggregates independently declaring the
|
|
498
|
+
# same description are two distinct candidates a later bare
|
|
370
499
|
# reference chooses between (via `declared_by:` once there is
|
|
371
500
|
# more than one), never silently merged into one slot the way
|
|
372
501
|
# a bare description-only key would.
|
|
@@ -376,16 +505,16 @@ module Hecks
|
|
|
376
505
|
|
|
377
506
|
private
|
|
378
507
|
|
|
379
|
-
#
|
|
508
|
+
# Primitive 2 (RuleReference#resolve_owner_keyed) — see that
|
|
380
509
|
# method's own comment for the pool shape; the three branches
|
|
381
510
|
# below (exact owner / unambiguous single candidate / ambiguous)
|
|
382
|
-
# are this construct's
|
|
383
|
-
# `declared_by:` only exists here so far.
|
|
511
|
+
# are this construct's own refusal wording, not shared, since
|
|
512
|
+
# `declared_by:` only exists here so far. Unresolved (no
|
|
384
513
|
# candidate yet, or `declared_by:` naming an aggregate that
|
|
385
514
|
# hasn't declared it yet) is no longer a fourth branch that
|
|
386
|
-
# raises
|
|
515
|
+
# raises here — see `#pending_chapter_given`, below, for why:
|
|
387
516
|
# a chapter split across files can genuinely reference a
|
|
388
|
-
# precondition a
|
|
517
|
+
# precondition a later file declares, and "not found among
|
|
389
518
|
# what's loaded so far" cannot tell that apart from "genuinely
|
|
390
519
|
# never declared" until every file has.
|
|
391
520
|
def reference_named_chapter_given(description, declared_by:)
|
|
@@ -411,7 +540,7 @@ module Hecks
|
|
|
411
540
|
@named_givens[description] = named
|
|
412
541
|
end
|
|
413
542
|
|
|
414
|
-
# A
|
|
543
|
+
# A chapter may be split across files — the same reason a query
|
|
415
544
|
# hop's own cross-file target, a correlation key's own emitting
|
|
416
545
|
# command, and an event's own declared shape are all resolved
|
|
417
546
|
# once the whole chapter is assembled rather than refused the
|
|
@@ -419,18 +548,18 @@ module Hecks
|
|
|
419
548
|
# far (`BluebookBuilder.validate_assembled!`'s own comment).
|
|
420
549
|
#
|
|
421
550
|
# Unlike those, though, a chapter-given's resolved value is not
|
|
422
|
-
# a pass/fail check on an already-built IR — it
|
|
551
|
+
# a pass/fail check on an already-built IR — it is part of the
|
|
423
552
|
# referencing aggregate's own IR (`preconditions:` below), built
|
|
424
|
-
# and handed off the moment
|
|
553
|
+
# and handed off the moment this aggregate's own file finishes
|
|
425
554
|
# loading, long before a later file might declare the real
|
|
426
|
-
# thing. So this hands back a
|
|
555
|
+
# thing. So this hands back a placeholder `Given` — embedded
|
|
427
556
|
# exactly where the resolved one would be, by Ruby object
|
|
428
|
-
# reference, in this aggregate's own `preconditions`
|
|
429
|
-
# command in this
|
|
557
|
+
# reference, in this aggregate's own `preconditions` and in any
|
|
558
|
+
# command in this same aggregate that separately bare-references
|
|
430
559
|
# the same description (`CommandBuilder#given`'s own hash-chain
|
|
431
560
|
# read of this aggregate's `@named_givens`, the identical key) —
|
|
432
561
|
# and queues the request in `@chapter_pending_givens`.
|
|
433
|
-
# `BluebookBuilder#resolve_pending_chapter_givens!`
|
|
562
|
+
# `BluebookBuilder#resolve_pending_chapter_givens!` mutates this
|
|
434
563
|
# exact object in place, once every file has loaded, so every
|
|
435
564
|
# existing reference to it (there is only ever the one object,
|
|
436
565
|
# never a copy) sees the resolved fields simultaneously. Safe
|
|
@@ -448,23 +577,39 @@ module Hecks
|
|
|
448
577
|
|
|
449
578
|
public
|
|
450
579
|
|
|
451
|
-
#
|
|
452
|
-
#
|
|
453
|
-
#
|
|
580
|
+
# Declares a rule the whole aggregate must satisfy, checked after every command, before
|
|
581
|
+
# save.
|
|
582
|
+
#
|
|
583
|
+
# The aggregate boundary is what an invariant defines (S10, ADR
|
|
584
|
+
# 0025 — "Rules") — the same check a value object's already gets
|
|
454
585
|
# (`ValueObjectBuilder#invariant`, whose own shape this mirrors
|
|
455
|
-
# exactly).
|
|
456
|
-
# an aggregate-level rule had nowhere to live, so "the balance
|
|
586
|
+
# exactly). Without an aggregate-level rule, "the balance
|
|
457
587
|
# never goes negative" was three different `given`/`ensures`
|
|
458
588
|
# texts across banking's six balance-moving commands, and the
|
|
459
589
|
# four that only increase it said nothing at all — completeness
|
|
460
590
|
# depended on someone noticing which commands could decrease it.
|
|
461
|
-
#
|
|
462
|
-
#
|
|
591
|
+
#
|
|
592
|
+
# Answers the `invariant` word through the table's `calls:`
|
|
593
|
+
# column — item #13's full metaprogrammed
|
|
594
|
+
# dispatch (slice 4b), same reasoning as `given_impl` above.
|
|
595
|
+
#
|
|
596
|
+
# @param description [String] the rule's description
|
|
597
|
+
# @yield the predicate body; evaluated for its extracted source, never called directly
|
|
598
|
+
# @return [void]
|
|
599
|
+
# @raise [Bluebook::DSL::Malformed] if the block's source could not be extracted
|
|
463
600
|
def invariant_impl(description, &predicate)
|
|
464
601
|
@invariants << build_rule(Invariant, description, predicate, owner_name: @name, word: "invariant",
|
|
465
602
|
extraction_failure: "it would be a rule the IR cannot carry")
|
|
466
603
|
end
|
|
467
604
|
|
|
605
|
+
# Assembles every declared attribute, construct and rule into an `Aggregate`, after
|
|
606
|
+
# draining pending commands/queries/entities and running every `seal_*` check.
|
|
607
|
+
#
|
|
608
|
+
# @return [Bluebook::Aggregate] the built aggregate
|
|
609
|
+
# @raise [Bluebook::DSL::Malformed] if identity resolution or any `seal_*` check fails —
|
|
610
|
+
# a mutation or query naming an undeclared field, an inconsistent default, a lifecycle
|
|
611
|
+
# guard with no lifecycle, a lifecycle-field mutation outside a transition, a projected
|
|
612
|
+
# field naming an undeclared reference, or a correction targeting an unreferenced field
|
|
468
613
|
def build
|
|
469
614
|
drain_pending!
|
|
470
615
|
resolve_pending_identity!
|
|
@@ -493,13 +638,27 @@ module Hecks
|
|
|
493
638
|
provenance: @provenance
|
|
494
639
|
)
|
|
495
640
|
|
|
496
|
-
# After the IR exists, on purpose : a reference is declared
|
|
641
|
+
# After the IR exists, on purpose : a reference is declared in the
|
|
497
642
|
# aggregate, and the aggregate the IR graph knows is `ir`, not the
|
|
498
643
|
# builder.
|
|
499
644
|
stamp_references(ir)
|
|
500
645
|
ir
|
|
501
646
|
end
|
|
502
647
|
|
|
648
|
+
# Evaluates an `aggregate` block against a fresh builder and returns the built aggregate.
|
|
649
|
+
#
|
|
650
|
+
# @param name [String] the aggregate's name
|
|
651
|
+
# @param chapter_named_givens [Hash{String => Hash{String => Bluebook::Given}}] the
|
|
652
|
+
# chapter-wide given pool
|
|
653
|
+
# @param chapter_pending_givens [Array<Hash>] unresolved chapter-wide bare given
|
|
654
|
+
# references
|
|
655
|
+
# @param chapter_entity_named_givens [Hash{String => Hash{String => Bluebook::Given}}]
|
|
656
|
+
# the chapter-wide, entity-scoped given pool
|
|
657
|
+
# @param chapter_entity_pending_givens [Array<Hash>] unresolved chapter-wide,
|
|
658
|
+
# entity-scoped bare given references
|
|
659
|
+
# @yield the aggregate body, evaluated with the builder as `self`; may be omitted
|
|
660
|
+
# @return [Bluebook::Aggregate] the built aggregate
|
|
661
|
+
# @raise [Bluebook::DSL::Malformed] if the body fails any check `#build` raises
|
|
503
662
|
def self.build(name, chapter_named_givens: {}, chapter_pending_givens: [],
|
|
504
663
|
chapter_entity_named_givens: {}, chapter_entity_pending_givens: [], &block)
|
|
505
664
|
builder = new(name, chapter_named_givens: chapter_named_givens, chapter_pending_givens: chapter_pending_givens,
|
|
@@ -511,14 +670,14 @@ module Hecks
|
|
|
511
670
|
|
|
512
671
|
private
|
|
513
672
|
|
|
514
|
-
#
|
|
515
|
-
# immediately,
|
|
516
|
-
# `instance_eval
|
|
673
|
+
# Deferred construction — `entity`/`command`/`query` queue a descriptor rather than
|
|
674
|
+
# building immediately, inline, the moment their own DSL line runs during
|
|
675
|
+
# `instance_eval`. Building immediately would mean a command's own resolution (`sets
|
|
517
676
|
# :field` importing the owner's own attribute, `given("desc")`
|
|
518
677
|
# referencing an aggregate-level precondition, a query's own
|
|
519
678
|
# positional-param resolution) only ever saw whatever `@entities`/
|
|
520
|
-
# `attributes`/`@named_givens`/`@value_objects` held
|
|
521
|
-
#
|
|
679
|
+
# `attributes`/`@named_givens`/`@value_objects` held as of that
|
|
680
|
+
# exact textual line — never what the aggregate's block would go
|
|
522
681
|
# on to declare after it. Three real, confirmed cases in the
|
|
523
682
|
# self-hosted meta-domain violate the "declare before you
|
|
524
683
|
# reference" convention every other resolution rule relies on
|
|
@@ -526,26 +685,26 @@ module Hecks
|
|
|
526
685
|
# Member/Dispatch — see docs/resolution-rules/
|
|
527
686
|
# implicit-append-fields.md's own "Known limitations").
|
|
528
687
|
#
|
|
529
|
-
# This is the
|
|
530
|
-
#
|
|
688
|
+
# This is the same move `BluebookBuilder` already makes one level
|
|
689
|
+
# up, at the chapter level — build every aggregate first, then run
|
|
531
690
|
# cross-referential validation (`validate_query_hops!`,
|
|
532
691
|
# `validate_projected_fields!`, `validate_no_bidirectional_
|
|
533
692
|
# references!`) once `@aggregates` is fully populated — extended
|
|
534
|
-
# one level down: `entity`/`command`/`query` now only
|
|
693
|
+
# one level down: `entity`/`command`/`query` now only queue a
|
|
535
694
|
# descriptor (`@pending_entities`/`@pending_commands`/
|
|
536
695
|
# `@pending_queries`, each preserving its own declared order),
|
|
537
|
-
# and `#build` drains them here, in this exact order,
|
|
696
|
+
# and `#build` drains them here, in this exact order, before any
|
|
538
697
|
# of the existing `seal_*` validations (which already assume
|
|
539
698
|
# `@commands`/`@entities`/`@queries` are the real, final, built
|
|
540
|
-
# objects) — entities
|
|
699
|
+
# objects) — entities first and fully, since a command's own
|
|
541
700
|
# `sets :list, append: {...}` needs a list's element entity
|
|
542
701
|
# already built (`.attributes` populated) to resolve against, not
|
|
543
702
|
# just named.
|
|
544
703
|
#
|
|
545
704
|
# `attribute`/`value_object`/`identified_by`/`given` (block form)
|
|
546
|
-
# are
|
|
705
|
+
# are not deferred — they still build eagerly during
|
|
547
706
|
# `instance_eval`, unchanged. Nothing reads `@entities`/
|
|
548
|
-
# `@commands`/`@queries` from anywhere
|
|
707
|
+
# `@commands`/`@queries` from anywhere other than `#build` and its
|
|
549
708
|
# own private helpers (checked directly), so nothing else in this
|
|
550
709
|
# file needed to change for this to be safe.
|
|
551
710
|
def drain_pending!
|
|
@@ -583,7 +742,7 @@ module Hecks
|
|
|
583
742
|
@value_objects << value_object
|
|
584
743
|
end
|
|
585
744
|
|
|
586
|
-
#
|
|
745
|
+
# Legacy — see `has_many`/`has_one`/`belongs_to`'s own comment;
|
|
587
746
|
# byte-identical to what those three did before this slice.
|
|
588
747
|
def legacy_has_many(type, as:, optional: false)
|
|
589
748
|
plural = Naming.demodulise(type)
|