hecks 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/hecks/adapters/driven/claude_code.rb +7 -7
- data/lib/hecks/adapters/driven/d1.rb +187 -23
- data/lib/hecks/adapters/driven/folder.rb +10 -10
- data/lib/hecks/adapters/driven/google_authentication.rb +8 -8
- data/lib/hecks/adapters/driven/governance_authorization.rb +31 -13
- data/lib/hecks/adapters/driven/heki/journal.rb +5 -4
- data/lib/hecks/adapters/driven/heki/saga_store.rb +5 -5
- data/lib/hecks/adapters/driven/heki.rb +13 -7
- data/lib/hecks/adapters/driven/identity_registry.rb +2 -2
- data/lib/hecks/adapters/driven/in_memory_ordering.rb +3 -3
- data/lib/hecks/adapters/driven/lambda/client.rb +34 -9
- data/lib/hecks/adapters/driven/lambda.rb +39 -33
- data/lib/hecks/adapters/driven/local_storage.rb +17 -10
- data/lib/hecks/adapters/driven/memory.rb +205 -9
- data/lib/hecks/adapters/driven/mock_stripe_adapter.rb +1 -1
- data/lib/hecks/adapters/driven/postgres/codec.rb +27 -11
- data/lib/hecks/adapters/driven/postgres/outbox.rb +40 -2
- data/lib/hecks/adapters/driven/postgres/reconnect.rb +23 -7
- data/lib/hecks/adapters/driven/postgres/schema_builder.rb +14 -14
- data/lib/hecks/adapters/driven/postgres.rb +175 -28
- data/lib/hecks/adapters/driven/prism.rb +4 -4
- data/lib/hecks/adapters/driven/sql_query_builder.rb +34 -22
- data/lib/hecks/adapters/driven/sqlite/codec.rb +38 -10
- data/lib/hecks/adapters/driven/sqlite/projection.rb +60 -32
- data/lib/hecks/adapters/driven/sqlite/schema_builder.rb +12 -12
- data/lib/hecks/adapters/driven/sqlite.rb +181 -21
- data/lib/hecks/adapters/driven.rb +4 -4
- data/lib/hecks/adapters/driving/github_webhook.rb +18 -18
- data/lib/hecks/behaviors/dsl.rb +2 -2
- data/lib/hecks/behaviors/expectations.rb +23 -23
- data/lib/hecks/behaviors/ir.rb +1 -1
- data/lib/hecks/behaviors/rspec.rb +1 -1
- data/lib/hecks/behaviors/runner.rb +2 -2
- data/lib/hecks/behaviors.rb +1 -1
- data/lib/hecks/bluebook/aggregate.rb +13 -13
- data/lib/hecks/bluebook/assembly/aggregate_assembly.rb +10 -10
- data/lib/hecks/bluebook/assembly/build.rb +1 -1
- data/lib/hecks/bluebook/assembly/contract.rb +39 -16
- data/lib/hecks/bluebook/assembly/contracts.rb +40 -38
- data/lib/hecks/bluebook/assembly/marks.rb +18 -18
- data/lib/hecks/bluebook/assembly/specializer.rb +19 -21
- data/lib/hecks/bluebook/assembly.rb +9 -9
- data/lib/hecks/bluebook/attribute.rb +9 -9
- data/lib/hecks/bluebook/behaviour/aggregate.rb +11 -11
- data/lib/hecks/bluebook/behaviour/attribute.rb +5 -5
- data/lib/hecks/bluebook/behaviour/chapter.rb +23 -5
- data/lib/hecks/bluebook/behaviour/command.rb +23 -23
- data/lib/hecks/bluebook/behaviour/domain_port.rb +7 -7
- data/lib/hecks/bluebook/behaviour/entity.rb +8 -8
- data/lib/hecks/bluebook/behaviour/hexagon.rb +4 -4
- data/lib/hecks/bluebook/behaviour/lifecycle.rb +5 -5
- data/lib/hecks/bluebook/behaviour/policy.rb +12 -12
- data/lib/hecks/bluebook/behaviour/process_manager.rb +7 -7
- data/lib/hecks/bluebook/behaviour/query.rb +1 -1
- data/lib/hecks/bluebook/behaviour/read_model.rb +8 -8
- data/lib/hecks/bluebook/behaviour/traits.rb +12 -12
- data/lib/hecks/bluebook/behaviour/value_object.rb +6 -6
- data/lib/hecks/bluebook/capabilities.rb +27 -0
- data/lib/hecks/bluebook/chapter.rb +28 -9
- data/lib/hecks/bluebook/command.rb +12 -12
- data/lib/hecks/bluebook/domain_port.rb +9 -9
- data/lib/hecks/bluebook/dsl/adapter_builder.rb +24 -0
- data/lib/hecks/bluebook/dsl/aggregate_builder/sealing.rb +49 -49
- data/lib/hecks/bluebook/dsl/aggregate_builder.rb +96 -96
- data/lib/hecks/bluebook/dsl/attribute_collector.rb +41 -41
- data/lib/hecks/bluebook/dsl/binding_proxy.rb +22 -2
- data/lib/hecks/bluebook/dsl/bluebook_builder/validation.rb +111 -74
- data/lib/hecks/bluebook/dsl/bluebook_builder.rb +48 -30
- data/lib/hecks/bluebook/dsl/bootstrap_table.rb +116 -0
- data/lib/hecks/bluebook/dsl/command_builder.rb +103 -103
- data/lib/hecks/bluebook/dsl/const_shim.rb +46 -15
- data/lib/hecks/bluebook/dsl/domain_port_builder.rb +90 -25
- data/lib/hecks/bluebook/dsl/entity_builder.rb +56 -56
- data/lib/hecks/bluebook/dsl/generic_dispatch.rb +148 -132
- data/lib/hecks/bluebook/dsl/hecksagon_builder.rb +89 -30
- data/lib/hecks/bluebook/dsl/identity_declaration.rb +17 -17
- data/lib/hecks/bluebook/dsl/lifecycle_builder.rb +27 -4
- data/lib/hecks/bluebook/dsl/policy_builder.rb +30 -21
- data/lib/hecks/bluebook/dsl/port_builder.rb +38 -7
- data/lib/hecks/bluebook/dsl/port_operation_builder.rb +56 -22
- data/lib/hecks/bluebook/dsl/process_manager_builder.rb +35 -35
- data/lib/hecks/bluebook/dsl/query_builder.rb +5 -5
- data/lib/hecks/bluebook/dsl/read_model_builder.rb +34 -34
- data/lib/hecks/bluebook/dsl/rule_reference.rb +41 -39
- data/lib/hecks/bluebook/dsl/translation_builder.rb +9 -9
- data/lib/hecks/bluebook/dsl/value_object_builder.rb +16 -16
- data/lib/hecks/bluebook/dsl/word_gate.rb +59 -53
- data/lib/hecks/bluebook/dsl/world_builder.rb +51 -8
- data/lib/hecks/bluebook/entity.rb +11 -11
- data/lib/hecks/bluebook/expression/ast_json.rb +20 -20
- data/lib/hecks/bluebook/expression/ast_reader.rb +3 -3
- data/lib/hecks/bluebook/expression/canonical_form.rb +9 -9
- data/lib/hecks/bluebook/expression/evaluator.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver/block_predicates.rb +18 -18
- data/lib/hecks/bluebook/expression/resolver.rb +60 -62
- data/lib/hecks/bluebook/hexagon.rb +1 -1
- data/lib/hecks/bluebook/lifecycle.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/adapter_judge.rb +1 -1
- data/lib/hecks/bluebook/meta_validator/judge.rb +101 -87
- data/lib/hecks/bluebook/meta_validator/plan.rb +39 -39
- data/lib/hecks/bluebook/meta_validator/port_judge.rb +2 -2
- data/lib/hecks/bluebook/meta_validator/readings.rb +43 -43
- data/lib/hecks/bluebook/meta_validator/reconstruction.rb +44 -37
- data/lib/hecks/bluebook/meta_validator/shapes.rb +25 -21
- data/lib/hecks/bluebook/meta_validator/syntax_boot.rb +145 -31
- data/lib/hecks/bluebook/meta_validator/translation_judge.rb +6 -6
- data/lib/hecks/bluebook/meta_validator/world_judge.rb +5 -5
- data/lib/hecks/bluebook/meta_validator.rb +70 -70
- data/lib/hecks/bluebook/model_check.rb +170 -84
- data/lib/hecks/bluebook/pattern_subset.rb +9 -9
- data/lib/hecks/bluebook/policy.rb +15 -13
- data/lib/hecks/bluebook/process_manager.rb +14 -14
- data/lib/hecks/bluebook/project_register.rb +6 -6
- data/lib/hecks/bluebook/query.rb +4 -4
- data/lib/hecks/bluebook/read_model.rb +14 -14
- data/lib/hecks/bluebook/reference.rb +8 -8
- data/lib/hecks/bluebook/smoke_test.rb +19 -19
- data/lib/hecks/bluebook/synthesizer.rb +12 -12
- data/lib/hecks/bluebook/translation.rb +4 -4
- data/lib/hecks/bluebook/value_object.rb +6 -6
- data/lib/hecks/bluebook.rb +2 -2
- data/lib/hecks/codemod/legacy_dispatch_args.rb +299 -0
- data/lib/hecks/codemod/legacy_dispatch_recorder.rb +186 -0
- data/lib/hecks/codemod.rb +36 -35
- data/lib/hecks/construct.rb +6 -6
- data/lib/hecks/corpus.rb +317 -0
- data/lib/hecks/deprecation.rb +95 -0
- data/lib/hecks/doc/reference.rb +19 -19
- data/lib/hecks/embryonaut_bluebook.rb +11 -11
- data/lib/hecks/facade/cli_door.rb +69 -10
- data/lib/hecks/facade/cli_runner.rb +105 -24
- data/lib/hecks/facade/command_request.rb +23 -0
- data/lib/hecks/facade/handle.rb +79 -32
- data/lib/hecks/facade/json_door.rb +106 -25
- data/lib/hecks/facade/surface/aggregate_door.rb +42 -27
- data/lib/hecks/facade/surface/chapter.rb +26 -17
- data/lib/hecks/facade/surface.rb +16 -3
- data/lib/hecks/facade.rb +15 -4
- data/lib/hecks/forms/app.rb +46 -30
- data/lib/hecks/forms/command_form_renderer.rb +70 -9
- data/lib/hecks/forms/field_renderer.rb +142 -6
- data/lib/hecks/forms/field_shape.rb +180 -19
- data/lib/hecks/forms/html.rb +51 -7
- data/lib/hecks/forms/index_renderer.rb +14 -2
- data/lib/hecks/forms/params.rb +120 -23
- data/lib/hecks/forms/port_argument.rb +2 -2
- data/lib/hecks/forms/query_form_renderer.rb +2 -2
- data/lib/hecks/forms/record_renderer.rb +2 -2
- data/lib/hecks/forms/record_table.rb +1 -1
- data/lib/hecks/forms/value_object_shape.rb +3 -3
- data/lib/hecks/forms.rb +24 -4
- data/lib/hecks/fqn.rb +1 -1
- data/lib/hecks/framework/bluebook/governance.bluebook +9 -0
- data/lib/hecks/framework.rb +48 -17
- data/lib/hecks/freezer.rb +11 -11
- data/lib/hecks/fuzzing/bounded_exhaustive_expressions.rb +90 -90
- data/lib/hecks/fuzzing/combination_miner.rb +119 -0
- data/lib/hecks/fuzzing/concurrent_dispatch.rb +140 -45
- data/lib/hecks/fuzzing/coverage_campaign.rb +118 -0
- data/lib/hecks/fuzzing/differential.rb +158 -0
- data/lib/hecks/fuzzing/domain_generator.rb +694 -0
- data/lib/hecks/fuzzing/era_boundary.rb +37 -18
- data/lib/hecks/fuzzing/form_census.rb +35 -20
- data/lib/hecks/fuzzing/generated_domain_check.rb +95 -0
- data/lib/hecks/fuzzing/invalid_value_generator.rb +6 -6
- data/lib/hecks/fuzzing/isolated_boot.rb +62 -62
- data/lib/hecks/fuzzing/nondeterministic.rb +67 -0
- data/lib/hecks/fuzzing/persistence_parity.rb +19 -21
- data/lib/hecks/fuzzing/properties/corrections.rb +9 -9
- data/lib/hecks/fuzzing/properties/dispatch_and_mutations.rb +154 -49
- data/lib/hecks/fuzzing/properties/guards.rb +42 -42
- data/lib/hecks/fuzzing/properties/invariants_and_aggregation.rb +32 -32
- data/lib/hecks/fuzzing/properties/lifecycle_and_replay.rb +22 -35
- data/lib/hecks/fuzzing/properties/outbox.rb +24 -24
- data/lib/hecks/fuzzing/properties/querying.rb +17 -17
- data/lib/hecks/fuzzing/properties.rb +64 -40
- data/lib/hecks/fuzzing/qa_settings.rb +152 -0
- data/lib/hecks/fuzzing/replay.rb +114 -114
- data/lib/hecks/fuzzing/rotation_priority.rb +10 -10
- data/lib/hecks/fuzzing/rust_gap_manifest.rb +113 -0
- data/lib/hecks/fuzzing/self_consistency.rb +141 -112
- data/lib/hecks/fuzzing/sequence_generator/adversary.rb +35 -35
- data/lib/hecks/fuzzing/sequence_generator/catalog.rb +18 -11
- data/lib/hecks/fuzzing/sequence_generator/outcome_tracker.rb +13 -12
- data/lib/hecks/fuzzing/sequence_generator/picker.rb +21 -12
- data/lib/hecks/fuzzing/sequence_generator/step_builder.rb +58 -31
- data/lib/hecks/fuzzing/sequence_generator.rb +111 -23
- data/lib/hecks/fuzzing/shrinker.rb +197 -0
- data/lib/hecks/fuzzing/structural_skips.rb +23 -130
- data/lib/hecks/fuzzing/sweep_depth.rb +4 -4
- data/lib/hecks/fuzzing/target_capabilities.rb +49 -18
- data/lib/hecks/fuzzing/value_generator.rb +14 -14
- data/lib/hecks/fuzzing.rb +5 -0
- data/lib/hecks/grammar/evolve.rb +10 -10
- data/lib/hecks/grammar.rb +7 -7
- data/lib/hecks/ir.rb +13 -13
- data/lib/hecks/language/bluebook/bluebook.bluebook +41 -0
- data/lib/hecks/language/bluebook/policy.bluebook +11 -1
- data/lib/hecks/language/bluebook/vocabulary.bluebook +348 -13
- data/lib/hecks/language/oidc.json +5 -0
- data/lib/hecks/literal.rb +9 -9
- data/lib/hecks/naming.rb +25 -25
- data/lib/hecks/ports/access_control.rb +58 -2
- data/lib/hecks/ports/agent/answers.rb +83 -6
- data/lib/hecks/ports/agent.rb +119 -35
- data/lib/hecks/ports/authentication.rb +44 -4
- data/lib/hecks/ports/authorization.rb +53 -11
- data/lib/hecks/ports/clock.rb +42 -23
- data/lib/hecks/ports/extraction.rb +16 -0
- data/lib/hecks/ports/identity_assignment.rb +24 -2
- data/lib/hecks/ports/identity_generation.rb +17 -3
- data/lib/hecks/ports/identity_resolution.rb +18 -1
- data/lib/hecks/ports/loading.rb +4 -0
- data/lib/hecks/ports/persistence/append_only.rb +172 -8
- data/lib/hecks/ports/persistence/binding_policy.rb +34 -0
- data/lib/hecks/ports/persistence/codec_boundary.rb +178 -0
- data/lib/hecks/ports/persistence/execution.rb +4 -0
- data/lib/hecks/ports/persistence/null_saga_store.rb +12 -1
- data/lib/hecks/ports/persistence/plugin.rb +42 -4
- data/lib/hecks/ports/persistence/plugins/era/era_check.rb +218 -25
- data/lib/hecks/ports/persistence/plugins/era/era_guard/shape_diff.rb +77 -9
- data/lib/hecks/ports/persistence/plugins/era/era_guard.rb +81 -24
- data/lib/hecks/ports/persistence/plugins/era/era_tamper.rb +29 -18
- data/lib/hecks/ports/persistence/plugins/era/lineage.rb +144 -60
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/era_store.rb +103 -8
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/field_cache.rb +98 -23
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/head_compiler.rb +282 -109
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/mint_transaction.rb +63 -25
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/provisioning.rb +118 -66
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/resumable_backfill.rb +51 -28
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/tail_merge.rb +28 -5
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage/transform_installer.rb +25 -12
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage.rb +129 -34
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/coverage_check.rb +51 -6
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/era_resolver.rb +33 -9
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/merge_coordinator.rb +16 -0
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager/minter.rb +57 -4
- data/lib/hecks/ports/persistence/plugins/era/postgres_era/lineage_manager.rb +25 -2
- data/lib/hecks/ports/persistence/plugins/era/postgres_era.rb +314 -90
- data/lib/hecks/ports/persistence/plugins/era/storage_shape.rb +68 -10
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/approval_digest.rb +9 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_one.rb +9 -2
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/layer_two.rb +42 -8
- data/lib/hecks/ports/persistence/plugins/era/translation/audit/unfed_report.rb +16 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/audit.rb +36 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/reattest.rb +23 -3
- data/lib/hecks/ports/persistence/plugins/era/translation/rule_compiler.rb +18 -19
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/differ.rb +5 -5
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold/writer.rb +1 -1
- data/lib/hecks/ports/persistence/plugins/era/translation/scaffold.rb +2 -2
- data/lib/hecks/ports/persistence/plugins/era.rb +12 -2
- data/lib/hecks/ports/persistence/remote_runtime.rb +9 -2
- data/lib/hecks/ports/persistence/repository_factory.rb +23 -3
- data/lib/hecks/ports/persistence/state_codec.rb +319 -0
- data/lib/hecks/ports/persistence.rb +36 -1
- data/lib/hecks/ports/projection.rb +61 -7
- data/lib/hecks/ports/query/in_memory.rb +3 -3
- data/lib/hecks/ports/query/ordering.rb +6 -6
- data/lib/hecks/ports/query.rb +35 -0
- data/lib/hecks/projections/bootstrap_table.rb +112 -0
- data/lib/hecks/projections/diagrams.rb +75 -75
- data/lib/hecks/projections/glossary/html.rb +3 -3
- data/lib/hecks/projections/glossary/markdown.rb +3 -3
- data/lib/hecks/projections/glossary/mermaid.rb +1 -1
- data/lib/hecks/projections/glossary/sections.rb +1 -1
- data/lib/hecks/projections/glossary/sentences.rb +5 -5
- data/lib/hecks/projections/glossary.rb +15 -15
- data/lib/hecks/projections/ir.rb +1 -1
- data/lib/hecks/projections/model/deviations.rb +18 -17
- data/lib/hecks/projections/model.rb +25 -21
- data/lib/hecks/projections/oidc.rb +7 -7
- data/lib/hecks/projections/parser_table.rb +5 -5
- data/lib/hecks/projections/reference.rb +3 -3
- data/lib/hecks/projections/rust_vocabulary.rb +443 -0
- data/lib/hecks/projections/shape.rb +2 -2
- data/lib/hecks/projections/statements.rb +11 -11
- data/lib/hecks/projections/vocabulary.rb +9 -9
- data/lib/hecks/projections.rb +5 -3
- data/lib/hecks/projector/cli_projector.rb +29 -29
- data/lib/hecks/projector/docs_projector.rb +13 -13
- data/lib/hecks/projector/exporter.rb +42 -21
- data/lib/hecks/projector/ir_projector.rb +1 -1
- data/lib/hecks/projector/narrate_projector.rb +11 -11
- data/lib/hecks/projector/target.rb +13 -13
- data/lib/hecks/projector.rb +15 -15
- data/lib/hecks/query_ir.rb +47 -47
- data/lib/hecks/query_specification/common/comparators.rb +19 -3
- data/lib/hecks/query_specification/common/comparison.rb +109 -27
- data/lib/hecks/query_specification/common/dsl.rb +65 -9
- data/lib/hecks/query_specification/common/null_policy.rb +57 -13
- data/lib/hecks/query_specification/common/null_semantics.rb +4 -0
- data/lib/hecks/query_specification/common/options.rb +25 -0
- data/lib/hecks/query_specification/field_path.rb +69 -15
- data/lib/hecks/query_specification/hop_path.rb +57 -20
- data/lib/hecks/query_specification/read_model/specification.rb +4 -0
- data/lib/hecks/rendering.rb +3 -3
- data/lib/hecks/router/namespace_installer.rb +3 -3
- data/lib/hecks/router.rb +1 -1
- data/lib/hecks/runtime/aggregate_lock.rb +11 -11
- data/lib/hecks/runtime/boot_gates.rb +3 -3
- data/lib/hecks/runtime/caller.rb +8 -8
- data/lib/hecks/runtime/capability_graph.rb +2 -2
- data/lib/hecks/runtime/command_interpreter/argument_gate.rb +22 -25
- data/lib/hecks/runtime/command_interpreter/mutation_applier.rb +42 -63
- data/lib/hecks/runtime/command_interpreter.rb +159 -102
- data/lib/hecks/runtime/command_rules/admissibility.rb +69 -69
- data/lib/hecks/runtime/command_rules/arithmetic.rb +137 -91
- data/lib/hecks/runtime/command_rules/authorization.rb +38 -17
- data/lib/hecks/runtime/command_rules/emission.rb +18 -1
- data/lib/hecks/runtime/command_rules/references.rb +35 -35
- data/lib/hecks/runtime/command_rules.rb +3 -0
- data/lib/hecks/runtime/dependency_planning.rb +11 -11
- data/lib/hecks/runtime/dispatcher.rb +283 -90
- data/lib/hecks/runtime/entity_element.rb +151 -67
- data/lib/hecks/runtime/entity_interpreter.rb +108 -82
- data/lib/hecks/runtime/errors.rb +19 -19
- data/lib/hecks/runtime/event.rb +6 -6
- data/lib/hecks/runtime/identity.rb +22 -22
- data/lib/hecks/runtime/instance.rb +39 -14
- data/lib/hecks/runtime/interpreting.rb +12 -12
- data/lib/hecks/runtime/invocation.rb +276 -0
- data/lib/hecks/runtime/loader.rb +14 -14
- data/lib/hecks/runtime/outbox.rb +23 -23
- data/lib/hecks/runtime/policy_interpreter.rb +54 -54
- data/lib/hecks/runtime/port_operation_interpreter.rb +22 -19
- data/lib/hecks/runtime/query_interpreter.rb +55 -64
- data/lib/hecks/runtime/reaction_invocation.rb +20 -20
- data/lib/hecks/runtime/read_model_interpreter.rb +40 -40
- data/lib/hecks/runtime/rebuild_sweep.rb +4 -4
- data/lib/hecks/runtime/reference_hop.rb +6 -6
- data/lib/hecks/runtime/refusal_wording.rb +92 -115
- data/lib/hecks/runtime/registry/saga_persistence.rb +21 -21
- data/lib/hecks/runtime/registry/verification.rb +36 -26
- data/lib/hecks/runtime/registry.rb +45 -28
- data/lib/hecks/runtime/remote_dispatcher.rb +38 -23
- data/lib/hecks/runtime/routing.rb +10 -153
- data/lib/hecks/runtime/saga_interpreter/correlation.rb +17 -17
- data/lib/hecks/runtime/saga_interpreter.rb +64 -64
- data/lib/hecks/runtime/saga_pending_dispatch.rb +12 -12
- data/lib/hecks/runtime/tenant_check.rb +9 -9
- data/lib/hecks/runtime/tenant_scope.rb +5 -5
- data/lib/hecks/runtime/value/admission.rb +75 -30
- data/lib/hecks/runtime/value/coercion.rb +318 -127
- data/lib/hecks/runtime/value/entity_list_coercion.rb +34 -34
- data/lib/hecks/runtime/value.rb +21 -21
- data/lib/hecks/runtime.rb +7 -7
- data/lib/hecks/storehouse.rb +64 -64
- data/lib/hecks/version.rb +3 -3
- data/lib/hecks/vocabulary.rb +205 -4
- data/lib/hecks.rb +13 -11
- data/lib/rubocop/cop/hecks/fallback_hash_lookup.rb +11 -11
- data/lib/rubocop/cop/hecks/sequential_hash_rename_in_loop.rb +14 -12
- data/lib/rubocop/cop/hecks/thread_shared_ivar_mutation.rb +11 -11
- metadata +22 -2
|
@@ -24,9 +24,9 @@ module Hecks
|
|
|
24
24
|
|
|
25
25
|
private
|
|
26
26
|
|
|
27
|
-
#
|
|
27
|
+
# **Root-first, then the SQLite escape hatch, then the join loop** —
|
|
28
28
|
# each step's own comment names a real, previously-shipped bug the
|
|
29
|
-
# current
|
|
29
|
+
# current order fixes (the reference/TenantScope refusal ordering
|
|
30
30
|
# above, the root-first head processing below). Splitting this
|
|
31
31
|
# into smaller methods would scatter that ordering across method
|
|
32
32
|
# boundaries where a future editor could silently break it, and
|
|
@@ -40,7 +40,7 @@ module Hecks
|
|
|
40
40
|
def project(domain, model, args)
|
|
41
41
|
bluebook = @registry.bluebook(domain)
|
|
42
42
|
rootless = model.reference_target.nil?
|
|
43
|
-
#
|
|
43
|
+
# Before the adapter early-return below, so the SQLite path inherits it.
|
|
44
44
|
# Without this a stale caller passing a wrapped reference gets a
|
|
45
45
|
# path-dependent answer — an adapter could quietly open the wrapped
|
|
46
46
|
# reference while the in-process path reads it whole and finds nothing.
|
|
@@ -49,16 +49,16 @@ module Hecks
|
|
|
49
49
|
# wrong at all.
|
|
50
50
|
refuse_object_reference(model, args) unless rootless
|
|
51
51
|
reference_id = reference(args.fetch(model.reference_name)) unless rootless
|
|
52
|
-
# Computed off the
|
|
52
|
+
# Computed off the original model, before TenantScope wraps it — the
|
|
53
53
|
# "which head(s) do options apply to" question is about what the
|
|
54
54
|
# bluebook author declared, not about the synthetic tenant clause
|
|
55
55
|
# the wrapper adds underneath. Plural (ADR 0055) — `on:` lets more
|
|
56
56
|
# than one many-side head be eligible at once.
|
|
57
57
|
eligible = model.filtered_head_names
|
|
58
58
|
model = TenantScope.apply(model, args)
|
|
59
|
-
# A
|
|
59
|
+
# A rootless, `group_by`-declared, or `count`/`median`-declared
|
|
60
60
|
# model skips the SQLite native escape hatch entirely (there is
|
|
61
|
-
# no root aggregate to look up a repository
|
|
61
|
+
# no root aggregate to look up a repository for when rootless,
|
|
62
62
|
# and `query_read_model` knows nothing about grouping or
|
|
63
63
|
# reducing) — always runs the in-process loop below instead.
|
|
64
64
|
# Correct everywhere ; not SQL-pushed-down for a SQLite-backed
|
|
@@ -73,17 +73,17 @@ module Hecks
|
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
|
|
76
|
-
#
|
|
76
|
+
# **Root first, always** — regardless of `include` order in the
|
|
77
77
|
# bluebook. `read_model_builder.rb`'s own `include` is
|
|
78
78
|
# documented "Order-independent" (the `:many` flag is resolved
|
|
79
79
|
# at build time, once `@reference_target` is known), but that
|
|
80
|
-
# promise was never kept
|
|
80
|
+
# promise was never kept here: this loop used to run heads in
|
|
81
81
|
# their literal declared order and match each "many" head
|
|
82
|
-
# against whatever was
|
|
82
|
+
# against whatever was already in `projected` — empty, the
|
|
83
83
|
# very first time through, if a many-side head happened to be
|
|
84
84
|
# declared before the root. A real, live bug (not a guess):
|
|
85
85
|
# `include Promotion` before `include Item` on a read model
|
|
86
|
-
# whose root
|
|
86
|
+
# whose root is Item silently returned an empty array for
|
|
87
87
|
# Promotion — no error, just a wrong, too-small answer — while
|
|
88
88
|
# the reverse order worked purely by accident. `partition`,
|
|
89
89
|
# not `sort_by`: Ruby's `sort_by` is not guaranteed stable,
|
|
@@ -97,14 +97,14 @@ module Hecks
|
|
|
97
97
|
[fetch(bluebook, domain, head[:aggregate], reference_id)]
|
|
98
98
|
elsif rootless
|
|
99
99
|
# No root to FK-match against — a rootless model reads
|
|
100
|
-
# each of its own heads
|
|
101
|
-
# heads on one rootless model are
|
|
100
|
+
# each of its own heads whole, independently. Multiple
|
|
101
|
+
# heads on one rootless model are never cross-joined
|
|
102
102
|
# against each other, and there is no DSL to declare
|
|
103
103
|
# one if you wanted to — `ReadModelBuilder#include_impl`
|
|
104
104
|
# takes only `type`/`as:` (checked directly, not
|
|
105
105
|
# assumed), and `group_by` groups this bulk read's own
|
|
106
106
|
# output, it names no predicate between two heads.
|
|
107
|
-
# Building a cross-join here would mean
|
|
107
|
+
# Building a cross-join here would mean choosing a join
|
|
108
108
|
# semantics (equality on which fields?) nobody has
|
|
109
109
|
# declared — a real, deliberate scope limit pending a
|
|
110
110
|
# future `include ..., joins: ...`-shaped grammar
|
|
@@ -124,7 +124,7 @@ module Hecks
|
|
|
124
124
|
projected << { aggregate: head[:aggregate], rows: rows }
|
|
125
125
|
rows_by_as[head[:as]] = head[:many] ? rows : rows.first
|
|
126
126
|
end
|
|
127
|
-
# Declared order preserved in the
|
|
127
|
+
# Declared order preserved in the output — only the
|
|
128
128
|
# computation above needed reordering, not what a caller sees
|
|
129
129
|
# back.
|
|
130
130
|
heads = model.aggregate_heads.to_h { |head| [head[:as], rows_by_as[head[:as]]] }
|
|
@@ -145,9 +145,9 @@ module Hecks
|
|
|
145
145
|
end]
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
-
#
|
|
148
|
+
# **The root-first fix's own fix** — root-first alone only reaches one
|
|
149
149
|
# level: it guarantees the root is in `projected` before any other
|
|
150
|
-
# head is matched, but a
|
|
150
|
+
# head is matched, but a chain of non-root heads (a head that
|
|
151
151
|
# references another non-root head, not the root) is still
|
|
152
152
|
# matched against whatever declaration order happened to put in
|
|
153
153
|
# `projected` so far. `include Coupon` before `include Promotion`
|
|
@@ -160,9 +160,9 @@ module Hecks
|
|
|
160
160
|
# to declare `include` in dependency order (the same promise
|
|
161
161
|
# `read_model_builder.rb` already makes and this file is the one
|
|
162
162
|
# place obligated to keep), but by topologically sorting the
|
|
163
|
-
# non-root heads on their
|
|
163
|
+
# non-root heads on their own declared reference fields before
|
|
164
164
|
# this method's runtime matching ever runs — Kahn's algorithm,
|
|
165
|
-
# picking ready heads in
|
|
165
|
+
# picking ready heads in declared order at each step so declaring
|
|
166
166
|
# order still governs whenever there is no dependency to break a
|
|
167
167
|
# tie. This generalizes root-first (a chain of length 1) to a
|
|
168
168
|
# chain of any depth, and to a head depending on more than one
|
|
@@ -192,13 +192,13 @@ module Hecks
|
|
|
192
192
|
ordered
|
|
193
193
|
end
|
|
194
194
|
|
|
195
|
-
# Which
|
|
195
|
+
# Which other declared (non-root) heads a head's own aggregate
|
|
196
196
|
# holds a reference field toward — the same relationship this
|
|
197
197
|
# file's runtime matching checks record-by-record, asked here
|
|
198
198
|
# statically, once, to order heads before any record is read.
|
|
199
199
|
#
|
|
200
200
|
# `head[:aggregate]` names whatever `include` was given — and
|
|
201
|
-
# `include` accepts a nested
|
|
201
|
+
# `include` accepts a nested entity (Member, nested under
|
|
202
202
|
# ValueObject ; Handler and Dispatch, nested under ProcessManager
|
|
203
203
|
# — bluebook.bluebook's own `WholeBluebook` read model includes
|
|
204
204
|
# all three) just as readily as a top-level aggregate.
|
|
@@ -220,7 +220,7 @@ module Hecks
|
|
|
220
220
|
.map { |other| other[:aggregate] }
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
-
# `group_by`'s own declared fields, checked against the
|
|
223
|
+
# `group_by`'s own declared fields, checked against the one
|
|
224
224
|
# many-side head they apply to (`seal_group_by` already refuses
|
|
225
225
|
# zero or several) — resolved here, once, rather than re-derived
|
|
226
226
|
# per row. Raises loudly on a typo'd field name rather than
|
|
@@ -232,15 +232,15 @@ module Hecks
|
|
|
232
232
|
aggregate = bluebook.aggregate(target[:aggregate])
|
|
233
233
|
model.group_by_fields.each do |field|
|
|
234
234
|
next if aggregate.attribute(field)
|
|
235
|
-
#
|
|
235
|
+
# The lifecycle field is a field, and refusing it here was a drift
|
|
236
236
|
# between two halves of the same language: `where(status: "logged")`
|
|
237
237
|
# has always been legal on the same aggregate, because a lifecycle
|
|
238
238
|
# state is stored on the record like anything else — it is simply
|
|
239
239
|
# declared by `lifecycle :status` rather than by `attribute`.
|
|
240
240
|
#
|
|
241
241
|
# It is also the grouping anybody actually wants. "How are we doing"
|
|
242
|
-
# over a bug ledger
|
|
243
|
-
# group by every field
|
|
242
|
+
# over a bug ledger is the count per status, and a report that could
|
|
243
|
+
# group by every field except that one could not answer the question
|
|
244
244
|
# reports exist for.
|
|
245
245
|
next if aggregate.lifecycle && aggregate.lifecycle.field.to_sym == field.to_sym
|
|
246
246
|
|
|
@@ -253,7 +253,7 @@ module Hecks
|
|
|
253
253
|
|
|
254
254
|
# One level of nesting per field, in `group_by`'s own declared
|
|
255
255
|
# order — the leaf is the row with every grouped field removed
|
|
256
|
-
# (already spent, as the keys that reached it).
|
|
256
|
+
# (already spent, as the keys that reached it). Assumes the full
|
|
257
257
|
# `group_by` path uniquely identifies one row (true for grouping by
|
|
258
258
|
# an aggregate's own full identity, ConsoleSettings' own real use)
|
|
259
259
|
# — `leaves.first` silently keeps only the first row when several
|
|
@@ -263,9 +263,9 @@ module Hecks
|
|
|
263
263
|
def nest(rows, fields)
|
|
264
264
|
field, *rest = fields
|
|
265
265
|
rows.group_by { |row| row[field] }.transform_values do |group|
|
|
266
|
-
# Strip
|
|
266
|
+
# Strip only the field just grouped by, not the whole remaining
|
|
267
267
|
# list — `rest`'s own fields have to survive into the recursive
|
|
268
|
-
# call below, or the
|
|
268
|
+
# call below, or the next level groups by a key that's already
|
|
269
269
|
# gone (found by trying it: a two-field group_by's own second
|
|
270
270
|
# level came back keyed `nil` for every group, every time).
|
|
271
271
|
stripped = group.map { |row| row.reject { |key, _| key == field } }
|
|
@@ -273,7 +273,7 @@ module Hecks
|
|
|
273
273
|
end
|
|
274
274
|
end
|
|
275
275
|
|
|
276
|
-
# `count`/`median`'s own declared target — the
|
|
276
|
+
# `count`/`median`'s own declared target — the same single
|
|
277
277
|
# many-side head `group_by_target` resolves, for the same reason
|
|
278
278
|
# (`seal_aggregation` already refuses zero or several many-side
|
|
279
279
|
# heads, and refuses count/median declared alongside group_by, so
|
|
@@ -303,12 +303,12 @@ module Hecks
|
|
|
303
303
|
target
|
|
304
304
|
end
|
|
305
305
|
|
|
306
|
-
#
|
|
307
|
-
# middle value, sorted ; an
|
|
306
|
+
# **The standard definition**. An odd count's median is its one true
|
|
307
|
+
# middle value, sorted ; an even count's median is the average of
|
|
308
308
|
# its two middle values — the common convention (as opposed to,
|
|
309
309
|
# say, always taking the lower of the two), and the one this
|
|
310
310
|
# session's own task named explicitly as the deliberate choice.
|
|
311
|
-
# An
|
|
311
|
+
# An empty collection has no median: nil, not zero, so a caller
|
|
312
312
|
# cannot mistake "nothing to average" for "the values averaged to
|
|
313
313
|
# zero". Reuses `Ports::Query::InMemory`'s own field reading
|
|
314
314
|
# (`FieldPath.dig` + `comparable`) — the same unwrap `where`/
|
|
@@ -326,8 +326,8 @@ module Hecks
|
|
|
326
326
|
|
|
327
327
|
def fetch(bluebook, domain, aggregate_name, id)
|
|
328
328
|
@registry.read_repository(domain, bluebook.aggregate(aggregate_name)).find(id) ||
|
|
329
|
-
raise(NotFound, RefusalWording.
|
|
330
|
-
|
|
329
|
+
raise(NotFound, RefusalWording.render_site("NotFound", "read_model_reference_missing",
|
|
330
|
+
aggregate: aggregate_name, offered: Rendering.describe(id)))
|
|
331
331
|
end
|
|
332
332
|
|
|
333
333
|
def records(bluebook, domain, aggregate_name)
|
|
@@ -335,27 +335,27 @@ module Hecks
|
|
|
335
335
|
aggregate ? @registry.read_repository(domain, aggregate).all : []
|
|
336
336
|
end
|
|
337
337
|
|
|
338
|
-
# A stored reference holds the target's id inside the
|
|
339
|
-
#
|
|
338
|
+
# A stored reference holds the target's id inside the reference
|
|
339
|
+
# attribute's own declared shape, so reading it is reading that shape —
|
|
340
340
|
# a different thing from the identity unwrap that was removed. An
|
|
341
|
-
#
|
|
341
|
+
# identity is declared as a path and followed (Runtime::Identity) ; a
|
|
342
342
|
# reference has no path of its own, and `Value.scalar` refuses a
|
|
343
343
|
# composite rather than guessing which field was meant.
|
|
344
344
|
#
|
|
345
345
|
# Storing the scalar itself would remove this reading altogether. That
|
|
346
|
-
# is a change to how references are
|
|
346
|
+
# is a change to how references are stored, not to how identities are
|
|
347
347
|
# declared, so it is not made here.
|
|
348
348
|
# An ask names itself where a command would name itself, and says the
|
|
349
349
|
# same thing about the same shape. `Value.refuse_object_reference` is
|
|
350
|
-
# not reused because it speaks of a
|
|
350
|
+
# not reused because it speaks of a command and its attribute ; a read
|
|
351
351
|
# model has a query name and one declared reference.
|
|
352
352
|
def refuse_object_reference(model, args)
|
|
353
353
|
offered = args.fetch(model.reference_name, nil)
|
|
354
354
|
return unless offered.is_a?(Hash) || offered.is_a?(Value)
|
|
355
355
|
|
|
356
356
|
raise TypeMismatch,
|
|
357
|
-
RefusalWording.
|
|
358
|
-
|
|
357
|
+
RefusalWording.render_site("TypeMismatch", "read_model_object_reference",
|
|
358
|
+
query: model.query_name, field: model.reference_name)
|
|
359
359
|
end
|
|
360
360
|
|
|
361
361
|
# A reference is the id, in the argument and in the stored row alike.
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
module Hecks
|
|
2
2
|
module Runtime
|
|
3
|
-
#
|
|
3
|
+
# The out-of-band half of `projects` (S12, ADR 0025 — "Consistency
|
|
4
4
|
# across aggregate boundaries"). A projected field is never written
|
|
5
5
|
# by the command that reads it — nothing at dispatch time takes a
|
|
6
6
|
# live cross-aggregate read the way `CommandRules::References
|
|
7
7
|
# #dereference` still does — so this is the one place a projected
|
|
8
8
|
# field's value actually gets copied over: walk every record of the
|
|
9
|
-
#
|
|
9
|
+
# owning aggregate, resolve each of its own `projected_fields`
|
|
10
10
|
# through the reference it names, and `save` the local copy.
|
|
11
11
|
#
|
|
12
|
-
#
|
|
12
|
+
# **Explicit and callable, not automatic** — no on-boot detection of a
|
|
13
13
|
# freshly-declared `projects` with no held-era precedent, no
|
|
14
14
|
# generated `Policy#for_each` reaction keeping it live in real
|
|
15
15
|
# time as the target changes. Both are real extensions this same
|
|
@@ -19,7 +19,7 @@ module Hecks
|
|
|
19
19
|
# needs — proven to work end to end before either automatic
|
|
20
20
|
# trigger is built on top of it.
|
|
21
21
|
#
|
|
22
|
-
#
|
|
22
|
+
# **Needs no new adapter capability**. `find`/`all`/`save` are the same
|
|
23
23
|
# three primitives every real adapter already answers identically
|
|
24
24
|
# (`Ports::Persistence::AppendOnly#save` — append, then project,
|
|
25
25
|
# the same for Memory/Postgres/SQLite/D1/Heki) — confirmed by
|
|
@@ -13,12 +13,12 @@ module Hecks
|
|
|
13
13
|
# answers a hop for free, with no per-engine code and no way for
|
|
14
14
|
# one engine to forget it.
|
|
15
15
|
#
|
|
16
|
-
# A hop's own filtering never happens here —
|
|
16
|
+
# A hop's own filtering never happens here — resolving one hop
|
|
17
17
|
# means running one ordinary, adapter-agnostic query against the
|
|
18
|
-
# hop's
|
|
18
|
+
# hop's target aggregate (through the same Ports::Query boundary
|
|
19
19
|
# any other query goes through), and folding the ids it answers
|
|
20
20
|
# back in as a local membership check. A multi-hop chain resolves
|
|
21
|
-
# from the
|
|
21
|
+
# from the far end inward: `fold` only ever peels off the head hop
|
|
22
22
|
# (QuerySpecification::HopPath.next_hop, the one-step primitive),
|
|
23
23
|
# and hands everything still left in the tail to a recursive
|
|
24
24
|
# `apply` call — so hop 2, hop 3, and so on each get resolved by
|
|
@@ -63,12 +63,12 @@ module Hecks
|
|
|
63
63
|
QuerySpecification::Common::WhereClause.new(field: hop.attribute.name, op: "in", value: ids)
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
# Every id the inner clause admits on the hop's
|
|
66
|
+
# Every id the inner clause admits on the hop's target — one
|
|
67
67
|
# whole, ordinary query against the target's own repository,
|
|
68
68
|
# through the very same Ports::Query boundary the outer ask
|
|
69
69
|
# uses, so a hop is answered by whatever engine the target
|
|
70
70
|
# aggregate is actually bound to (which may not be the engine
|
|
71
|
-
# the
|
|
71
|
+
# the outer aggregate is bound to at all) rather than by a
|
|
72
72
|
# second reading of the comparators.
|
|
73
73
|
def matching_ids(domain, target, wheres, args, registry:)
|
|
74
74
|
spec = apply(QuerySpecification::Common::Options.new(wheres: wheres), args,
|
|
@@ -81,7 +81,7 @@ module Hecks
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
# Never returned to a caller that might call an IR-level method
|
|
84
|
-
# (`to_h`, …) whose
|
|
84
|
+
# (`to_h`, …) whose own internal `wheres` read would resolve
|
|
85
85
|
# against the original object, not this override — the exact
|
|
86
86
|
# caution TenantScope::Scoped's own comment gives, for the exact
|
|
87
87
|
# same reason: SimpleDelegator only intercepts calls made
|
|
@@ -1,134 +1,111 @@
|
|
|
1
|
+
require_relative "../vocabulary"
|
|
2
|
+
|
|
1
3
|
module Hecks
|
|
2
4
|
module Runtime
|
|
3
5
|
# Every DomainRefusal wording that is not already data — `given`/
|
|
4
6
|
# `ensures`/a declared `invariant` already carry their own description,
|
|
5
7
|
# read at dispatch time off the command or value object that declared
|
|
6
|
-
# them. These are different in kind:
|
|
8
|
+
# them. These are different in kind: language-level refusals, the same
|
|
7
9
|
# wording for every domain, not authored per-bluebook.
|
|
8
10
|
#
|
|
9
|
-
#
|
|
10
|
-
# (language/bluebook/vocabulary.bluebook)
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
# Read off the generated table, not typed a second time. The rows are
|
|
12
|
+
# Vocabulary::RefusalTemplate (language/bluebook/vocabulary.bluebook),
|
|
13
|
+
# projected into lib/hecks/vocabulary.rb by bin/project_vocabulary and
|
|
14
|
+
# into rust/src/kernel/vocab/refusal_template.rs by
|
|
15
|
+
# bin/project_rust_vocabulary; both regenerations are diffed in CI, so
|
|
16
|
+
# there is no hand copy left here to drift. Declared order is kept.
|
|
17
|
+
#
|
|
18
|
+
# **The arguments are data too**. Vocabulary::RefusalSiteArgument names the
|
|
19
|
+
# values each site takes and how each is written (a list's separator,
|
|
20
|
+
# its sort, its empty reading, its quoting). Call sites use
|
|
21
|
+
# `render_site` and hand over raw values; the Rust kernel's typed
|
|
22
|
+
# `render_args` applies the same rows, and the projection that writes it
|
|
23
|
+
# pins every site's output against `format_argument`/`substitute` here.
|
|
15
24
|
module RefusalWording
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
# rubocop:disable-next Layout/HashAlignment
|
|
23
|
-
TEMPLATES = {
|
|
24
|
-
%w[NotFound creating_no_identity] =>
|
|
25
|
-
"{command} creates a {aggregate} — pass {identity}:",
|
|
26
|
-
%w[AlreadyExists creating_duplicate] =>
|
|
27
|
-
"{command} creates a {aggregate} that already exists — {identity} {offered}",
|
|
28
|
-
%w[AlreadyExists entity_duplicate] =>
|
|
29
|
-
"a {entity} already exists on {aggregate} — {identity} {offered}",
|
|
30
|
-
%w[NotFound acting_no_identity] =>
|
|
31
|
-
"{command} acts on an existing {aggregate} — pass {identity}:",
|
|
32
|
-
%w[NotFound record_missing] =>
|
|
33
|
-
"no {aggregate} with {identity} {offered}",
|
|
34
|
-
%w[NotFound entity_parent_no_identity] =>
|
|
35
|
-
"{command} acts on a {aggregate}'s {entity} — pass {identity}:",
|
|
36
|
-
%w[UnknownVerb entity_unknown] =>
|
|
37
|
-
"{aggregate} has no entity {entity}",
|
|
38
|
-
%w[NotFound entity_element_no_identity] =>
|
|
39
|
-
"{command} acts on one {entity} — pass {identity}:",
|
|
40
|
-
%w[NotFound entity_element_missing] =>
|
|
41
|
-
"no {entity} with {identity} {wants} on {aggregate} {parent_id}",
|
|
42
|
-
%w[NotFound reference_target_missing] =>
|
|
43
|
-
"no {target} with {heads} {key}",
|
|
44
|
-
%w[NotFound read_model_reference_missing] =>
|
|
45
|
-
"no {aggregate} with reference {offered}",
|
|
46
|
-
%w[TypeMismatch read_model_object_reference] =>
|
|
47
|
-
"{query} refused — a reference is an id, and {field} arrived as an object",
|
|
48
|
-
%w[UnknownVerb no_query] =>
|
|
49
|
-
"{aggregate} has no query {query}",
|
|
50
|
-
%w[UnknownVerb entity_query_missing] =>
|
|
51
|
-
"{entity} has no query {query}",
|
|
52
|
-
%w[UnknownVerb entity_holds_no_list] =>
|
|
53
|
-
"{aggregate} holds no list of {entity}",
|
|
54
|
-
%w[UnknownVerb entity_no_command] =>
|
|
55
|
-
"{entity} has no command {command}",
|
|
56
|
-
%w[UnknownVerb aggregate_no_command] =>
|
|
57
|
-
"{aggregate} has no command {command}",
|
|
58
|
-
%w[UnknownVerb port_no_operation] =>
|
|
59
|
-
"{port} has no operation {operation}",
|
|
60
|
-
%w[UnknownVerb no_domain] =>
|
|
61
|
-
"no domain {domain} loaded (verb {verb})",
|
|
62
|
-
%w[UnknownVerb no_read_model] =>
|
|
63
|
-
"{domain} has no read model {query}",
|
|
64
|
-
%w[UnknownVerb not_fully_qualified] =>
|
|
65
|
-
"{verb} is not a fully-qualified verb (Domain::Aggregate.Command)",
|
|
66
|
-
%w[UnknownVerb no_aggregate] =>
|
|
67
|
-
"{domain} has no aggregate {aggregate}",
|
|
68
|
-
%w[LifecycleRefused transition_blocked] =>
|
|
69
|
-
"{command} refused — {field} is {current}, and {command} moves it only from {allowed}",
|
|
70
|
-
%w[TypeMismatch value_object_shape] =>
|
|
71
|
-
"{name} is a {type} — pass its fields as an object, not {offered}",
|
|
72
|
-
%w[TypeMismatch reference_wrong_shape] =>
|
|
73
|
-
"{command} refused — a reference is an id, and {attribute} arrived as {offered}{known_by}",
|
|
74
|
-
%w[TypeMismatch multi_field_scalar] =>
|
|
75
|
-
"{type} has multiple fields and cannot stand in for a scalar",
|
|
76
|
-
%w[TypeMismatch composite_identity] =>
|
|
77
|
-
"{type} is a composite identity — an identity must have exactly one field",
|
|
78
|
-
%w[TypeMismatch numeric_field] =>
|
|
79
|
-
"{type}.{field} expects {expected}, got {offered}",
|
|
80
|
-
%w[TypeMismatch non_finite_field] =>
|
|
81
|
-
"{type}.{field} must be a finite number, got {offered}",
|
|
82
|
-
%w[TypeMismatch integer_range] =>
|
|
83
|
-
"{type}.{field} must fit in a 64-bit integer, got {offered}",
|
|
84
|
-
%w[TypeMismatch pattern_mismatch] =>
|
|
85
|
-
"{type}.{field} must match {pattern}, got {offered}",
|
|
86
|
-
%w[TypeMismatch arithmetic_amount] =>
|
|
87
|
-
"{op} of {target} needs an Integer, got {offered}",
|
|
88
|
-
%w[TypeMismatch arithmetic_current] =>
|
|
89
|
-
"{op} of {target} needs an Integer {target}, got {offered}",
|
|
90
|
-
%w[TypeMismatch arithmetic_shared_field] =>
|
|
91
|
-
"{op} of {target} needs a value object with one shared Integer field",
|
|
92
|
-
%w[UnknownArgument unknown_args] =>
|
|
93
|
-
"{command} does not declare {unknown} — it takes {declared}",
|
|
94
|
-
%w[AbsentArgument absent_args] =>
|
|
95
|
-
"{command} was not given {absent} — it takes {declared}",
|
|
96
|
-
%w[InvariantViolation closed_set_member] =>
|
|
97
|
-
"{type} admits {admitted} — got {offered}",
|
|
98
|
-
%w[InvariantViolation value_object_invariant] =>
|
|
99
|
-
"{name} invariant violated — {description} (given {offered})",
|
|
100
|
-
%w[InvariantViolation admits_declared_set] =>
|
|
101
|
-
"{name} admits {admits} — {admitted} — got {offered}",
|
|
102
|
-
%w[InvariantViolation undeclared_set] =>
|
|
103
|
-
"{name} admits {admits}, which this chapter does not declare — a closed set is named Aggregate::SetName, and it must " \
|
|
104
|
-
"be one the bluebook actually holds",
|
|
105
|
-
%w[Unauthorized tenant_required] =>
|
|
106
|
-
"{query} declares authorize with tenant: {field} — pass {field}: to name which {field} this ask is scoped to",
|
|
107
|
-
%w[Unauthorized role_mismatch] =>
|
|
108
|
-
"{command} refused — role: {role}, and the caller stated {caller_role}",
|
|
109
|
-
%w[Unauthorized cross_tenant_reference] =>
|
|
110
|
-
"{aggregate} {field} is {tenant}, but {attribute} names a {target} whose own {target_field} " \
|
|
111
|
-
"is {other} — a cross-tenant reference",
|
|
112
|
-
%w[AttributeAbsent absent_read] =>
|
|
113
|
-
"{aggregate} {field} is absent on this record — declared, not optional, and added since it was written. Backfill it " \
|
|
114
|
-
"in a translation (backfill :{field}, default: ...), or declare it optional: true",
|
|
115
|
-
%w[ProjectionAbsent absent_read] =>
|
|
116
|
-
"{aggregate} {field} is not yet projected on this record — declared via projects :{field}, but no rebuild sweep has " \
|
|
117
|
-
"populated it. Run the sweep, or read {reference}.{remote_field} directly if this rule cannot wait"
|
|
118
|
-
}.freeze
|
|
25
|
+
TEMPLATES = Hecks::Vocabulary.rows("RefusalTemplate")
|
|
26
|
+
.to_h { |row| [[row["refusal"], row["site"]].freeze, row["template"]] }
|
|
27
|
+
.freeze
|
|
28
|
+
|
|
29
|
+
SHAPES = %w[scalar list].freeze
|
|
30
|
+
QUOTINGS = %w[none inspect].freeze
|
|
119
31
|
|
|
120
32
|
module_function
|
|
121
33
|
|
|
122
34
|
# Plain text substitution, never expression syntax — a template is
|
|
123
|
-
# read, not evaluated.
|
|
124
|
-
#
|
|
125
|
-
# identity reading, …) and this only replaces the markers.
|
|
35
|
+
# read, not evaluated. Values arrive already formatted; prefer
|
|
36
|
+
# `render_site`, which formats them off the declared rows.
|
|
126
37
|
def render(refusal, site, **values)
|
|
127
|
-
template
|
|
38
|
+
substitute(template(refusal, site), values)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The one door call sites use: exactly the arguments the site
|
|
42
|
+
# declares (a missing or undeclared one raises ArgumentError before
|
|
43
|
+
# any wording exists), each formatted by its RefusalSiteArgument row,
|
|
44
|
+
# substituted in declared order.
|
|
45
|
+
#
|
|
46
|
+
# RefusalWording.render_site("UnknownArgument", "unknown_args",
|
|
47
|
+
# command: "Close", unknown: [:parcel], declared: [])
|
|
48
|
+
# # => "Close does not declare parcel — it takes none"
|
|
49
|
+
def render_site(refusal, site, **arguments)
|
|
50
|
+
specs = argument_rows(refusal, site)
|
|
51
|
+
declared = specs.map { |spec| spec["argument"].to_sym }
|
|
52
|
+
missing = declared - arguments.keys
|
|
53
|
+
extra = arguments.keys - declared
|
|
54
|
+
if missing.any? || extra.any?
|
|
55
|
+
raise ArgumentError, "#{refusal}/#{site} takes #{declared.join(', ')} — " \
|
|
56
|
+
"missing: #{missing.join(', ')}; undeclared: #{extra.join(', ')}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
render_with(template(refusal, site), specs, arguments)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# `render_site` without the registry lookups: a template, its
|
|
63
|
+
# argument rows, and raw values. The Rust projection calls this with
|
|
64
|
+
# the chapter's own rows to compute the expected wording it pins.
|
|
65
|
+
def render_with(template, specs, arguments)
|
|
66
|
+
values = specs.to_h do |spec|
|
|
67
|
+
name = spec["argument"].to_sym
|
|
68
|
+
[name, format_argument(spec, arguments.fetch(name))]
|
|
69
|
+
end
|
|
70
|
+
substitute(template, values)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# One argument, written the way its row says. A list is sorted first
|
|
74
|
+
# (before quoting), then each item quoted, then joined; an empty list
|
|
75
|
+
# reads `when_empty`. A scalar is quoted or taken as its own text.
|
|
76
|
+
def format_argument(spec, value)
|
|
77
|
+
inspect = spec.fetch("quoting") == "inspect"
|
|
78
|
+
return inspect ? value.inspect : value.to_s unless spec.fetch("shape") == "list"
|
|
79
|
+
|
|
80
|
+
items = Array(value)
|
|
81
|
+
items = items.sort if spec.fetch("sorted") == "true"
|
|
82
|
+
items = items.map(&:inspect) if inspect
|
|
83
|
+
items.empty? ? spec.fetch("when_empty") : items.join(spec.fetch("separator"))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def substitute(template, values)
|
|
87
|
+
values.reduce(template) { |text, (key, value)| text.gsub("{#{key}}", value.to_s) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def template(refusal, site)
|
|
91
|
+
TEMPLATES.fetch([refusal, site]) do
|
|
128
92
|
raise KeyError, "no refusal template for #{refusal}/#{site} — declare it in " \
|
|
129
93
|
"Vocabulary::RefusalTemplate first"
|
|
130
94
|
end
|
|
131
|
-
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Read lazily, not into a constant: bin/project_vocabulary boots
|
|
98
|
+
# `hecks` (and so this file) before it writes the table a newly
|
|
99
|
+
# declared site's rows live in.
|
|
100
|
+
def argument_rows(refusal, site)
|
|
101
|
+
@argument_rows ||= Hecks::Vocabulary.rows("RefusalSiteArgument")
|
|
102
|
+
.group_by { |row| [row["refusal"], row["site"]] }
|
|
103
|
+
.transform_values(&:freeze)
|
|
104
|
+
.freeze
|
|
105
|
+
@argument_rows.fetch([refusal, site]) do
|
|
106
|
+
raise KeyError, "no refusal arguments for #{refusal}/#{site} — declare them in " \
|
|
107
|
+
"Vocabulary::RefusalSiteArgument first"
|
|
108
|
+
end
|
|
132
109
|
end
|
|
133
110
|
end
|
|
134
111
|
end
|