hecks 2.8.0 → 3.0.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 +50 -7
- 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 +32 -100
- 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 +10 -32
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +34 -108
- data/lib/hecks/adapters/driven/postgres/shared_connection.rb +180 -0
- data/lib/hecks/adapters/driven/postgres.rb +149 -136
- data/lib/hecks/adapters/driven/postgres_era.adapter +4 -15
- data/lib/hecks/adapters/driven/prism.rb +48 -38
- 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 +76 -62
- 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 +32 -142
- 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/bench/cli.rb +62 -0
- data/lib/hecks/bench/environment.rb +71 -0
- data/lib/hecks/bench/postgres_probe.rb +30 -0
- data/lib/hecks/bench/report.rb +76 -0
- data/lib/hecks/bench/ruby_runner.rb +93 -0
- data/lib/hecks/bench/run.rb +55 -0
- data/lib/hecks/bench/rust_runner.rb +101 -0
- data/lib/hecks/bench/stats.rb +46 -0
- data/lib/hecks/bench/suite.rb +98 -0
- data/lib/hecks/bench/workload.rb +89 -0
- data/lib/hecks/bench.rb +17 -0
- 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 +56 -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 +39 -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 +11 -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 +16 -50
- data/lib/hecks/bluebook/command.rb +23 -81
- data/lib/hecks/bluebook/domain_port.rb +49 -67
- 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 +96 -708
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +63 -169
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +3 -1
- 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 +42 -89
- 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 +148 -197
- 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 +38 -43
- 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 +46 -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 +39 -661
- data/lib/hecks/bluebook/hexagon.rb +18 -33
- 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 +25 -176
- data/lib/hecks/bluebook/meta_validator/shapes.rb +53 -267
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +47 -306
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +7 -43
- data/lib/hecks/bluebook/meta_validator/verdict_cache.rb +246 -0
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +6 -18
- data/lib/hecks/bluebook/meta_validator.rb +87 -352
- data/lib/hecks/bluebook/model_check/client_profile.rb +6 -21
- data/lib/hecks/bluebook/model_check.rb +122 -596
- data/lib/hecks/bluebook/pattern_subset.rb +49 -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 +27 -51
- data/lib/hecks/bluebook/read_model.rb +57 -81
- data/lib/hecks/bluebook/reference.rb +7 -41
- data/lib/hecks/bluebook/smoke_test.rb +9 -99
- 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/canonical_json.rb +30 -0
- data/lib/hecks/chapters.rb +102 -0
- data/lib/hecks/cli/behaviors.rb +61 -0
- data/lib/hecks/cli/canonicalise.rb +25 -0
- data/lib/hecks/cli/check_engine_agreement.rb +32 -0
- data/lib/hecks/cli/check_era.rb +88 -0
- data/lib/hecks/cli/console.rb +66 -0
- data/lib/hecks/cli/deploy_template_diff.rb +40 -0
- data/lib/hecks/cli/doc_coverage.rb +32 -0
- data/lib/hecks/cli/document.rb +5 -23
- data/lib/hecks/cli/expression_projection.rb +41 -0
- data/lib/hecks/cli/follow.rb +88 -0
- data/lib/hecks/cli/history.rb +44 -0
- data/lib/hecks/cli/ir.rb +4 -6
- data/lib/hecks/cli/mcp.rb +5 -6
- data/lib/hecks/cli/model_check.rb +24 -88
- data/lib/hecks/cli/pattern_cases.rb +47 -0
- data/lib/hecks/cli/present.rb +39 -0
- data/lib/hecks/cli/project_cli.rb +223 -64
- data/lib/hecks/cli/project_diagrams.rb +4 -10
- data/lib/hecks/cli/project_glossary.rb +44 -0
- data/lib/hecks/cli/reference.rb +46 -0
- data/lib/hecks/cli/refresh_projections.rb +44 -0
- data/lib/hecks/cli/refresh_rspec_runtime_baseline.rb +150 -0
- data/lib/hecks/cli/release.rb +70 -0
- data/lib/hecks/cli/release_gem.rb +59 -0
- data/lib/hecks/cli/rspec_io_parallel_files.rb +137 -0
- data/lib/hecks/cli/rspec_shard_files.rb +66 -0
- data/lib/hecks/cli/run.rb +15 -61
- data/lib/hecks/cli/seed_semantics_corpus.rb +71 -0
- data/lib/hecks/cli/serve_query_ir_mcp.rb +30 -0
- data/lib/hecks/cli/shape.rb +70 -0
- data/lib/hecks/cli/smoke_http.rb +230 -0
- data/lib/hecks/cli/smoke_test.rb +7 -19
- data/lib/hecks/cli/spec_example.rb +27 -0
- data/lib/hecks/cli/statements.rb +32 -0
- data/lib/hecks/cli/stores.rb +4 -6
- data/lib/hecks/cli/stress_concurrency_specs.rb +177 -0
- data/lib/hecks/cli.rb +216 -73
- data/lib/hecks/codemod.rb +324 -0
- data/lib/hecks/construct.rb +8 -34
- data/lib/hecks/corpus.rb +423 -0
- data/lib/hecks/deploy/bluebook/deploy.bluebook +617 -158
- data/lib/hecks/deploy/bluebook/deploy.hecksagon +41 -11
- data/lib/hecks/deploy/oidc.json +82 -2
- data/lib/hecks/doc/reference.rb +351 -0
- data/lib/hecks/doors/cli_door.rb +196 -0
- data/lib/hecks/doors/cli_runner.rb +297 -0
- data/lib/hecks/{facade → doors}/command_request.rb +14 -34
- data/lib/hecks/doors/handle.rb +206 -0
- data/lib/hecks/doors/json_door.rb +132 -0
- data/lib/hecks/doors/launcher_options.rb +129 -0
- data/lib/hecks/{cli → doors}/mcp_door.rb +25 -120
- data/lib/hecks/doors/mcp_door_scope.rb +97 -0
- data/lib/hecks/doors/ruby_door/aggregate_door.rb +162 -0
- data/lib/hecks/doors/ruby_door/chapter.rb +72 -0
- data/lib/hecks/doors/ruby_door.rb +77 -0
- data/lib/hecks/doors.rb +61 -0
- 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 +5 -72
- data/lib/hecks/engine_agreement.rb +156 -0
- data/lib/hecks/facade.rb +23 -51
- data/lib/hecks/forms/app.rb +21 -76
- data/lib/hecks/forms/banking_presentation.rb +50 -0
- 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 +31 -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/fuzzing/bounded_exhaustive_expressions.rb +393 -0
- data/lib/hecks/fuzzing/combination_miner.rb +86 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +214 -0
- data/lib/hecks/fuzzing/coverage_campaign.rb +143 -0
- data/lib/hecks/fuzzing/differential.rb +156 -0
- data/lib/hecks/fuzzing/domain_generator.rb +615 -0
- data/lib/hecks/fuzzing/era_boundary.rb +71 -0
- data/lib/hecks/fuzzing/form_census.rb +136 -0
- data/lib/hecks/fuzzing/generated_domain_check.rb +93 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +64 -0
- data/lib/hecks/fuzzing/isolated_boot.rb +301 -0
- data/lib/hecks/fuzzing/nondeterministic.rb +51 -0
- data/lib/hecks/fuzzing/outside_world.rb +45 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +102 -0
- data/lib/hecks/fuzzing/properties/corrections.rb +88 -0
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +314 -0
- data/lib/hecks/fuzzing/properties/guards.rb +190 -0
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +300 -0
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +93 -0
- data/lib/hecks/fuzzing/properties/outbox.rb +89 -0
- data/lib/hecks/fuzzing/properties/querying.rb +175 -0
- data/lib/hecks/fuzzing/properties.rb +162 -0
- data/lib/hecks/fuzzing/qa_settings.rb +123 -0
- data/lib/hecks/fuzzing/replay.rb +505 -0
- data/lib/hecks/fuzzing/rotation_priority.rb +48 -0
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +108 -0
- data/lib/hecks/fuzzing/self_consistency.rb +339 -0
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +385 -0
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +132 -0
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +95 -0
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +77 -0
- data/lib/hecks/fuzzing/sequence_generator/query_binding.rb +109 -0
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +237 -0
- data/lib/hecks/fuzzing/sequence_generator.rb +189 -0
- data/lib/hecks/fuzzing/shrinker.rb +166 -0
- data/lib/hecks/fuzzing/structural_skips.rb +33 -0
- data/lib/hecks/fuzzing/sweep_depth.rb +43 -0
- data/lib/hecks/fuzzing/target_capabilities.rb +163 -0
- data/lib/hecks/fuzzing/value_generator.rb +216 -0
- data/lib/hecks/fuzzing.rb +23 -0
- data/lib/hecks/grammar/evolve.rb +336 -0
- 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/hecks/adapters/codebase/codemods.rb +45 -0
- data/lib/hecks/hecks/adapters/codebase/conformance.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/corpus_tasks.rb +202 -0
- data/lib/hecks/hecks/adapters/codebase/gem_registry.rb +80 -0
- data/lib/hecks/hecks/adapters/codebase/kernel_tables.rb +54 -0
- data/lib/hecks/hecks/adapters/codebase/language.rb +240 -0
- data/lib/hecks/hecks/adapters/codebase/npm_registry.rb +102 -0
- data/lib/hecks/hecks/adapters/codebase/publishing.rb +142 -0
- data/lib/hecks/hecks/adapters/codebase/regeneration.rb +67 -0
- data/lib/hecks/hecks/adapters/codebase/release_facts.rb +90 -0
- data/lib/hecks/hecks/adapters/codebase/ruby_child.rb +83 -0
- data/lib/hecks/hecks/adapters/codebase/secret_vault.rb +36 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.adapter +7 -0
- data/lib/hecks/hecks/adapters/codebase/source_tree.rb +189 -0
- data/lib/hecks/hecks/adapters/codebase/sqlite_fixture.rb +71 -0
- data/lib/hecks/hecks/adapters/codebase/style.rb +171 -0
- data/lib/hecks/hecks/adapters/codebase/test_runner.rb +55 -0
- data/lib/hecks/hecks/adapters/codebase/test_suite.rb +165 -0
- data/lib/hecks/hecks/adapters/codebase/tree.rb +111 -0
- data/lib/hecks/hecks/adapters/console_capture.rb +79 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/deploy_toolchain.rb +108 -0
- data/lib/hecks/hecks/adapters/git.adapter +7 -0
- data/lib/hecks/hecks/adapters/git.rb +102 -0
- data/lib/hecks/hecks/adapters/host_http.adapter +7 -0
- data/lib/hecks/hecks/adapters/host_http.rb +65 -0
- data/lib/hecks/hecks/adapters/in_process_boot.adapter +7 -0
- data/lib/hecks/hecks/adapters/in_process_boot.rb +197 -0
- data/lib/hecks/hecks/adapters/in_process_operations.rb +202 -0
- data/lib/hecks/hecks/adapters/journal_store/changes.rb +143 -0
- data/lib/hecks/hecks/adapters/journal_store/compaction.rb +145 -0
- data/lib/hecks/hecks/adapters/journal_store/edge_audit.rb +132 -0
- data/lib/hecks/hecks/adapters/journal_store/examination.rb +135 -0
- data/lib/hecks/hecks/adapters/journal_store/held_domain.rb +163 -0
- data/lib/hecks/hecks/adapters/journal_store/readings.rb +187 -0
- data/lib/hecks/hecks/adapters/journal_store.adapter +7 -0
- data/lib/hecks/hecks/adapters/journal_store.rb +64 -0
- data/lib/hecks/hecks/adapters/local_files.adapter +7 -0
- data/lib/hecks/hecks/adapters/local_files.rb +46 -0
- data/lib/hecks/hecks/adapters/pg_admin.adapter +7 -0
- data/lib/hecks/hecks/adapters/pg_admin.rb +245 -0
- data/lib/hecks/hecks/adapters/process_pool.adapter +7 -0
- data/lib/hecks/hecks/adapters/process_pool.rb +119 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.adapter +7 -0
- data/lib/hecks/hecks/adapters/rust_toolchain.rb +179 -0
- data/lib/hecks/hecks/adapters/rust_workspace.rb +150 -0
- data/lib/hecks/hecks/adapters/shell.adapter +7 -0
- data/lib/hecks/hecks/adapters/shell.rb +49 -0
- data/lib/hecks/hecks/adapters/terminal.adapter +7 -0
- data/lib/hecks/hecks/adapters/terminal.rb +74 -0
- data/lib/hecks/hecks/codebase.behaviors +172 -0
- data/lib/hecks/hecks/codebase.bluebook +2453 -0
- data/lib/hecks/hecks/context_map.hecksagon +13 -0
- data/lib/hecks/hecks/custodian.behaviors +179 -0
- data/lib/hecks/hecks/custodian.bluebook +1847 -0
- data/lib/hecks/hecks/deploy.behaviors +74 -0
- data/lib/hecks/hecks/deploy.world +12 -0
- data/lib/hecks/hecks/environments/memory.world +10 -0
- data/lib/hecks/hecks/governance.world +7 -0
- data/lib/hecks/hecks/hecks.behaviors +62 -0
- data/lib/hecks/hecks/hecks.bluebook +124 -0
- data/lib/hecks/hecks/hecks.hecksagon +390 -0
- data/lib/hecks/hecks/hecks.world +42 -0
- data/lib/hecks/ir.rb +14 -83
- data/lib/hecks/kernel_capabilities.rb +194 -0
- 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 +26 -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 +19 -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 +83 -198
- data/lib/hecks/language/hecksagon/adapter_binding.bluebook +3 -10
- data/lib/hecks/language/hecksagon/domain_port.bluebook +14 -19
- data/lib/hecks/language/hecksagon/hecksagon.bluebook +26 -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_stdio_guard.rb +4 -41
- data/lib/hecks/naming.rb +29 -137
- data/lib/hecks/persistence_legacy_fixture/regenerate.rb +161 -0
- data/lib/hecks/persistence_legacy_fixture.rb +113 -0
- 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 +44 -144
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +15 -115
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +20 -85
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +6 -25
- data/lib/hecks/ports/persistence/plugins/era/expected_era.rb +18 -24
- 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 +19 -76
- 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 +43 -127
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +13 -45
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +131 -555
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +13 -63
- data/lib/hecks/ports/persistence/plugins/era/translation/approval_file.rb +227 -0
- 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 +50 -47
- 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/translation.rb +1 -0
- 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 +47 -108
- 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/projection_files.rb +285 -0
- data/lib/hecks/projections/bootstrap_table.rb +12 -44
- 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 +40 -207
- data/lib/hecks/projections/deploy/lambda.rb +191 -708
- 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/preview.sh.erb +1 -1
- 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 +3 -59
- data/lib/hecks/projections/deploy/preview.rb +2 -67
- data/lib/hecks/projections/deploy/scripts/deploy-service.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts/expected-era.header +1 -1
- data/lib/hecks/projections/deploy/scripts/hosting.mk.tmpl +3 -3
- data/lib/hecks/projections/deploy/scripts/settings.rb +3 -25
- data/lib/hecks/projections/deploy/scripts/smoke-after-deploy.sh.tmpl +1 -1
- data/lib/hecks/projections/deploy/scripts.rb +4 -57
- data/lib/hecks/projections/deploy/shared.rb +48 -147
- 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 +22 -584
- 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 +16 -43
- data/lib/hecks/projections/model.rb +12 -51
- data/lib/hecks/projections/oidc.rb +12 -56
- data/lib/hecks/projections/parser_table.rb +13 -32
- data/lib/hecks/projections/reference.rb +4 -18
- data/lib/hecks/projections/rust_vocabulary.rb +13 -251
- 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 +154 -400
- 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/quality_control/adapters/agent.adapter +7 -0
- data/lib/hecks/quality_control/adapters/agent.rb +58 -0
- data/lib/hecks/quality_control/adapters/angle_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/angle_tools.rb +15 -0
- data/lib/hecks/quality_control/adapters/clearance_recorder.rb +36 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/clearance_tools.rb +17 -0
- data/lib/hecks/quality_control/adapters/git_pr.adapter +7 -0
- data/lib/hecks/quality_control/adapters/git_pr.rb +226 -0
- data/lib/hecks/quality_control/adapters/github_checks.adapter +9 -0
- data/lib/hecks/quality_control/adapters/github_checks.rb +83 -0
- data/lib/hecks/quality_control/adapters/github_ci_webhook.rb +56 -0
- data/lib/hecks/quality_control/adapters/github_issues.adapter +7 -0
- data/lib/hecks/quality_control/adapters/github_issues.rb +49 -0
- data/lib/hecks/quality_control/adapters/qa_tool.rb +59 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/sweep_tools.rb +51 -0
- data/lib/hecks/quality_control/adapters/target_tools.adapter +7 -0
- data/lib/hecks/quality_control/adapters/target_tools.rb +43 -0
- data/lib/hecks/quality_control/cli/child.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_concurrency_racer.rb +45 -0
- data/lib/hecks/quality_control/cli/qa_discover_external_domains.rb +248 -0
- data/lib/hecks/quality_control/cli/qa_domain_novelty.rb +163 -0
- data/lib/hecks/quality_control/cli/qa_generated_domains.rb +450 -0
- data/lib/hecks/quality_control/cli/qa_log_bug.rb +195 -0
- data/lib/hecks/quality_control/cli/qa_mine_combinations.rb +220 -0
- data/lib/hecks/quality_control/cli/qa_open_pr.rb +305 -0
- data/lib/hecks/quality_control/cli/qa_postgres_migrate.rb +154 -0
- data/lib/hecks/quality_control/cli/qa_postgres_role.rb +159 -0
- data/lib/hecks/quality_control/cli/qa_pr_check.rb +232 -0
- data/lib/hecks/quality_control/cli/qa_seed_angles.rb +142 -0
- data/lib/hecks/quality_control/cli/qa_seed_targets.rb +69 -0
- data/lib/hecks/quality_control/cli/qa_sweep/all_mode.rb +280 -0
- data/lib/hecks/quality_control/cli/qa_sweep/checks.rb +329 -0
- data/lib/hecks/quality_control/cli/qa_sweep/dials.rb +92 -0
- data/lib/hecks/quality_control/cli/qa_sweep/finding_report.rb +117 -0
- data/lib/hecks/quality_control/cli/qa_sweep/parsing.rb +175 -0
- data/lib/hecks/quality_control/cli/qa_sweep/release_mode.rb +60 -0
- data/lib/hecks/quality_control/cli/qa_sweep.rb +611 -0
- data/lib/hecks/quality_control/cli/qa_tick.rb +274 -0
- data/lib/hecks/quality_control/quality_control.bluebook +2224 -0
- data/lib/hecks/quality_control/quality_control.hecksagon +13 -0
- data/lib/hecks/quality_control/quality_control.ports.hecksagon +54 -0
- data/lib/hecks/query_ir.rb +346 -0
- data/lib/hecks/query_ir_mcp.rb +198 -0
- 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 +5 -8
- data/lib/hecks/release/runner/clean_tree.rb +42 -0
- data/lib/hecks/release/runner/commands.rb +2 -4
- data/lib/hecks/release/runner/console.rb +6 -0
- data/lib/hecks/release/runner/gem_publisher.rb +15 -9
- data/lib/hecks/release/runner/npm_publisher.rb +9 -53
- data/lib/hecks/release/runner/preflight.rb +29 -20
- data/lib/hecks/release/runner/published.rb +8 -26
- data/lib/hecks/release/runner/tagger.rb +31 -23
- data/lib/hecks/release/runner.rb +60 -38
- data/lib/hecks/rendering.rb +4 -26
- data/lib/hecks/router/namespace_installer.rb +15 -39
- data/lib/hecks/router.rb +4 -8
- data/lib/hecks/runtime/adapter_lookup.rb +124 -0
- 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 +27 -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 +50 -104
- data/lib/hecks/runtime/dispatcher.rb +134 -243
- 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 +14 -70
- data/lib/hecks/runtime/invocation.rb +28 -164
- data/lib/hecks/runtime/loader.rb +109 -194
- data/lib/hecks/runtime/outbox.rb +39 -262
- data/lib/hecks/runtime/policy_interpreter.rb +43 -202
- data/lib/hecks/runtime/port_operation_interpreter.rb +28 -97
- data/lib/hecks/runtime/query_interpreter.rb +122 -187
- data/lib/hecks/runtime/reaction_invocation.rb +36 -138
- data/lib/hecks/runtime/reaction_outcome.rb +89 -0
- 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 +28 -87
- data/lib/hecks/runtime/registry/saga_persistence.rb +10 -102
- data/lib/hecks/runtime/registry/verification.rb +213 -227
- data/lib/hecks/runtime/registry/world_defaults.rb +4 -53
- data/lib/hecks/runtime/registry.rb +156 -247
- data/lib/hecks/runtime/remote_dispatcher.rb +68 -154
- 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 +162 -760
- data/lib/hecks/runtime/value/entity_list_coercion.rb +26 -219
- data/lib/hecks/runtime/value/field_checks.rb +172 -0
- data/lib/hecks/runtime/value.rb +15 -84
- data/lib/hecks/runtime.rb +35 -69
- data/lib/hecks/rust_build/conformance.rb +158 -0
- data/lib/hecks/rust_build/conformance_fuzz.rb +71 -0
- data/lib/hecks/rust_build/coverage.rb +218 -0
- data/lib/hecks/rust_build/native_build.rb +124 -0
- data/lib/hecks/rust_build/project_rust/root_mod.rb +120 -0
- data/lib/hecks/rust_build/project_rust.rb +332 -0
- data/lib/hecks/rust_build/wasm.rb +105 -0
- data/lib/hecks/rust_build/wasm_browser.rb +113 -0
- data/lib/hecks/rust_build.rb +144 -0
- data/lib/hecks/storehouse.rb +155 -582
- data/lib/hecks/tenancy/bluebook/tenancy.bluebook +10 -34
- data/lib/hecks/tenancy/bluebook/tenancy.hecksagon +6 -23
- data/lib/hecks/three_zero/forms.yml +127 -0
- data/lib/hecks/three_zero.rb +12 -0
- data/lib/hecks/tools/argument_gate_matrix.rb +316 -0
- data/lib/hecks/tools/comment_style.rb +1070 -0
- data/lib/hecks/tools/corpus_report.rb +87 -0
- data/lib/hecks/tools/deploy_recipe.rb +255 -0
- data/lib/hecks/tools/deploy_recipe_lint.rb +282 -0
- data/lib/hecks/tools/drop_implicit_append_fields.rb +95 -0
- data/lib/hecks/tools/era_projection_backfill.rb +94 -0
- data/lib/hecks/tools/era_reattest.rb +146 -0
- data/lib/hecks/tools/evolve_run.rb +189 -0
- data/lib/hecks/tools/fuzz_sweep.rb +373 -0
- data/lib/hecks/tools/heki_compaction.rb +100 -0
- data/lib/hecks/tools/hoist_local_givens.rb +246 -0
- data/lib/hecks/tools/journal_compaction.rb +89 -0
- data/lib/hecks/tools/oidc_manifests.rb +70 -0
- data/lib/hecks/tools/query_ir_run.rb +81 -0
- data/lib/hecks/tools/regeneration_run.rb +166 -0
- data/lib/hecks/tools/rust_comment_style.rb +642 -0
- data/lib/hecks/tools/sequence_script.rb +62 -0
- data/lib/hecks/tools/tail_merge.rb +117 -0
- data/lib/hecks/tools/tenant_provisioning.rb +142 -0
- data/lib/hecks/tools/translation_audit.rb +208 -0
- data/lib/hecks/tools/translation_scaffold.rb +131 -0
- data/lib/hecks/tools.rb +86 -0
- 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 +52 -90
- 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
- data/qa/settings.yml +66 -0
- data/rust/.cargo/config.toml +6 -0
- data/rust/.gitignore +2 -0
- data/rust/Cargo.lock +54 -0
- data/rust/Cargo.toml +61 -0
- data/rust/build/.cargo/config.toml +3 -0
- data/rust/build/.gitignore +1 -0
- data/rust/build/Cargo.lock +7 -0
- data/rust/build/Cargo.toml +19 -0
- data/rust/build/src/build_artifact.rs +47 -0
- data/rust/build/src/cargo_sync.rs +248 -0
- data/rust/build/src/json.rs +421 -0
- data/rust/build/src/lineage_pass.rs +285 -0
- data/rust/build/src/main.rs +51 -0
- data/rust/build/src/optional_pass.rs +150 -0
- data/rust/build/src/pipeline.rs +264 -0
- data/rust/build/src/reserved_names.rs +76 -0
- data/rust/build/src/resolve.rs +124 -0
- data/rust/build/src/root.rs +25 -0
- data/rust/build/src/sidecars.rs +73 -0
- data/rust/build/src/subprocess.rs +51 -0
- data/rust/build/src/tmp.rs +31 -0
- data/rust/codegen/.cargo/config.toml +3 -0
- data/rust/codegen/.gitignore +1 -0
- data/rust/codegen/Cargo.lock +7 -0
- data/rust/codegen/Cargo.toml +20 -0
- data/rust/codegen/src/attr.rs +32 -0
- data/rust/codegen/src/bridging.rs +481 -0
- data/rust/codegen/src/commands.rs +1226 -0
- data/rust/codegen/src/constraints.rs +109 -0
- data/rust/codegen/src/dependency_planning.rs +209 -0
- data/rust/codegen/src/domain_generator.rs +1288 -0
- data/rust/codegen/src/exemplar.rs +230 -0
- data/rust/codegen/src/expr_emitter.rs +87 -0
- data/rust/codegen/src/fielded.rs +192 -0
- data/rust/codegen/src/hecks_naming.rs +33 -0
- data/rust/codegen/src/json.rs +335 -0
- data/rust/codegen/src/json_codec.rs +697 -0
- data/rust/codegen/src/literal.rs +207 -0
- data/rust/codegen/src/main.rs +290 -0
- data/rust/codegen/src/manifest.rs +227 -0
- data/rust/codegen/src/mutations.rs +784 -0
- data/rust/codegen/src/naming.rs +286 -0
- data/rust/codegen/src/ports.rs +208 -0
- data/rust/codegen/src/prelude.rs +116 -0
- data/rust/codegen/src/queries.rs +786 -0
- data/rust/codegen/src/reactions.rs +532 -0
- data/rust/codegen/src/read_models.rs +697 -0
- data/rust/codegen/src/reference_specs.rs +40 -0
- data/rust/codegen/src/registry.rs +966 -0
- data/rust/codegen/src/reserved_names.rs +76 -0
- data/rust/codegen/src/shared.rs +93 -0
- data/rust/codegen/src/skip_reason.rs +25 -0
- data/rust/codegen/src/types.rs +362 -0
- data/rust/host/.cargo/config.toml +3 -0
- data/rust/host/.gitignore +1 -0
- data/rust/host/Cargo.lock +4297 -0
- data/rust/host/Cargo.toml +61 -0
- data/rust/host/HECKS_RELEASE +1 -0
- data/rust/host/rds-ca-bundle.pem +2736 -0
- data/rust/host/src/api.rs +1596 -0
- data/rust/host/src/approval.rs +652 -0
- data/rust/host/src/auth.rs +1287 -0
- data/rust/host/src/bin/expr_harness.rs +46 -0
- data/rust/host/src/bin/lineage_harness.rs +119 -0
- data/rust/host/src/bin/mint_harness.rs +135 -0
- data/rust/host/src/checkout.rs +265 -0
- data/rust/host/src/dispatch.rs +1443 -0
- data/rust/host/src/expr_json.rs +1117 -0
- data/rust/host/src/field_hints.rs +52 -0
- data/rust/host/src/ir.rs +733 -0
- data/rust/host/src/journal.rs +874 -0
- data/rust/host/src/lambda_client.rs +378 -0
- data/rust/host/src/log.rs +147 -0
- data/rust/host/src/main.rs +474 -0
- data/rust/host/src/mint.rs +1865 -0
- data/rust/host/src/payments/keystore.rs +330 -0
- data/rust/host/src/payments/tests.rs +1400 -0
- data/rust/host/src/payments.rs +767 -0
- data/rust/host/src/presentation.rs +698 -0
- data/rust/host/src/presentation_write.rs +1431 -0
- data/rust/host/src/query_step.rs +149 -0
- data/rust/host/src/rate_limit.rs +904 -0
- data/rust/host/src/reference_transform.rs +241 -0
- data/rust/host/src/reference_validate.rs +338 -0
- data/rust/host/src/resend.rs +296 -0
- data/rust/host/src/secrets.rs +106 -0
- data/rust/host/src/server.rs +663 -0
- data/rust/host/src/storage_shape.rs +175 -0
- data/rust/host/src/test_pg.rs +40 -0
- data/rust/host/src/ui_schema.rs +1351 -0
- data/rust/host/src/wasm_runner.rs +191 -0
- data/rust/host/src/web/newsletter/cooldown.rs +126 -0
- data/rust/host/src/web/newsletter/tests.rs +104 -0
- data/rust/host/src/web/newsletter.rs +310 -0
- data/rust/host/src/web/newsletter_send.rs +357 -0
- data/rust/host/src/web/registrations.rs +1505 -0
- data/rust/host/src/web.rs +2937 -0
- data/rust/lsp/.gitignore +1 -0
- data/rust/lsp/Cargo.lock +7 -0
- data/rust/lsp/Cargo.toml +16 -0
- data/rust/lsp/README.md +133 -0
- data/rust/lsp/src/diagnostics.rs +165 -0
- data/rust/lsp/src/json.rs +385 -0
- data/rust/lsp/src/main.rs +399 -0
- data/rust/lsp/src/outline.rs +177 -0
- data/rust/lsp/src/rpc.rs +67 -0
- data/rust/parser/.cargo/config.toml +5 -0
- data/rust/parser/.gitignore +1 -0
- data/rust/parser/Cargo.lock +7 -0
- data/rust/parser/Cargo.toml +21 -0
- data/rust/parser/src/build/closed_sets.rs +27 -0
- data/rust/parser/src/build/identity.rs +276 -0
- data/rust/parser/src/build/mod.rs +12 -0
- data/rust/parser/src/build/naming.rs +141 -0
- data/rust/parser/src/build/pattern_subset.rs +161 -0
- data/rust/parser/src/build/query_derive.rs +72 -0
- data/rust/parser/src/build/query_inference.rs +269 -0
- data/rust/parser/src/build/query_options.rs +100 -0
- data/rust/parser/src/build/read_model.rs +94 -0
- data/rust/parser/src/build/references.rs +32 -0
- data/rust/parser/src/canonical.rs +114 -0
- data/rust/parser/src/diag.rs +55 -0
- data/rust/parser/src/emit.rs +953 -0
- data/rust/parser/src/expr/ast_json.rs +147 -0
- data/rust/parser/src/expr/evaluator.rs +81 -0
- data/rust/parser/src/expr/mod.rs +84 -0
- data/rust/parser/src/expr/resolver.rs +497 -0
- data/rust/parser/src/ir.rs +387 -0
- data/rust/parser/src/keywords.rs +585 -0
- data/rust/parser/src/lex.rs +662 -0
- data/rust/parser/src/main.rs +252 -0
- data/rust/parser/src/parse/aggregate.rs +612 -0
- data/rust/parser/src/parse/chapter.rs +517 -0
- data/rust/parser/src/parse/command.rs +637 -0
- data/rust/parser/src/parse/domain_port.rs +127 -0
- data/rust/parser/src/parse/entity.rs +527 -0
- data/rust/parser/src/parse/file.rs +67 -0
- data/rust/parser/src/parse/hecksagon.rs +231 -0
- data/rust/parser/src/parse/lifecycle.rs +162 -0
- data/rust/parser/src/parse/mod.rs +1262 -0
- data/rust/parser/src/parse/policy.rs +87 -0
- data/rust/parser/src/parse/process_manager.rs +305 -0
- data/rust/parser/src/parse/query.rs +100 -0
- data/rust/parser/src/parse/read_model.rs +214 -0
- data/rust/parser/src/parse/value_object.rs +250 -0
- data/rust/parser/src/ruby_value.rs +422 -0
- data/rust/project/bridging.rb +257 -0
- data/rust/project/commands.rb +877 -0
- data/rust/project/constraints.rb +86 -0
- data/rust/project/dependency_planning.rb +178 -0
- data/rust/project/domain_generator.rb +980 -0
- data/rust/project/exemplar.rb +148 -0
- data/rust/project/expr_emitter.rb +72 -0
- data/rust/project/fielded.rb +141 -0
- data/rust/project/json_codec.rb +641 -0
- data/rust/project/mutations.rb +721 -0
- data/rust/project/naming.rb +205 -0
- data/rust/project/ports.rb +65 -0
- data/rust/project/queries.rb +493 -0
- data/rust/project/reactions.rb +317 -0
- data/rust/project/read_models.rb +690 -0
- data/rust/project/reference_specs.rb +27 -0
- data/rust/project/registry.rb +396 -0
- data/rust/project/skip_reason.rb +29 -0
- data/rust/project/types.rb +225 -0
- data/rust/project/write_if_changed.rb +83 -0
- data/rust/project.rb +30 -0
- data/rust/project_rust_pipeline.rb +383 -0
- data/rust/src/exemplar/commands.rs +279 -0
- data/rust/src/exemplar/constraints.rs +18 -0
- data/rust/src/exemplar/fielded.rs +280 -0
- data/rust/src/exemplar/json.rs +277 -0
- data/rust/src/exemplar/mod.rs +17 -0
- data/rust/src/exemplar/mutations.rs +80 -0
- data/rust/src/exemplar/queries.rs +33 -0
- data/rust/src/exemplar/reactions.rs +88 -0
- data/rust/src/exemplar/read_models.rs +48 -0
- data/rust/src/exemplar/registry.rs +166 -0
- data/rust/src/exemplar/types.rs +44 -0
- data/rust/src/kernel/attribute_shapes/composite.rs +88 -0
- data/rust/src/kernel/attribute_shapes/list.rs +18 -0
- data/rust/src/kernel/attribute_shapes/mod.rs +44 -0
- data/rust/src/kernel/attribute_shapes/optional.rs +9 -0
- data/rust/src/kernel/attribute_shapes/scalar.rs +39 -0
- data/rust/src/kernel/cli.rs +585 -0
- data/rust/src/kernel/dispatch.rs +1090 -0
- data/rust/src/kernel/expr.rs +458 -0
- data/rust/src/kernel/expression_operators/arithmetic.rs +128 -0
- data/rust/src/kernel/expression_operators/comparison.rs +48 -0
- data/rust/src/kernel/expression_operators/enumeration.rs +232 -0
- data/rust/src/kernel/expression_operators/logical.rs +13 -0
- data/rust/src/kernel/expression_operators/membership.rs +18 -0
- data/rust/src/kernel/expression_operators/mod.rs +68 -0
- data/rust/src/kernel/expression_operators/pattern_match.rs +73 -0
- data/rust/src/kernel/expression_operators/positional.rs +126 -0
- data/rust/src/kernel/expression_operators/presence.rs +101 -0
- data/rust/src/kernel/expression_operators/sign_test.rs +16 -0
- data/rust/src/kernel/expression_operators/sized.rs +35 -0
- data/rust/src/kernel/expression_operators/text.rs +79 -0
- data/rust/src/kernel/expression_operators/to_string.rs +18 -0
- data/rust/src/kernel/json.rs +779 -0
- data/rust/src/kernel/mod.rs +133 -0
- data/rust/src/kernel/named_query.rs +219 -0
- data/rust/src/kernel/orchestrate.rs +1810 -0
- data/rust/src/kernel/pattern.rs +307 -0
- data/rust/src/kernel/query_comparators.rs +206 -0
- data/rust/src/kernel/query_ordering.rs +103 -0
- data/rust/src/kernel/read_model.rs +914 -0
- data/rust/src/kernel/reference_lookup.rs +149 -0
- data/rust/src/kernel/repository.rs +468 -0
- data/rust/src/kernel/routing.rs +612 -0
- data/rust/src/kernel/vocab/aggregate_dispatch_order.rs +118 -0
- data/rust/src/kernel/vocab/entity_dispatch_order.rs +114 -0
- data/rust/src/kernel/vocab/field_hint.rs +70 -0
- data/rust/src/kernel/vocab/mod.rs +20 -0
- data/rust/src/kernel/vocab/query_comparator.rs +65 -0
- data/rust/src/kernel/vocab/refusal_template.rs +1592 -0
- data/rust/src/lib.rs +8 -0
- data/rust/src/main.rs +16 -0
- data/rust/web/.gitignore +1 -0
- data/rust/web/Cargo.lock +119 -0
- data/rust/web/Cargo.toml +18 -0
- data/rust/web/src/lib.rs +8 -0
- metadata +478 -12
- data/lib/hecks/facade/cli_door.rb +0 -178
- data/lib/hecks/facade/cli_runner.rb +0 -271
- data/lib/hecks/facade/handle.rb +0 -293
- data/lib/hecks/facade/json_door.rb +0 -247
- data/lib/hecks/facade/surface/aggregate_door.rb +0 -222
- data/lib/hecks/facade/surface/chapter.rb +0 -116
- data/lib/hecks/facade/surface.rb +0 -61
- data/lib/hecks/mcp_door_scope.rb +0 -176
|
@@ -2,52 +2,21 @@ module Hecks
|
|
|
2
2
|
module Bluebook
|
|
3
3
|
module DSL
|
|
4
4
|
class BluebookBuilder
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# runs once a chapter is fully assembled (reference targets, event
|
|
8
|
-
# shapes, correlation keys, query hops, projected fields,
|
|
9
|
-
# bidirectional-reference cycles), plus each one's own private
|
|
10
|
-
# helpers. Split out of bluebook_builder.rb (which keeps the DSL
|
|
11
|
-
# surface — chapter/aggregate/read_model/policy declaration,
|
|
12
|
-
# `#build`, and `self.build`) for the same reason
|
|
13
|
-
# AggregateBuilder::Sealing exists: one cohesive concern, filed by
|
|
14
|
-
# responsibility rather than by an arbitrary line count.
|
|
15
|
-
#
|
|
16
|
-
# `extend`ed onto BluebookBuilder rather than `include`d — every
|
|
17
|
-
# one of these is a pure function of its own explicit arguments
|
|
18
|
-
# (an assembled `Bluebook::Chapter`, an aggregate list, ...), never
|
|
19
|
-
# builder-instance state (no `@ivar` read anywhere in this
|
|
20
|
-
# module), and every caller reaches these as
|
|
21
|
-
# `BluebookBuilder.validate_assembled!(...)` class-level calls,
|
|
22
|
-
# `#build`'s own `self.class.validate_assembled!` included.
|
|
23
|
-
# `extend` is `include`'s class-level counterpart: the same "one
|
|
24
|
-
# class, its methods, filed across files" pattern
|
|
25
|
-
# `Runtime::Registry` already uses (`include Verification`), just
|
|
26
|
-
# turning each method into a class (singleton) method instead of
|
|
27
|
-
# an instance method, the same role `def self.foo` fills on a
|
|
28
|
-
# class that keeps its methods in one file.
|
|
5
|
+
# Whole-chapter, cross-aggregate checks that `#build` runs once a chapter is assembled.
|
|
6
|
+
# Extended onto BluebookBuilder: pure functions of their arguments, no builder state.
|
|
29
7
|
module Validation
|
|
30
|
-
# Runs every whole-chapter check against one
|
|
8
|
+
# Runs every whole-chapter check against one assembled chapter.
|
|
9
|
+
# Public so `MetaValidator.judge_deferred!` can call it with no builder instance.
|
|
31
10
|
#
|
|
32
|
-
#
|
|
33
|
-
# assembled `Bluebook::Chapter`, callable both from `#build` (right
|
|
34
|
-
# after assembly) and from `MetaValidator.judge_deferred!` (once, on
|
|
35
|
-
# a chapter whose files have all loaded — see `#build`'s own comment
|
|
36
|
-
# for why that split exists at all). Public, not
|
|
37
|
-
# `private_class_method`'d, for exactly that second caller —
|
|
38
|
-
# `MetaValidator` needs to reach this with no builder instance in
|
|
39
|
-
# hand, only the chapter `judge_deferred!` already read back out of
|
|
40
|
-
# the registry.
|
|
41
|
-
#
|
|
42
|
-
# @param bluebook [Bluebook::Chapter] the fully assembled chapter to validate
|
|
11
|
+
# @param bluebook [Bluebook::Chapter] the assembled chapter to validate
|
|
43
12
|
# @return [void]
|
|
44
|
-
# @raise [Bluebook::DSL::Malformed] if any
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
# manager's `correlates_by` resolves to something other than a scalar field
|
|
13
|
+
# @raise [Bluebook::DSL::Malformed] if any check finds a violation
|
|
14
|
+
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a
|
|
15
|
+
# `correlates_by` resolves to something other than a scalar field
|
|
48
16
|
def validate_assembled!(bluebook)
|
|
49
|
-
|
|
50
|
-
# so an undeclared value object
|
|
17
|
+
validate_not_the_gem_module!(bluebook)
|
|
18
|
+
# an attribute type references its Shape, so an undeclared value object
|
|
19
|
+
# fails resolution
|
|
51
20
|
validate_reference_value_objects!(bluebook.aggregates)
|
|
52
21
|
validate_correlation_keys!(bluebook.process_managers, bluebook.aggregates)
|
|
53
22
|
validate_no_bidirectional_references!(bluebook.aggregates)
|
|
@@ -56,40 +25,18 @@ module Hecks
|
|
|
56
25
|
validate_with_projections!(bluebook.policies, bluebook.process_managers, bluebook.aggregates)
|
|
57
26
|
end
|
|
58
27
|
|
|
59
|
-
#
|
|
60
|
-
#
|
|
61
|
-
# hop can be checked, for exactly the reason
|
|
62
|
-
# validate_no_bidirectional_references! above already gives:
|
|
63
|
-
# `Bluebook.new` just stamped `hecks_owner` on every
|
|
64
|
-
# aggregate, so `Reference#resolve` finally has a chapter to
|
|
65
|
-
# walk. Before this line every target in the file (including
|
|
66
|
-
# ones declared above the aggregate doing the asking) would
|
|
67
|
-
# have resolved to nil.
|
|
28
|
+
# Hops resolve only now: `Bluebook.new` has just stamped `hecks_owner` on
|
|
29
|
+
# every aggregate.
|
|
68
30
|
infer_hop_query_arguments!(bluebook)
|
|
69
31
|
validate_query_hops!(bluebook)
|
|
70
32
|
|
|
71
|
-
#
|
|
72
|
-
# own reference cannot resolve until every aggregate in the
|
|
73
|
-
# chapter is real and owner-stamped (S12, ADR 0025).
|
|
33
|
+
# A `projects` reference needs the same owner-stamped aggregates to resolve (ADR 0025).
|
|
74
34
|
validate_projected_fields!(bluebook)
|
|
75
35
|
|
|
76
36
|
validate_provisions!(bluebook)
|
|
77
37
|
end
|
|
78
38
|
|
|
79
39
|
# Checks every `provides` row against `Capabilities::CONTRACTS`.
|
|
80
|
-
#
|
|
81
|
-
# **What a declared capability must name**. A `provides` row is only
|
|
82
|
-
# worth trusting in place of a name check if it is checked: an
|
|
83
|
-
# unknown capability, a missing or extra key, or a verb that is
|
|
84
|
-
# not this chapter's own command/query of the right kind would
|
|
85
|
-
# otherwise wire a role check to nothing, in silence.
|
|
86
|
-
#
|
|
87
|
-
# @param bluebook [Bluebook::Chapter] the chapter whose `provides` rows to check
|
|
88
|
-
# @return [void]
|
|
89
|
-
# @raise [Bluebook::DSL::Malformed] if a row names a capability
|
|
90
|
-
# `Capabilities::CONTRACTS` does not know, its keys do not match the
|
|
91
|
-
# capability's contract exactly, or a verb names no real command/query
|
|
92
|
-
# of the declared kind
|
|
93
40
|
def validate_provisions!(bluebook)
|
|
94
41
|
bluebook.provides.group_by(&:capability).each do |capability, rows|
|
|
95
42
|
contract = Capabilities::CONTRACTS.fetch(capability) do
|
|
@@ -107,17 +54,6 @@ module Hecks
|
|
|
107
54
|
end
|
|
108
55
|
end
|
|
109
56
|
|
|
110
|
-
# Checks that one `provides` row's verb names a real command or query.
|
|
111
|
-
#
|
|
112
|
-
# @param bluebook [Bluebook::Chapter] the chapter the row was declared on
|
|
113
|
-
# @param capability [String] the capability the row belongs to, such as
|
|
114
|
-
# `"authorization"`
|
|
115
|
-
# @param row [Bluebook::Chapter::Provision] the one declared row to check
|
|
116
|
-
# @param kind [Symbol] `:command`, `:query` or `:port_operation`, the kind of
|
|
117
|
-
# verb this row's key must name (from `Capabilities::CONTRACTS`)
|
|
118
|
-
# @return [void]
|
|
119
|
-
# @raise [Bluebook::DSL::Malformed] if `row.verb` does not split into a real
|
|
120
|
-
# aggregate and a member of the right kind that aggregate declares
|
|
121
57
|
def validate_provided_verb!(bluebook, capability, row, kind)
|
|
122
58
|
return validate_provided_port_operation!(bluebook, capability, row) if kind == :port_operation
|
|
123
59
|
|
|
@@ -129,18 +65,9 @@ module Hecks
|
|
|
129
65
|
"which names no #{kind} this chapter declares (spelled \"Aggregate.#{kind.capitalize}\")"
|
|
130
66
|
end
|
|
131
67
|
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
#
|
|
135
|
-
# verb has the `"Aggregate.Port.Operation"` shape and its aggregate is
|
|
136
|
-
# this chapter's; `Registry#verify!` checks the operation exists.
|
|
137
|
-
#
|
|
138
|
-
# @param bluebook [Bluebook::Chapter] the chapter the row was declared on
|
|
139
|
-
# @param capability [String] the capability the row belongs to
|
|
140
|
-
# @param row [Bluebook::Chapter::Provision] the one declared row to check
|
|
141
|
-
# @return [void]
|
|
142
|
-
# @raise [Bluebook::DSL::Malformed] if the verb is not `"Aggregate.Port.Operation"`
|
|
143
|
-
# or names an aggregate this chapter does not declare
|
|
68
|
+
# Only the verb's shape and aggregate are checkable here: the port is declared
|
|
69
|
+
# in the hecksagon, which attaches after the chapter is built. `Registry#verify!`
|
|
70
|
+
# checks the operation.
|
|
144
71
|
def validate_provided_port_operation!(bluebook, capability, row)
|
|
145
72
|
aggregate_name, port, operation = row.verb.split(".", 3)
|
|
146
73
|
return if bluebook.aggregate(aggregate_name) && port && operation && !operation.include?(".")
|
|
@@ -149,44 +76,13 @@ module Hecks
|
|
|
149
76
|
"which is not spelled \"Aggregate.Port.Operation\" over an aggregate this chapter declares"
|
|
150
77
|
end
|
|
151
78
|
|
|
152
|
-
# Names of the members a `provides` verb of the given kind may address.
|
|
153
|
-
#
|
|
154
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate the verb is scoped to
|
|
155
|
-
# @param kind [Symbol] `:command` or `:query`
|
|
156
|
-
# @return [Array<String>] every command's `hecks_name` when `kind` is `:command`,
|
|
157
|
-
# else every query's declared `name`
|
|
158
79
|
def provided_member_names(aggregate, kind)
|
|
159
80
|
kind == :command ? aggregate.commands.map(&:hecks_name) : aggregate.queries.map(&:name)
|
|
160
81
|
end
|
|
161
82
|
|
|
162
|
-
# Refuses an entity command that names
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
# the refusal message once read "references must target aggregate
|
|
166
|
-
# heads," a claim broader than what this actually checks.
|
|
167
|
-
#
|
|
168
|
-
# `CommandBuilder#reference_to` sets `references` only when the target's
|
|
169
|
-
# bare name equals the owner's ; anything else becomes a reference
|
|
170
|
-
# attribute. So on an aggregate command `references` is always a copy of
|
|
171
|
-
# that aggregate's own name, and looking it up in an index of aggregates
|
|
172
|
-
# is a tautology — that branch never refused anything and structurally
|
|
173
|
-
# could not. Verified across all eight golden chapters before deleting it.
|
|
174
|
-
#
|
|
175
|
-
# On a piece's command the owner is the entity, and an entity is not a
|
|
176
|
-
# head, so what this actually refuses is `reference_to <its own name>`
|
|
177
|
-
# written inside `entity do … end`. A piece is reached through its
|
|
178
|
-
# aggregate ; a command on one addresses the aggregate, never the piece.
|
|
179
|
-
#
|
|
180
|
-
# Reference attributes are the language's business now — offered as the
|
|
181
|
-
# head's own id and resolved as references, so `Aggregate.Reference` and
|
|
182
|
-
# `Command.Reference` refuse an undeclared head with no predicate at all.
|
|
183
|
-
#
|
|
184
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
185
|
-
# @return [void]
|
|
186
|
-
# @raise [Bluebook::DSL::Malformed] if an entity command's `references` names
|
|
187
|
-
# something other than one of this chapter's own aggregate heads — which, given
|
|
188
|
-
# how `CommandBuilder#reference_to` sets it, only happens when the entity command
|
|
189
|
-
# names its own entity as its root
|
|
83
|
+
# Refuses an entity command that names its own entity as its root.
|
|
84
|
+
# `CommandBuilder#reference_to` sets `references` only to the owner's bare name, so on
|
|
85
|
+
# an aggregate command a head lookup is a tautology; only an entity self-reference fails.
|
|
190
86
|
def validate_reference_value_objects!(aggregates)
|
|
191
87
|
heads = aggregates.map(&:hecks_name)
|
|
192
88
|
|
|
@@ -207,27 +103,8 @@ module Hecks
|
|
|
207
103
|
"an entity command is addressed through its aggregate; #{violations.uniq.join('; ')}"
|
|
208
104
|
end
|
|
209
105
|
|
|
210
|
-
#
|
|
211
|
-
#
|
|
212
|
-
# object with its own attributes, not a label"). No new `event
|
|
213
|
-
# do ... end` construct exists to hand-author and keep in step
|
|
214
|
-
# with every emitting command by hand — an event's own known
|
|
215
|
-
# shape is whichever command(s) declare `emits` for its name, and
|
|
216
|
-
# this is the one thing that has to hold for that convention to
|
|
217
|
-
# mean anything: every command that emits a given name has to
|
|
218
|
-
# agree on what it carries. An event is one fact; a fact does not
|
|
219
|
-
# carry two different truths depending on who is telling it.
|
|
220
|
-
#
|
|
221
|
-
# Structural fields only (name/type/list/optional) — `pattern:`/
|
|
222
|
-
# `admits:`/`default:` are refinements on a field, not a second
|
|
223
|
-
# claim about what the payload holds, so two emitting commands
|
|
224
|
-
# are free to differ there without actually disagreeing about
|
|
225
|
-
# the event's own shape.
|
|
226
|
-
#
|
|
227
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
228
|
-
# @return [void]
|
|
229
|
-
# @raise [Bluebook::DSL::Malformed] if two or more commands `emits` the same event
|
|
230
|
-
# name with different structural shapes
|
|
106
|
+
# Every command that emits a given event must agree on its structural shape
|
|
107
|
+
# (name, type, list, optional); `pattern:`/`admits:`/`default:` may differ (ADR 0025).
|
|
231
108
|
def validate_event_shapes!(aggregates)
|
|
232
109
|
event_emitters(aggregates).each do |event_name, pairs|
|
|
233
110
|
next if pairs.size == 1
|
|
@@ -242,39 +119,10 @@ module Hecks
|
|
|
242
119
|
end
|
|
243
120
|
end
|
|
244
121
|
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
# shape per event name, and command references being first-class
|
|
250
|
-
# (`Naming.command_ref`) means the target side is a real
|
|
251
|
-
# resolvable command, not a string that might be a typo.
|
|
252
|
-
#
|
|
253
|
-
# **Same-chapter only, on purpose** — a `with:` whose source event or
|
|
254
|
-
# target command lives outside this chapter (an `across` policy
|
|
255
|
-
# reacting to another domain's event entirely) is silently left
|
|
256
|
-
# unchecked rather than refused: there is nothing here yet to
|
|
257
|
-
# check it against, and "unresolvable" is not the same claim as
|
|
258
|
-
# "wrong."
|
|
259
|
-
#
|
|
260
|
-
# A FOR_EACH policy's source isn't the event at all — a fan-out
|
|
261
|
-
# `with:`'s symbols read the query row `for_each` answers
|
|
262
|
-
# (FreezeAccountsOnSuspension's own comment: "`account` is the
|
|
263
|
-
# key the fan-out merges for each row"), which this has no shape
|
|
264
|
-
# for; the source half is skipped for those, the target half
|
|
265
|
-
# (does the dispatched command actually declare the field) still
|
|
266
|
-
# runs, since that half is true regardless of where the value
|
|
267
|
-
# came from.
|
|
268
|
-
#
|
|
269
|
-
# @param policies [Array<Bluebook::Policy>] every reaction hoisted onto the
|
|
270
|
-
# chapter's own aggregates
|
|
271
|
-
# @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
|
|
272
|
-
# process managers
|
|
273
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
274
|
-
# @return [void]
|
|
275
|
-
# @raise [Bluebook::DSL::Malformed] if a policy's or a saga dispatch's `with:`
|
|
276
|
-
# names a field its target command does not declare, or reads a source field
|
|
277
|
-
# its event does not carry (see `check_with_spec!` for the full rule)
|
|
122
|
+
# Checks `with:` projections against the source event's shape and the target's fields.
|
|
123
|
+
# Same-chapter only: an unresolvable cross-chapter source or target is skipped.
|
|
124
|
+
# A for_each policy's source is a query row with no known shape, so only its target
|
|
125
|
+
# is checked.
|
|
278
126
|
def validate_with_projections!(policies, process_managers, aggregates)
|
|
279
127
|
lookup = command_lookup(aggregates)
|
|
280
128
|
heads = correlation_heads(process_managers)
|
|
@@ -299,47 +147,11 @@ module Hecks
|
|
|
299
147
|
end
|
|
300
148
|
end
|
|
301
149
|
|
|
302
|
-
# `
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
# saga
|
|
306
|
-
#
|
|
307
|
-
# memory starts as the opening event's payload
|
|
308
|
-
# (`SagaInterpreter#instance = { ..., memory: event.payload }`,
|
|
309
|
-
# never updated after), never the leg's own. Settlement's own
|
|
310
|
-
# comment names exactly this: "the credit leg reads a destination
|
|
311
|
-
# no event carried" — `AccountDebited` never declares `:reference`,
|
|
312
|
-
# only `TransferRequested` (`pm.starts_on`) does, and that is
|
|
313
|
-
# where the value is genuinely still coming from.
|
|
314
|
-
# One closed sequence of per-field checks against a fixed set of
|
|
315
|
-
# legal `with:` sources (target's own attributes, correlation,
|
|
316
|
-
# emitter identity, event/memory shape) — see the comment above
|
|
317
|
-
# for why each source is legal. Extracting the loop body would
|
|
318
|
-
# mean threading ten-plus already-resolved locals (target,
|
|
319
|
-
# source_shape, memory_shape, correlation, identity_sources, ...)
|
|
320
|
-
# into a new method for no gain: each `next`/`raise` already
|
|
321
|
-
# reads as its own rule at its own site.
|
|
322
|
-
#
|
|
323
|
-
# @param command_ref [String] the dotted name of the command `with:` dispatches,
|
|
324
|
-
# such as `"Account.Debit"` or `"Account.Piece.Move"`
|
|
325
|
-
# @param event_name [String, nil] the triggering event's name, or `nil` for a
|
|
326
|
-
# FOR_EACH policy (whose source is a query row, not an event)
|
|
327
|
-
# @param with_spec [Array<Array(Symbol, Object)>] the declared `with:` pairs,
|
|
328
|
-
# field name to source — a Symbol naming a field to read, or a literal value
|
|
329
|
-
# @param lookup [Hash{String => Class}] every command in the chapter, keyed by
|
|
330
|
-
# `"owner.hecks_name"` (`command_lookup`'s own shape)
|
|
331
|
-
# @param label [String] a human-readable description of what declared this
|
|
332
|
-
# `with:`, used only in a raised message
|
|
333
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
334
|
-
# @param correlation_heads [Array<Symbol>] every saga's own `correlates_by` head
|
|
335
|
-
# in this chapter, a legal addressing key alongside a command's own attributes
|
|
336
|
-
# @param process_manager [Bluebook::ProcessManager, nil] the process manager this
|
|
337
|
-
# `with:` dispatches from, or `nil` for a policy's own trigger
|
|
338
|
-
# @return [void]
|
|
339
|
-
# @raise [Bluebook::DSL::Malformed] if `with:` names a field the target command
|
|
340
|
-
# does not declare, or reads a Symbol source that resolves to neither the
|
|
341
|
-
# correlation key, the emitter's identity, nor a field the triggering event or
|
|
342
|
-
# (for a saga leg) the starting event's shape carries
|
|
150
|
+
# Checks one `with:` spec: each key must be a field the target command accepts,
|
|
151
|
+
# and each Symbol source must be the correlation key, the emitter's identity, or a
|
|
152
|
+
# field of the event's shape. A saga leg also falls back to the opening event's shape,
|
|
153
|
+
# since saga memory starts as that payload. One closed sequence of per-field checks;
|
|
154
|
+
# splitting it threads many locals.
|
|
343
155
|
# rubocop:disable-next Metrics/CyclomaticComplexity
|
|
344
156
|
# rubocop:disable-next Metrics/PerceivedComplexity
|
|
345
157
|
def check_with_spec!(command_ref, event_name, with_spec, lookup, label, aggregates, correlation_heads,
|
|
@@ -348,16 +160,9 @@ module Hecks
|
|
|
348
160
|
source_shape = event_name && event_shape_for(event_name, aggregates)
|
|
349
161
|
memory_shape = process_manager && event_shape_for(process_manager.starts_on, aggregates)
|
|
350
162
|
correlation = process_manager&.correlates_by && process_manager.correlation_head
|
|
351
|
-
# A policy's source also carries the emitter's
|
|
352
|
-
# `PolicyInterpreter#emitter_identity
|
|
353
|
-
#
|
|
354
|
-
# identity (it arrives through `reference_to`, not an
|
|
355
|
-
# `attribute`), so before this a policy on `KnightCaptured` could
|
|
356
|
-
# not spell `with: { label: :label }` at all — "reads :label off
|
|
357
|
-
# KnightCaptured, which does not declare it" — and chess's
|
|
358
|
-
# AdvancePly grew optional, unread attributes just to survive a
|
|
359
|
-
# wholesale forward. Policies only: a saga leg's own source is
|
|
360
|
-
# `SagaInterpreter#dispatch_args`, which merges no such thing.
|
|
163
|
+
# A policy's source also carries the emitter's identity
|
|
164
|
+
# (`PolicyInterpreter#emitter_identity`); a saga leg's source
|
|
165
|
+
# (`SagaInterpreter#dispatch_args`) merges no such thing.
|
|
361
166
|
identity_sources = process_manager.nil? && event_name ? event_identity_heads_for(event_name, aggregates) : []
|
|
362
167
|
|
|
363
168
|
with_spec.each do |field, source|
|
|
@@ -380,34 +185,9 @@ module Hecks
|
|
|
380
185
|
end
|
|
381
186
|
end
|
|
382
187
|
|
|
383
|
-
#
|
|
384
|
-
#
|
|
385
|
-
#
|
|
386
|
-
# `attributes` — `CommandBuilder#reference_to`'s self-reference
|
|
387
|
-
# branch sets `command.references` instead (S2), and mints no new
|
|
388
|
-
# field at all. What addresses it is not one name but the same
|
|
389
|
-
# set `CommandInterpreter::ArgumentGate#refuse_unknown_arguments`
|
|
390
|
-
# already accepts at dispatch time — `:id`, the owning aggregate's
|
|
391
|
-
# own `identity_heads` (real corpus proof — `Account.Debit`
|
|
392
|
-
# dispatched everywhere as `number: ...`, `Account`'s own
|
|
393
|
-
# `identified_by`), and `Naming.reference_key(command.references)`
|
|
394
|
-
# (real corpus proof — `FreezeAccountsOnSuspension`'s `for_each`
|
|
395
|
-
# fan-out, whose own comment reads "`account` is the key the
|
|
396
|
-
# fan-out merges for each row it answers"). Both are simultaneously
|
|
397
|
-
# legal there, not context-dependent alternatives, so both are
|
|
398
|
-
# legal here : this mirrors that gate rather than re-deriving a
|
|
399
|
-
# narrower rule that would refuse one of two real, already-shipped
|
|
400
|
-
# dispatch conventions.
|
|
401
|
-
#
|
|
402
|
-
# @param command [Class<Bluebook::Command>] the command `with:` dispatches
|
|
403
|
-
# @param field [Symbol] the `with:` key to check
|
|
404
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates,
|
|
405
|
-
# searched when `field` might name `command`'s own reference key
|
|
406
|
-
# @param correlation_heads [Array<Symbol>] every saga's own `correlates_by` head
|
|
407
|
-
# in this chapter
|
|
408
|
-
# @return [Boolean] `true` when `field` is one of `command`'s own attributes,
|
|
409
|
-
# `:id`, a correlation head, or the identity/reference key of the aggregate
|
|
410
|
-
# `command.references` names
|
|
188
|
+
# Mirrors `ArgumentGate#refuse_unknown_arguments`: `:id`, the owning aggregate's
|
|
189
|
+
# `identity_heads` and `Naming.reference_key(command.references)` are all legal keys
|
|
190
|
+
# beside the command's own attributes.
|
|
411
191
|
def command_declares?(command, field, aggregates, correlation_heads)
|
|
412
192
|
return true if command.attributes.any? { |a| a.name == field }
|
|
413
193
|
return true if field == :id
|
|
@@ -420,38 +200,14 @@ module Hecks
|
|
|
420
200
|
referenced.identity_heads.include?(field) || Naming.reference_key(command.references) == field
|
|
421
201
|
end
|
|
422
202
|
|
|
423
|
-
#
|
|
424
|
-
#
|
|
425
|
-
# saga in this domain's own `correlates_by` head, carried through
|
|
426
|
-
# every dispatch as pure passthrough (Settlement's own comment:
|
|
427
|
-
# "`reference:` carries the correlation forward... this is pure
|
|
428
|
-
# passthrough, not an addressing key"). A command declaring none of
|
|
429
|
-
# its attributes named this is not a gap; the correlation key rides
|
|
430
|
-
# through commands that never read it, same as it does at runtime.
|
|
431
|
-
#
|
|
432
|
-
# @param process_managers [Array<Bluebook::ProcessManager>] the chapter's declared
|
|
433
|
-
# process managers
|
|
434
|
-
# @return [Array<Symbol>] the `correlation_head` of every process manager that
|
|
435
|
-
# declares a `correlates_by`
|
|
203
|
+
# A saga's `correlates_by` head is a legal `with:` key: correlation rides
|
|
204
|
+
# through commands that never read it.
|
|
436
205
|
def correlation_heads(process_managers)
|
|
437
206
|
process_managers.filter_map { |pm| pm.correlates_by && pm.correlation_head }
|
|
438
207
|
end
|
|
439
208
|
|
|
440
|
-
# Yields every command
|
|
441
|
-
#
|
|
442
|
-
# Every command this chapter declares, an aggregate's own and
|
|
443
|
-
# every entity nested inside one, paired with a name for what
|
|
444
|
-
# declares it — shared by `validate_event_shapes!` and
|
|
445
|
-
# `validate_with_projections!`'s own command lookup, the same
|
|
446
|
-
# reach `HecksagonBuilder#commands_in` needs one level up (S8).
|
|
447
|
-
#
|
|
448
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
449
|
-
# @yieldparam owner [String] the declaring aggregate's `hecks_name`, or
|
|
450
|
-
# `"Aggregate.Entity"` for a command declared on a nested entity
|
|
451
|
-
# @yieldparam command [Class<Bluebook::Command>] the command class
|
|
452
|
-
# @return [Enumerator<Array(String, Class)>] an enumerator over the same pairs
|
|
453
|
-
# when no block is given; otherwise the value of the underlying `each`, unused
|
|
454
|
-
# by any caller
|
|
209
|
+
# Yields every command the chapter declares with its owner's name
|
|
210
|
+
# ("Aggregate.Entity" for entities).
|
|
455
211
|
def each_command(aggregates)
|
|
456
212
|
return enum_for(:each_command, aggregates) unless block_given?
|
|
457
213
|
|
|
@@ -463,116 +219,41 @@ module Hecks
|
|
|
463
219
|
end
|
|
464
220
|
end
|
|
465
221
|
|
|
466
|
-
#
|
|
467
|
-
#
|
|
468
|
-
# **Not memoised** — caching this as `@event_emitters ||=` on the builder
|
|
469
|
-
# instance would be safe for a one-file chapter but wrong for one split
|
|
470
|
-
# across several: the first file's `build()` call would compute and cache
|
|
471
|
-
# it from whatever `@aggregates` held at that moment, and every later
|
|
472
|
-
# file's own validation would keep reading that same stale snapshot,
|
|
473
|
-
# silently missing any command a later file adds. Recomputed fresh every
|
|
474
|
-
# call instead — this walks the whole chapter once per `#build`, not a
|
|
475
|
-
# hot path worth memoising at that cost.
|
|
476
|
-
#
|
|
477
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
478
|
-
# @return [Hash{String => Array<Array(String, Class)>}] every event name mapped
|
|
479
|
-
# to the `[owner, command]` pairs (`each_command`'s own shape) that `emits` it;
|
|
480
|
-
# an unqueried event name answers `[]`
|
|
222
|
+
# Not memoised: a chapter split across files is validated once per file, and a
|
|
223
|
+
# cached index would miss the commands later files add.
|
|
481
224
|
def event_emitters(aggregates)
|
|
482
225
|
each_command(aggregates).with_object(Hash.new { |h, k| h[k] = [] }) do |(owner, command), index|
|
|
483
226
|
command.emits.each { |event_name| index[event_name] << [owner, command] }
|
|
484
227
|
end
|
|
485
228
|
end
|
|
486
229
|
|
|
487
|
-
# The comparable shape of
|
|
488
|
-
#
|
|
489
|
-
#
|
|
490
|
-
#
|
|
491
|
-
# field through two different, locally-scoped wrapper value
|
|
492
|
-
# objects (e.g. one aggregate's own `value: SomeText` vs
|
|
493
|
-
# another's `value: OtherText`, exactly the per-aggregate "own
|
|
494
|
-
# text VO" convention every aggregate in this grammar already
|
|
495
|
-
# follows for everything from `RuleText` to `FieldRef`) without
|
|
496
|
-
# actually disagreeing about the event's shape — comparing
|
|
497
|
-
# `a.type` by name would flag that as a violation for no real
|
|
498
|
-
# reason: an event is one fact, and two isomorphic wrapper types
|
|
499
|
-
# tell an identical one. So a value-object type is unwrapped to
|
|
500
|
-
# its own attribute shape (recursively — a wrapper could itself
|
|
501
|
-
# wrap another) before comparing, and only a primitive type
|
|
502
|
-
# (nothing left to unwrap) or two VOs that truly differ once
|
|
503
|
-
# unwrapped still counts as a real mismatch. `owner` carries the
|
|
504
|
-
# type's `value_object` lookup — a command's own attributes only
|
|
505
|
-
# know their type's name, never the aggregate that declared it,
|
|
506
|
-
# and two sibling aggregates in one chapter each keep a
|
|
507
|
-
# same-named VO private to themselves, so the unwrap has to ask
|
|
508
|
-
# the same aggregate the field's own command belongs to, never a
|
|
509
|
-
# neighbor's.
|
|
510
|
-
#
|
|
511
|
-
# @param command [Class<Bluebook::Command>] the command whose attribute shape
|
|
512
|
-
# to compute
|
|
513
|
-
# @param owner [Bluebook::Aggregate, nil] the aggregate whose `value_object`
|
|
514
|
-
# lookup resolves any wrapper type by name; `nil` when the owner could not
|
|
515
|
-
# be resolved, in which case nothing is unwrapped
|
|
516
|
-
# @return [Array<Array>] one `[name, unwrapped_type, list, optional]` tuple per
|
|
517
|
-
# attribute, sorted; `unwrapped_type` is a type name String for a primitive
|
|
518
|
-
# or an unresolved type, or a recursively-unwrapped Array of the same shape
|
|
519
|
-
# for a value object
|
|
230
|
+
# The comparable shape of a command's attributes, value objects unwrapped.
|
|
231
|
+
# Structural, not nominal: aggregates may type a field with differently named
|
|
232
|
+
# wrapper value objects without disagreeing about the event. `owner` supplies the
|
|
233
|
+
# `value_object` lookup, since same-named value objects are private to each aggregate.
|
|
520
234
|
def event_shape(command, owner)
|
|
521
235
|
command.attributes.map { |a| [a.name, unwrap_shape(owner, a.type.to_s), a.list?, a.optional?] }.sort
|
|
522
236
|
end
|
|
523
237
|
|
|
524
|
-
# Recursively unwraps a type name to a primitive or a value object's own shape.
|
|
525
|
-
#
|
|
526
|
-
# @param owner [Bluebook::Aggregate, nil] the aggregate whose `value_object`
|
|
527
|
-
# lookup resolves `type_name`; `nil` answers `type_name` unchanged
|
|
528
|
-
# @param type_name [String] the attribute type to unwrap
|
|
529
|
-
# @param seen [Array<String>] type names already unwrapped on this path, so a
|
|
530
|
-
# self-referential value object bottoms out on its own name instead of
|
|
531
|
-
# recursing forever
|
|
532
|
-
# @return [String, Array<Array>] `type_name` itself when `owner` is `nil`, when
|
|
533
|
-
# it is already a primitive, when it is already in `seen`, or when it names no
|
|
534
|
-
# value object `owner` declares; otherwise the same `[name, unwrapped_type,
|
|
535
|
-
# list, optional]` shape `event_shape` returns, one tuple per member
|
|
536
238
|
def unwrap_shape(owner, type_name, seen = [])
|
|
537
|
-
# owner couldn't be resolved, so compare by name: nothing left to unwrap
|
|
538
239
|
return type_name if owner.nil?
|
|
539
240
|
return type_name if Attribute::PRIMITIVES.include?(type_name)
|
|
540
|
-
# a self-referential
|
|
241
|
+
# a self-referential value object bottoms out on its own name
|
|
541
242
|
return type_name if seen.include?(type_name)
|
|
542
243
|
|
|
543
244
|
shape = owner.value_object(type_name)
|
|
544
|
-
# not this owner's own VO (a reference type, say): nothing further to unwrap
|
|
545
245
|
return type_name unless shape
|
|
546
246
|
|
|
547
247
|
shape.attributes.map { |a| [a.name, unwrap_shape(owner, a.type.to_s, seen + [type_name]), a.list?, a.optional?] }.sort
|
|
548
248
|
end
|
|
549
249
|
|
|
550
|
-
#
|
|
551
|
-
#
|
|
552
|
-
# `owner` (from `each_command`) is a plain string — the aggregate's
|
|
553
|
-
# `hecks_name` alone, or `"Aggregate.Entity"` for an entity's own
|
|
554
|
-
# command. Either way the value objects a command's fields can be
|
|
250
|
+
# The value objects a command's fields can be
|
|
555
251
|
# typed with are the aggregate's own (`Entity` carries no
|
|
556
|
-
# `value_object` lookup
|
|
557
|
-
# already resolves hop/type lookups only at the aggregate level,
|
|
558
|
-
# e.g. `validate_hop_tail!`'s `target.value_object(type)`), so only
|
|
559
|
-
# the first segment ever matters here.
|
|
560
|
-
#
|
|
561
|
-
# @param owner [String] an `each_command` owner string, such as `"Game"` or
|
|
562
|
-
# `"Game.Knight"`
|
|
563
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
564
|
-
# @return [Bluebook::Aggregate, nil] the aggregate whose `hecks_name` matches
|
|
565
|
-
# `owner`'s first dotted segment, or `nil` if none does
|
|
252
|
+
# `value_object` lookup), so only the owner string's first segment matters.
|
|
566
253
|
def owner_aggregate(owner, aggregates)
|
|
567
254
|
aggregates.find { |a| a.hecks_name == owner.to_s.split(".").first }
|
|
568
255
|
end
|
|
569
256
|
|
|
570
|
-
# The comparable attribute shape of whichever command first emits `event_name`.
|
|
571
|
-
#
|
|
572
|
-
# @param event_name [String, Symbol] the event name to look up
|
|
573
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
574
|
-
# @return [Array<Array>, nil] the first emitting command's `event_shape`, or `nil`
|
|
575
|
-
# if no command in the chapter emits this event
|
|
576
257
|
def event_shape_for(event_name, aggregates)
|
|
577
258
|
pairs = event_emitters(aggregates).fetch(event_name.to_s, [])
|
|
578
259
|
return nil if pairs.empty?
|
|
@@ -581,16 +262,8 @@ module Hecks
|
|
|
581
262
|
event_shape(command, owner_aggregate(owner_name, aggregates))
|
|
582
263
|
end
|
|
583
264
|
|
|
584
|
-
#
|
|
585
|
-
#
|
|
586
|
-
# (`Event#id` is the parent's), so an owner spelled "Game.Knight"
|
|
587
|
-
# answers Game's heads.
|
|
588
|
-
#
|
|
589
|
-
# @param event_name [String, Symbol] the event name to look up
|
|
590
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
591
|
-
# @return [Array<Symbol>] the emitting aggregate's own `identity_heads`, plus the
|
|
592
|
-
# emitting entity's own `identity_heads` when the event is an entity's; `[]` if
|
|
593
|
-
# no command in the chapter emits this event, or its owner cannot be resolved
|
|
265
|
+
# An entity's event is stamped with its owning aggregate's identity, so
|
|
266
|
+
# "Game.Knight" answers Game's heads.
|
|
594
267
|
def event_identity_heads_for(event_name, aggregates)
|
|
595
268
|
pairs = event_emitters(aggregates).fetch(event_name.to_s, [])
|
|
596
269
|
return [] if pairs.empty?
|
|
@@ -600,57 +273,32 @@ module Hecks
|
|
|
600
273
|
return [] unless aggregate
|
|
601
274
|
|
|
602
275
|
heads = aggregate.identity_heads.map(&:to_sym)
|
|
603
|
-
# An entity's event also carries the piece's own identity
|
|
604
|
-
#
|
|
605
|
-
# (`Emission#emit`: `payload: args`), so `id`-shaped heads are
|
|
606
|
-
# genuinely there at runtime even though no `attribute` line on
|
|
607
|
-
# the entity command declares them.
|
|
276
|
+
# An entity's event also carries the piece's own identity: the args a piece
|
|
277
|
+
# was addressed by are its payload (`Emission#emit`).
|
|
608
278
|
entity_names = owner_name.to_s.split(".").drop(1)
|
|
609
279
|
entity = entity_names.reduce(aggregate) { |owner, name| owner&.entities&.find { |e| e.hecks_name == name } }
|
|
610
280
|
heads + (entity ? entity.identity_heads.map(&:to_sym) : [])
|
|
611
281
|
end
|
|
612
282
|
|
|
613
|
-
# Every command in the chapter, keyed by its fully dotted address.
|
|
614
|
-
#
|
|
615
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
616
|
-
# @return [Hash{String => Class}] `"owner.hecks_name"` (`each_command`'s owner
|
|
617
|
-
# string joined with the command's own `hecks_name`) mapped to the command class
|
|
618
283
|
def command_lookup(aggregates)
|
|
619
284
|
each_command(aggregates).with_object({}) do |(owner, command), index|
|
|
620
285
|
index["#{owner}.#{command.hecks_name}"] = command
|
|
621
286
|
end
|
|
622
287
|
end
|
|
623
288
|
|
|
624
|
-
# A reference ring
|
|
625
|
-
#
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
#
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
#
|
|
638
|
-
# Acyclic within a chapter (ADR 0025, "References") — catches any
|
|
639
|
-
# ring, however long (A -> B -> C -> A), not only a direct pair
|
|
640
|
-
# (A -> B -> A), the same DFS coloring a reference graph needs
|
|
641
|
-
# for any cycle. A cross-chapter reference is unreachable here
|
|
642
|
-
# rather than unchecked: `Reference#resolve` is scoped to its
|
|
643
|
-
# own chapter by construction, so a target this chapter never
|
|
644
|
-
# declares is a dangling name, not an edge — `edges.key?` below
|
|
645
|
-
# is what keeps the walk from ever leaving this chapter's own
|
|
646
|
-
# aggregates. Self-reference stays legal (`parent.parent.name`
|
|
647
|
-
# for a hierarchy is real and safe) — excluded the same way a
|
|
648
|
-
# direct pair is.
|
|
649
|
-
#
|
|
650
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
651
|
-
# @return [void]
|
|
652
|
-
# @raise [Bluebook::DSL::Malformed] if the chapter's own aggregate-to-aggregate
|
|
653
|
-
# reference graph contains a cycle
|
|
289
|
+
# A reference ring means no aggregate in it is a consistency boundary. Checked at
|
|
290
|
+
# chapter level because seeing a cycle needs every end declared (ADR 0025). Catches
|
|
291
|
+
# any ring length; self-reference stays legal, and a cross-chapter target is a
|
|
292
|
+
# dangling name, not an edge.
|
|
293
|
+
# A chapter's constants install under its name, or its `namespace`; landing in the
|
|
294
|
+
# gem's own `Hecks` module would shadow the gem's modules (ADR 0080), so it refuses.
|
|
295
|
+
def validate_not_the_gem_module!(bluebook)
|
|
296
|
+
return unless (bluebook.namespace || bluebook.name) == "Hecks"
|
|
297
|
+
|
|
298
|
+
raise Malformed, "#{bluebook.name}'s constants would install in the gem's own Hecks module — " \
|
|
299
|
+
"declare a namespace, such as namespace \"Hecks::Domain\", or rename the chapter"
|
|
300
|
+
end
|
|
301
|
+
|
|
654
302
|
def validate_no_bidirectional_references!(aggregates)
|
|
655
303
|
edges = aggregates.to_h do |aggregate|
|
|
656
304
|
[aggregate.hecks_name, aggregate.reference_targets.uniq.reject { |target| target == aggregate.hecks_name }]
|
|
@@ -667,14 +315,8 @@ module Hecks
|
|
|
667
315
|
"through a query instead of a reference pointing back"
|
|
668
316
|
end
|
|
669
317
|
|
|
670
|
-
# Plain DFS with a visiting/done coloring
|
|
671
|
-
#
|
|
672
|
-
# itself (in the order it closes), or nil.
|
|
673
|
-
#
|
|
674
|
-
# @param edges [Hash{String => Array<String>}] each aggregate's `hecks_name`
|
|
675
|
-
# mapped to the names of the aggregates it references
|
|
676
|
-
# @return [Array<String>, nil] the aggregate names forming a cycle, in the order
|
|
677
|
-
# the walk closes it, or `nil` if the graph has none
|
|
318
|
+
# Plain DFS with a visiting/done coloring; returns the ring in the order it
|
|
319
|
+
# closes, or nil.
|
|
678
320
|
def find_reference_cycle(edges)
|
|
679
321
|
state = {}
|
|
680
322
|
|
|
@@ -686,17 +328,6 @@ module Hecks
|
|
|
686
328
|
nil
|
|
687
329
|
end
|
|
688
330
|
|
|
689
|
-
# One DFS step of `find_reference_cycle`, from `node`.
|
|
690
|
-
#
|
|
691
|
-
# @param node [String] the aggregate name to walk from
|
|
692
|
-
# @param edges [Hash{String => Array<String>}] the whole reference graph, as
|
|
693
|
-
# `find_reference_cycle` receives it
|
|
694
|
-
# @param state [Hash{String => Symbol}] each visited node's `:visiting` or
|
|
695
|
-
# `:done` color, mutated in place across the whole walk
|
|
696
|
-
# @param path [Array<String>] the nodes visited on the current path, mutated
|
|
697
|
-
# in place as the walk descends and backtracks
|
|
698
|
-
# @return [Array<String>, nil] the cycle closing back on `node`, or `nil` if
|
|
699
|
-
# no cycle is reachable from here
|
|
700
331
|
def reference_cycle_from(node, edges, state, path)
|
|
701
332
|
return nil if state[node] == :done
|
|
702
333
|
return path[path.index(node)..] if state[node] == :visiting
|
|
@@ -718,36 +349,9 @@ module Hecks
|
|
|
718
349
|
end
|
|
719
350
|
|
|
720
351
|
# Resolves and checks every `where` hop deferred at aggregate-seal time.
|
|
721
|
-
#
|
|
722
|
-
#
|
|
723
|
-
#
|
|
724
|
-
# its own references and deferred it here, unable to check
|
|
725
|
-
# further: it cannot yet resolve what the reference points at.
|
|
726
|
-
# This runs once every aggregate exists in one chapter, so it
|
|
727
|
-
# can.
|
|
728
|
-
#
|
|
729
|
-
# Only `where` clauses ever reach here — a hop on ORDER BY is
|
|
730
|
-
# refused outright, immediately, back in seal_query_field
|
|
731
|
-
# itself (that answer never needed the target's shape).
|
|
732
|
-
#
|
|
733
|
-
# An entity's own queries do reach `EntityBuilder#reference_to` —
|
|
734
|
-
# tier-1 sealing (`AggregateBuilder#query_surfaces`) already
|
|
735
|
-
# recognises a hop on an entity's own field and defers it exactly
|
|
736
|
-
# like an aggregate's — but nothing walks entity queries at tier 2
|
|
737
|
-
# to check the deferral, so a bad hop, or even a well-formed one,
|
|
738
|
-
# would otherwise build silently and then match nothing at runtime
|
|
739
|
-
# (`QueryInterpreter#entity_rows` reads an element's fields by
|
|
740
|
-
# literal hash key, never follows a reference). Refused outright
|
|
741
|
-
# here instead of taught to follow the hop for real: no corpus
|
|
742
|
-
# member needs an entity query to cross a reference yet, and a
|
|
743
|
-
# named refusal beats a runtime that resolves nothing while
|
|
744
|
-
# looking like it might.
|
|
745
|
-
#
|
|
746
|
-
# @param bluebook [Bluebook::Chapter] the fully assembled chapter to check
|
|
747
|
-
# @return [void]
|
|
748
|
-
# @raise [Bluebook::DSL::Malformed] if an aggregate's `where` hop resolves to
|
|
749
|
-
# nothing, chains too deep, or lands somewhere illegal, or if an entity's own
|
|
750
|
-
# query hops through a reference at all
|
|
352
|
+
# An entity query that hops through a reference is refused outright: nothing follows
|
|
353
|
+
# the hop at runtime (`QueryInterpreter#entity_rows` reads fields by literal key),
|
|
354
|
+
# so it would match nothing.
|
|
751
355
|
def validate_query_hops!(bluebook)
|
|
752
356
|
bluebook.aggregates.each do |aggregate|
|
|
753
357
|
aggregate.queries.each do |query|
|
|
@@ -762,19 +366,8 @@ module Hecks
|
|
|
762
366
|
end
|
|
763
367
|
end
|
|
764
368
|
|
|
765
|
-
# Mints an implicit query attribute for
|
|
766
|
-
#
|
|
767
|
-
# The chapter-wide half of AggregateBuilder's local query-argument
|
|
768
|
-
# inference. A hop cannot resolve while its aggregate is still being
|
|
769
|
-
# built; here every Reference has an owner and target, so a symbolic
|
|
770
|
-
# comparison can inherit the type of the scalar it compares without a
|
|
771
|
-
# duplicate query-local declaration.
|
|
772
|
-
#
|
|
773
|
-
# @param bluebook [Bluebook::Chapter] the fully assembled chapter to infer
|
|
774
|
-
# query arguments over
|
|
775
|
-
# @return [void]
|
|
776
|
-
# @raise [Bluebook::DSL::Malformed] if a hop's reference has no `declared_in`
|
|
777
|
-
# aggregate to resolve its target through
|
|
369
|
+
# Mints an implicit query attribute for each symbolic hop comparison, typed from
|
|
370
|
+
# the scalar it compares against.
|
|
778
371
|
def infer_hop_query_arguments!(bluebook)
|
|
779
372
|
bluebook.aggregates.each do |aggregate|
|
|
780
373
|
aggregate.queries.each do |query|
|
|
@@ -796,23 +389,6 @@ module Hecks
|
|
|
796
389
|
end
|
|
797
390
|
end
|
|
798
391
|
|
|
799
|
-
# Builds the query attribute a resolved hop plan implies for `name`.
|
|
800
|
-
#
|
|
801
|
-
# The leaf `infer_hop_query_arguments!` infers for one resolved
|
|
802
|
-
# hop plan — a pure function of `plan` and the symbolic `name`
|
|
803
|
-
# it is naming, pulled out because it is a self-contained
|
|
804
|
-
# computation with no dependency on the enclosing loop's own
|
|
805
|
-
# iteration state (it neither reads nor mutates anything about
|
|
806
|
-
# `bluebook`/`aggregate`/`query` beyond what `plan` already
|
|
807
|
-
# carries).
|
|
808
|
-
#
|
|
809
|
-
# @param name [Symbol] the symbolic comparison value the query names, and the
|
|
810
|
-
# attribute name to mint
|
|
811
|
-
# @param plan [QuerySpecification::HopPath::Plan] the resolved hop chain whose
|
|
812
|
-
# target's shape `name` is inferred against
|
|
813
|
-
# @return [Bluebook::Attribute, nil] a `String`-typed attribute when the tail
|
|
814
|
-
# lands on the target's lifecycle field; otherwise an attribute copying the
|
|
815
|
-
# resolved leaf's type and list-ness; `nil` when the tail resolves to nothing
|
|
816
392
|
def inferred_hop_leaf(name, plan)
|
|
817
393
|
target = plan.hops.last.target
|
|
818
394
|
head, *nested = plan.tail.to_s.split(".")
|
|
@@ -827,14 +403,6 @@ module Hecks
|
|
|
827
403
|
end
|
|
828
404
|
end
|
|
829
405
|
|
|
830
|
-
# Refuses any `where` clause on an entity's own query that hops through a reference.
|
|
831
|
-
#
|
|
832
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate `entity` is nested under,
|
|
833
|
-
# named only in a raised message
|
|
834
|
-
# @param entity [Class<Bluebook::Entity>] the entity whose queries to check
|
|
835
|
-
# @return [void]
|
|
836
|
-
# @raise [Bluebook::DSL::Malformed] if any of `entity`'s queries has a `where`
|
|
837
|
-
# clause that hops through one of the entity's own references
|
|
838
406
|
def refuse_entity_query_hops!(aggregate, entity)
|
|
839
407
|
entity.queries.each do |query|
|
|
840
408
|
query.wheres.each do |clause|
|
|
@@ -849,27 +417,13 @@ module Hecks
|
|
|
849
417
|
end
|
|
850
418
|
end
|
|
851
419
|
|
|
852
|
-
# Resolves one `where` hop's target and checks where its tail lands.
|
|
853
|
-
#
|
|
854
|
-
# @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
|
|
855
|
-
# raised message
|
|
856
|
-
# @param query [Bluebook::Query] the query `clause` belongs to, named in a
|
|
857
|
-
# raised message
|
|
858
|
-
# @param clause [QuerySpecification::Common::WhereClause] the `where` clause whose
|
|
859
|
-
# `field` hops through one of `aggregate`'s own references
|
|
860
|
-
# @return [void]
|
|
861
|
-
# @raise [Bluebook::DSL::Malformed] if the hop's target is not declared in this
|
|
862
|
-
# chapter, the hop chain exceeds `QuerySpecification::HopPath::MAX_HOPS`, or
|
|
863
|
-
# the resolved tail fails `validate_hop_tail!`'s own checks
|
|
864
420
|
def validate_hop_clause!(aggregate, query, clause)
|
|
865
421
|
plan = QuerySpecification::HopPath.plan(clause.field, aggregate.attributes)
|
|
866
422
|
|
|
867
423
|
case plan.refusal
|
|
868
424
|
when :unresolvable
|
|
869
|
-
# HopPath.plan pushes even an unresolved hop onto `hops`
|
|
870
|
-
#
|
|
871
|
-
# real, known at declaration, independent of whether
|
|
872
|
-
# `resolve` succeeded — is always here to name.
|
|
425
|
+
# HopPath.plan pushes even an unresolved hop onto `hops`, so `target_name`
|
|
426
|
+
# is always there.
|
|
873
427
|
raise Malformed,
|
|
874
428
|
"#{aggregate.hecks_name}.#{query.hecks_name} asks about #{clause.field}, " \
|
|
875
429
|
"which hops to #{plan.hops.last.target_name}, which this chapter never " \
|
|
@@ -888,26 +442,8 @@ module Hecks
|
|
|
888
442
|
validate_hop_tail!(aggregate, query, clause, target, plan.tail)
|
|
889
443
|
end
|
|
890
444
|
|
|
891
|
-
#
|
|
892
|
-
#
|
|
893
|
-
# The same three-way answer seal_query_field gives for its own
|
|
894
|
-
# aggregate's fields — landing on a real scalar (fine), landing
|
|
895
|
-
# on a value object (refused by name), or naming nothing at all
|
|
896
|
-
# (refused by name) — asked instead of the hop's target aggregate,
|
|
897
|
-
# since that is whose shape the tail actually has to answer for.
|
|
898
|
-
#
|
|
899
|
-
# @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
|
|
900
|
-
# raised message
|
|
901
|
-
# @param query [Bluebook::Query] the query `clause` belongs to, named in a
|
|
902
|
-
# raised message
|
|
903
|
-
# @param clause [QuerySpecification::Common::WhereClause] the `where` clause the
|
|
904
|
-
# hop was resolved from, named in a raised message
|
|
905
|
-
# @param target [Bluebook::Aggregate] the aggregate the hop resolved to
|
|
906
|
-
# @param tail [String] the dotted field path remaining after the hop, such as
|
|
907
|
-
# `"status"` or `"amount.currency"`
|
|
908
|
-
# @return [void]
|
|
909
|
-
# @raise [Bluebook::DSL::Malformed] if `tail` lands on a value object rather
|
|
910
|
-
# than a scalar, or names no field `target` declares
|
|
445
|
+
# Same three-way answer as `seal_query_field` (scalar, value object, or
|
|
446
|
+
# nothing), asked of the hop's target.
|
|
911
447
|
def validate_hop_tail!(aggregate, query, clause, target, tail)
|
|
912
448
|
name, *nested = tail.to_s.split(".")
|
|
913
449
|
attribute = target.attributes.find { |candidate| candidate.name.to_s == name }
|
|
@@ -933,30 +469,8 @@ module Hecks
|
|
|
933
469
|
"not exist matches nothing and refuses nothing"
|
|
934
470
|
end
|
|
935
471
|
|
|
936
|
-
#
|
|
937
|
-
#
|
|
938
|
-
# A where hop with an ordered comparator is legitimate ("client
|
|
939
|
-
# whose balance > 500") — AggregateBuilder#seal_ordered_comparator
|
|
940
|
-
# already deferred this exact check for the same reason every
|
|
941
|
-
# other hop check is deferred, and this is where it gets asked,
|
|
942
|
-
# against the hop's target instead of the querying aggregate.
|
|
943
|
-
#
|
|
944
|
-
# @param aggregate [Bluebook::Aggregate] the querying aggregate, named in a
|
|
945
|
-
# raised message
|
|
946
|
-
# @param query [Bluebook::Query] the query `clause` belongs to, named in a
|
|
947
|
-
# raised message
|
|
948
|
-
# @param clause [QuerySpecification::Common::WhereClause] the `where` clause
|
|
949
|
-
# whose `op` and `field` are named in a raised message
|
|
950
|
-
# @param target [Bluebook::Aggregate] the aggregate the hop resolved to, named
|
|
951
|
-
# in a raised message
|
|
952
|
-
# @param attribute [Bluebook::Attribute, nil] the tail's root attribute on
|
|
953
|
-
# `target`, or `nil` when the tail lands on the lifecycle field
|
|
954
|
-
# @param nested [Array<String>] the tail's remaining dotted segments past
|
|
955
|
-
# `attribute`, `[]` for a bare field
|
|
956
|
-
# @return [void]
|
|
957
|
-
# @raise [Bluebook::DSL::Malformed] if `clause.op` is one of
|
|
958
|
-
# `AggregateBuilder::ORDERED_COMPARATORS` and the field it lands on, after
|
|
959
|
-
# `attribute`/`nested`, is not numeric
|
|
472
|
+
# An ordered comparator over a hopped field must land on a number; the check
|
|
473
|
+
# `AggregateBuilder#seal_ordered_comparator` deferred.
|
|
960
474
|
def validate_hop_comparator!(aggregate, query, clause, target, attribute, nested)
|
|
961
475
|
return unless AggregateBuilder::ORDERED_COMPARATORS.include?(clause.op.to_s.to_sym)
|
|
962
476
|
return if attribute &&
|
|
@@ -970,26 +484,9 @@ module Hecks
|
|
|
970
484
|
"anything else the adapters answer differently or not at all"
|
|
971
485
|
end
|
|
972
486
|
|
|
973
|
-
#
|
|
974
|
-
# `
|
|
975
|
-
#
|
|
976
|
-
# `reference_to` on this aggregate); this checks the reference
|
|
977
|
-
# actually resolves to a real aggregate in this chapter, and
|
|
978
|
-
# that aggregate really declares `remote_field` as a scalar.
|
|
979
|
-
#
|
|
980
|
-
# Reuses `QuerySpecification::HopPath` rather than re-deriving
|
|
981
|
-
# hop resolution a second way — `"reference/remote_field"` is
|
|
982
|
-
# the same single-hop shape a query's own `/`-spelled hop
|
|
983
|
-
# resolves, even though `projects`'s own DSL spelling is dotted
|
|
984
|
-
# (`from: :"customer.status"`): two constructs, two spellings,
|
|
985
|
-
# one resolution primitive. A single hop can never reach
|
|
986
|
-
# HopPath::MAX_HOPS, so :too_deep is structurally unreachable
|
|
987
|
-
# here and is not special-cased.
|
|
988
|
-
#
|
|
989
|
-
# @param bluebook [Bluebook::Chapter] the fully assembled chapter to check
|
|
990
|
-
# @return [void]
|
|
991
|
-
# @raise [Bluebook::DSL::Malformed] if any aggregate's `projects` field fails
|
|
992
|
-
# `validate_projected_field!`'s own checks
|
|
487
|
+
# Checks each `projects` reference resolves to an aggregate declaring
|
|
488
|
+
# `remote_field` as a scalar. A single hop never reaches HopPath::MAX_HOPS, so
|
|
489
|
+
# :too_deep is not special-cased.
|
|
993
490
|
def validate_projected_fields!(bluebook)
|
|
994
491
|
bluebook.aggregates.each do |aggregate|
|
|
995
492
|
aggregate.projected_fields.each { |field| validate_projected_field!(aggregate, field) }
|
|
@@ -997,22 +494,8 @@ module Hecks
|
|
|
997
494
|
end
|
|
998
495
|
|
|
999
496
|
# Checks that one `projects` field's reference resolves to a real scalar.
|
|
1000
|
-
#
|
|
1001
|
-
#
|
|
1002
|
-
# hop plan, each already explained by its own comment above
|
|
1003
|
-
# (the lifecycle fallback, the chained-projection fallback, the
|
|
1004
|
-
# final scalar check) — a fixed, closed sequence "resolve, then
|
|
1005
|
-
# check each way this could legitimately or illegitimately
|
|
1006
|
-
# land," not several unrelated concerns. Splitting it would
|
|
1007
|
-
# scatter `plan`/`target`/`remote_attribute` across new methods
|
|
1008
|
-
# that would each need most of them anyway.
|
|
1009
|
-
#
|
|
1010
|
-
# @param aggregate [Bluebook::Aggregate] the aggregate that declared `field`
|
|
1011
|
-
# @param field [Bluebook::ProjectedField] the declared `projects` field to check
|
|
1012
|
-
# @return [void]
|
|
1013
|
-
# @raise [Bluebook::DSL::Malformed] if `field.reference` hops to an aggregate
|
|
1014
|
-
# this chapter never declares, or `field.remote_field` names nothing on the
|
|
1015
|
-
# target, or names something other than a scalar
|
|
497
|
+
# One closed sequence over a single hop plan; splitting it scatters `plan`/`target`/
|
|
498
|
+
# `remote_attribute` across methods that each need most of them.
|
|
1016
499
|
# rubocop:disable-next Metrics/AbcSize
|
|
1017
500
|
def validate_projected_field!(aggregate, field)
|
|
1018
501
|
plan = QuerySpecification::HopPath.plan("#{field.reference}/#{field.remote_field}", aggregate.attributes)
|
|
@@ -1027,30 +510,13 @@ module Hecks
|
|
|
1027
510
|
target = plan.hops.last.target
|
|
1028
511
|
remote_attribute = target.attributes.find { |candidate| candidate.name.to_s == plan.tail }
|
|
1029
512
|
|
|
1030
|
-
#
|
|
1031
|
-
#
|
|
1032
|
-
# :status`, never a plain `attribute` — the same fallback
|
|
1033
|
-
# validate_hop_tail! already gives a query's own hop tail. A
|
|
1034
|
-
# lifecycle field is always a plain string by construction ;
|
|
1035
|
-
# nothing further to check once it matches by name.
|
|
513
|
+
# A lifecycle field is a plain string by construction, so a name match is enough (as in
|
|
514
|
+
# `validate_hop_tail!`).
|
|
1036
515
|
return if remote_attribute.nil? && target.lifecycle&.field.to_s == plan.tail
|
|
1037
516
|
|
|
1038
|
-
# A projection may chain through another projection
|
|
1039
|
-
#
|
|
1040
|
-
#
|
|
1041
|
-
# separate list from `attributes`, so a match there is
|
|
1042
|
-
# invisible to the check above even though it names a real,
|
|
1043
|
-
# always-current, stored field. `Transfer.projects
|
|
1044
|
-
# :source_customer_status, from: :"source.customer_status"`
|
|
1045
|
-
# reads Account's own already-projected `customer_status`
|
|
1046
|
-
# this way — Account is one hop from Customer, Transfer is
|
|
1047
|
-
# one hop from Account, and neither aggregate needs to know
|
|
1048
|
-
# about the other's target two hops away. A projected
|
|
1049
|
-
# field's remote value is always a scalar by construction
|
|
1050
|
-
# (`RebuildSweep.remote_value` never copies a reference, a
|
|
1051
|
-
# value object, or a list), so nothing further to check once
|
|
1052
|
-
# it matches by name — same reasoning the lifecycle
|
|
1053
|
-
# fallback just above already applies.
|
|
517
|
+
# A projection may chain through another projection; `projected_fields` is
|
|
518
|
+
# separate from `attributes`, and a projected value is always a scalar by
|
|
519
|
+
# construction, so a name match is enough.
|
|
1054
520
|
return if remote_attribute.nil? && target.projected_fields.any? { |f| f.name.to_s == plan.tail }
|
|
1055
521
|
|
|
1056
522
|
unless remote_attribute
|
|
@@ -1067,45 +533,14 @@ module Hecks
|
|
|
1067
533
|
"value, never a reference, a value object, or a list"
|
|
1068
534
|
end
|
|
1069
535
|
|
|
1070
|
-
# Decides whether a declared attribute is a plain, single scalar value.
|
|
1071
|
-
#
|
|
1072
|
-
# @param target [Bluebook::Aggregate] the aggregate `attribute` is declared on,
|
|
1073
|
-
# whose `value_object` lookup resolves `attribute`'s type
|
|
1074
|
-
# @param attribute [Bluebook::Attribute] the attribute to check
|
|
1075
|
-
# @return [Boolean] `true` when `attribute` is not a list, not a reference, and
|
|
1076
|
-
# its type names no value object `target` declares
|
|
1077
536
|
def projectable_scalar?(target, attribute)
|
|
1078
537
|
!attribute.list? && !attribute.reference? && target.value_object(attribute.type).nil?
|
|
1079
538
|
end
|
|
1080
539
|
|
|
1081
|
-
# `correlates_by`
|
|
1082
|
-
#
|
|
1083
|
-
#
|
|
1084
|
-
#
|
|
1085
|
-
# the question open. It cannot go further: a process manager is built
|
|
1086
|
-
# in isolation, before this chapter's aggregates exist to check
|
|
1087
|
-
# against. Here, with the whole document assembled, the dotted path
|
|
1088
|
-
# is walked for real — against whichever command actually emits an
|
|
1089
|
-
# event this process manager reacts to — so a path that still lands
|
|
1090
|
-
# on a value object is refused before the runtime ever has to decide
|
|
1091
|
-
# what a non-scalar correlation key even means: a saga keys off this
|
|
1092
|
-
# value directly, and a value object carries no guaranteed-stable
|
|
1093
|
-
# identity to key on the way a scalar does.
|
|
1094
|
-
#
|
|
1095
|
-
# A command that does not declare the path's first segment at all is
|
|
1096
|
-
# silently skipped, not refused — correlation has two other fallback
|
|
1097
|
-
# tiers below the payload dig (a correlation stamp, then the emitting
|
|
1098
|
-
# aggregate's own reference key; saga_interpreter/correlation.rb), so
|
|
1099
|
-
# an absent field is not this check's business. Only a field that
|
|
1100
|
-
# resolves, and resolves to something other than a scalar, is.
|
|
1101
|
-
#
|
|
1102
|
-
# @param process_managers [Array<Bluebook::ProcessManager>] the chapter's
|
|
1103
|
-
# declared process managers
|
|
1104
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
1105
|
-
# @return [void]
|
|
1106
|
-
# @raise [Bluebook::DSL::ProcessManagerBuilder::InvalidProcessManager] if a
|
|
1107
|
-
# process manager's `correlates_by` path resolves, on any command that emits
|
|
1108
|
-
# an event it reacts to, to something other than a scalar
|
|
540
|
+
# `correlates_by` must land on a scalar: the dotted path is walked against each
|
|
541
|
+
# command that emits an event the process manager reacts to. A command lacking the
|
|
542
|
+
# first segment is skipped, since correlation has fallback tiers
|
|
543
|
+
# (saga_interpreter/correlation.rb).
|
|
1109
544
|
def validate_correlation_keys!(process_managers, aggregates)
|
|
1110
545
|
process_managers.each do |pm|
|
|
1111
546
|
next unless pm.correlates_by
|
|
@@ -1118,15 +553,6 @@ module Hecks
|
|
|
1118
553
|
end
|
|
1119
554
|
end
|
|
1120
555
|
|
|
1121
|
-
# Finds why a process manager's `correlates_by` path is not a scalar, if it isn't.
|
|
1122
|
-
#
|
|
1123
|
-
# @param process_manager [Bluebook::ProcessManager] the process manager whose
|
|
1124
|
-
# `correlates_by` to check
|
|
1125
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
1126
|
-
# @return [String, nil] the reason the path is not a scalar, on the first
|
|
1127
|
-
# emitting command whose first segment resolves; `nil` if every command that
|
|
1128
|
-
# emits a reacted-to event either omits the first segment, or resolves to
|
|
1129
|
-
# a scalar
|
|
1130
556
|
def correlation_key_violation(process_manager, aggregates)
|
|
1131
557
|
head, *rest = process_manager.correlates_by.to_s.split(".")
|
|
1132
558
|
events = reacted_events(process_manager)
|
|
@@ -1142,12 +568,6 @@ module Hecks
|
|
|
1142
568
|
nil
|
|
1143
569
|
end
|
|
1144
570
|
|
|
1145
|
-
# The demodulised names of every event a process manager reacts to.
|
|
1146
|
-
#
|
|
1147
|
-
# @param process_manager [Bluebook::ProcessManager] the process manager whose
|
|
1148
|
-
# `starts_on`, `ends_on` and handler event types to collect
|
|
1149
|
-
# @return [Array<String>] the unique demodulised event names, `REFUSED` (the
|
|
1150
|
-
# compensation trigger, not a real event) excluded
|
|
1151
571
|
def reacted_events(process_manager)
|
|
1152
572
|
([process_manager.starts_on, process_manager.ends_on] + process_manager.handlers.map(&:event_type))
|
|
1153
573
|
.compact
|
|
@@ -1156,13 +576,6 @@ module Hecks
|
|
|
1156
576
|
.uniq
|
|
1157
577
|
end
|
|
1158
578
|
|
|
1159
|
-
# Every command across the chapter that emits one of the given events.
|
|
1160
|
-
#
|
|
1161
|
-
# @param events [Array<String>] the demodulised event names to match against
|
|
1162
|
-
# each command's own `emits`
|
|
1163
|
-
# @param aggregates [Array<Bluebook::Aggregate>] the chapter's declared aggregates
|
|
1164
|
-
# @return [Array<Array(Bluebook::Aggregate, Class)>] one `[aggregate, command]`
|
|
1165
|
-
# pair per matching command, an aggregate's own or one of its entities'
|
|
1166
579
|
def emitting_commands(events, aggregates)
|
|
1167
580
|
aggregates.flat_map do |aggregate|
|
|
1168
581
|
commands = aggregate.commands + aggregate.entities.flat_map(&:commands)
|
|
@@ -1171,16 +584,6 @@ module Hecks
|
|
|
1171
584
|
end
|
|
1172
585
|
end
|
|
1173
586
|
|
|
1174
|
-
# Decides whether one command attribute can serve as a correlation key.
|
|
1175
|
-
#
|
|
1176
|
-
# @param owner [Bluebook::Aggregate] the aggregate whose `value_object` lookup
|
|
1177
|
-
# resolves `attribute`'s type, for `walk_scalar`
|
|
1178
|
-
# @param attribute [Bluebook::Attribute] the command attribute the correlation
|
|
1179
|
-
# path's first segment named
|
|
1180
|
-
# @param segments [Array<String>] the path's remaining dotted segments, `[]` for
|
|
1181
|
-
# a bare field
|
|
1182
|
-
# @return [String, nil] why `attribute` cannot serve as a correlation key, or
|
|
1183
|
-
# `nil` if it can — see `walk_scalar` for what a non-`nil` reason means
|
|
1184
587
|
def list_or_scalar_violation(owner, attribute, segments)
|
|
1185
588
|
if attribute.list?
|
|
1186
589
|
return "#{attribute.name} is a list — a correlation key must name one instance's own field, " \
|
|
@@ -1190,23 +593,8 @@ module Hecks
|
|
|
1190
593
|
walk_scalar(owner, attribute.type.to_s, segments)
|
|
1191
594
|
end
|
|
1192
595
|
|
|
1193
|
-
# Walks the remaining dotted segments through nested value objects
|
|
1194
|
-
#
|
|
1195
|
-
# step either bottoms out at a real scalar (nil — no violation) or
|
|
1196
|
-
# names why it cannot: still a value object with no more path left,
|
|
1197
|
-
# a value object this domain never declared, a field that value
|
|
1198
|
-
# object does not have, or a segment left over after already
|
|
1199
|
-
# reaching a scalar.
|
|
1200
|
-
#
|
|
1201
|
-
# @param owner [Bluebook::Aggregate] the aggregate whose `value_object` lookup
|
|
1202
|
-
# resolves each stepped-into type name
|
|
1203
|
-
# @param type_name [String] the type to check or step into
|
|
1204
|
-
# @param segments [Array<String>] the dotted path segments remaining to walk
|
|
1205
|
-
# @return [String, nil] `nil` when the walk bottoms out cleanly on a scalar;
|
|
1206
|
-
# otherwise why it cannot: `type_name` is a value object with no segments
|
|
1207
|
-
# left, `type_name` is already a scalar with segments left over, `type_name`
|
|
1208
|
-
# names no value object `owner` declares, the next segment names no field of
|
|
1209
|
-
# that value object, or that field is a list
|
|
596
|
+
# Walks the remaining dotted segments through nested value objects; nil means the walk
|
|
597
|
+
# bottomed out on a scalar, otherwise the string says why it cannot.
|
|
1210
598
|
def walk_scalar(owner, type_name, segments)
|
|
1211
599
|
if segments.empty?
|
|
1212
600
|
return nil if Attribute::PRIMITIVES.include?(type_name)
|