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
|
@@ -12,7 +12,7 @@ module Hecks
|
|
|
12
12
|
# typed Value. Extended into Value, so every method here reads as
|
|
13
13
|
# `Value.for`, `Value.build`, … — `self` is the Value class.
|
|
14
14
|
module Coercion
|
|
15
|
-
#
|
|
15
|
+
# The four SHAPES an attribute's value can take — named here because
|
|
16
16
|
# `for_attribute` immediately below is the one place that actually
|
|
17
17
|
# branches on all four, and nowhere else in the language collects
|
|
18
18
|
# them into a single closed list. `Attribute#list?`/`#optional?`
|
|
@@ -23,7 +23,7 @@ module Hecks
|
|
|
23
23
|
# below — but the branch is exactly as real, so it gets a name here
|
|
24
24
|
# too rather than staying anonymous.
|
|
25
25
|
#
|
|
26
|
-
# A
|
|
26
|
+
# A second runtime's kernel ports this method by hand (rust/src/
|
|
27
27
|
# kernel/attribute_shapes/*.rs — one file per name in this array,
|
|
28
28
|
# generated into a Rust enum by bin/project_kernel_capabilities so
|
|
29
29
|
# every match over it is compiler-checked exhaustive). If a fifth
|
|
@@ -49,13 +49,13 @@ module Hecks
|
|
|
49
49
|
# recursively via `build`) with `scalar` as what's left once
|
|
50
50
|
# neither of those applies (the raw value, passed through
|
|
51
51
|
# unchanged).
|
|
52
|
-
# `boundary: false` is the
|
|
52
|
+
# `boundary: false` is the query door (`QueryInterpreter#normalize_args`):
|
|
53
53
|
# a query attribute's declared type names the argument for callers and
|
|
54
54
|
# generators, never a runtime shape — comparison unwraps both sides
|
|
55
55
|
# itself, so a `reference: {value: ...}` offered against a `String`
|
|
56
56
|
# query field is the documented allowance (see banking's own
|
|
57
57
|
# `Account.OpenForCustomer`), not a C3.8 mismatch.
|
|
58
|
-
# `argument: true` is the
|
|
58
|
+
# `argument: true` is the command/entity/port argument door only
|
|
59
59
|
# (`Interpreting#normalize_args`, every command/entity/port
|
|
60
60
|
# dispatch): the one place a nil for a non-optional attribute is
|
|
61
61
|
# the caller leaving a required argument empty (C3.7), absorbed
|
|
@@ -66,9 +66,9 @@ module Hecks
|
|
|
66
66
|
# legitimate "absent is not empty" value the aggregate's own
|
|
67
67
|
# attribute may hold. `QueryInterpreter#normalize_args` never
|
|
68
68
|
# passes `argument: true` — a null required value-object-typed
|
|
69
|
-
#
|
|
69
|
+
# query argument is checked, and refused, entirely on its own
|
|
70
70
|
# side (`null_vo_argument!`, query_interpreter.rb) precisely so
|
|
71
|
-
# it does
|
|
71
|
+
# it does not reach this default-absorbing fallback (QualityControl
|
|
72
72
|
# BUG#36 — a query's own null VO argument must refuse regardless
|
|
73
73
|
# of any default, unlike a command's).
|
|
74
74
|
def for_attribute(aggregate, attribute, value, boundary: true, argument: false)
|
|
@@ -78,12 +78,12 @@ module Hecks
|
|
|
78
78
|
return hydrate_entity_list(aggregate, attribute, value) if attribute.list? # :list
|
|
79
79
|
return value unless aggregate.respond_to?(:value_object)
|
|
80
80
|
|
|
81
|
-
#
|
|
81
|
+
# The set the attribute names is checked where the attribute is known.
|
|
82
82
|
# `build` below sees only the value object, never which attribute asked
|
|
83
83
|
# for it, so a command argument's `admits:` has to be read here — this
|
|
84
84
|
# is the door every argument and every head field comes through.
|
|
85
85
|
#
|
|
86
|
-
#
|
|
86
|
+
# After coercion, not before: a scalar arrives wrapped in whatever holder
|
|
87
87
|
# its type names (`{value: "append"}` for an OpName), and checking the
|
|
88
88
|
# raw payload would be checking the envelope.
|
|
89
89
|
value_object = value_object_for(aggregate, attribute.type)
|
|
@@ -99,12 +99,12 @@ module Hecks
|
|
|
99
99
|
coerced
|
|
100
100
|
end
|
|
101
101
|
|
|
102
|
-
# NIL
|
|
102
|
+
# NIL is not a value for a non-optional argument (C3.7/C3.8). An
|
|
103
103
|
# `optional:` attribute and a load from the store pass nil through
|
|
104
104
|
# as they always did; a command argument offered as null for a
|
|
105
105
|
# required attribute is
|
|
106
106
|
# refused as the field the caller left empty — a value object is
|
|
107
|
-
#
|
|
107
|
+
# built from no fields, so its first required field refuses with
|
|
108
108
|
# exactly the wording the Rust side's `from_json` gives it
|
|
109
109
|
# ("Money.cents expects Integer, got nil"), and a bare scalar
|
|
110
110
|
# refuses through `check_bare_primitive`'s own wording. Lists and
|
|
@@ -112,7 +112,7 @@ module Hecks
|
|
|
112
112
|
# relationship, `validate_relationship_cardinality`'s business).
|
|
113
113
|
# The `attribute.nil?`/`value.nil?` branch of `for_attribute`,
|
|
114
114
|
# pulled out on its own — an unknown attribute has no shape left
|
|
115
|
-
# to branch on, and a nil
|
|
115
|
+
# to branch on, and a nil value is either an ordinary absence
|
|
116
116
|
# (state assembly, hydration, a query ask) or, at the argument
|
|
117
117
|
# door only, `nil_argument`'s own C3.7 refusal.
|
|
118
118
|
private def nil_or_missing(aggregate, attribute, value, argument)
|
|
@@ -129,12 +129,12 @@ module Hecks
|
|
|
129
129
|
return build(value_object, {}, aggregate) if value_object
|
|
130
130
|
|
|
131
131
|
raise TypeMismatch,
|
|
132
|
-
RefusalWording.
|
|
133
|
-
|
|
134
|
-
|
|
132
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
133
|
+
type: aggregate.hecks_name, field: attribute.name,
|
|
134
|
+
expected: attribute.type, offered: "nil")
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
# A
|
|
137
|
+
# A bare primitive is type-checked at the boundary too (C3.8,
|
|
138
138
|
# docs/semantics/bluebook-semantics.md) — the same two predicates a
|
|
139
139
|
# value object's own fields get, so wrong-typed caller input is a
|
|
140
140
|
# TypeMismatch refusal here, never an evaluation fault later. Its
|
|
@@ -180,11 +180,51 @@ module Hecks
|
|
|
180
180
|
|
|
181
181
|
direct_head = direct_identity_head(value, target)
|
|
182
182
|
parts = paths.map { |path| identity_part(materialized, path, direct_head) }
|
|
183
|
-
|
|
183
|
+
if parts.any? { |part| part.nil? || (part.respond_to?(:empty?) && part.empty?) }
|
|
184
|
+
return sole_scalar_identity(value, paths) || value
|
|
185
|
+
end
|
|
184
186
|
|
|
185
187
|
Naming.identity(parts)
|
|
186
188
|
end
|
|
187
189
|
|
|
190
|
+
# A command may redeclare a `reference_to` field under its own,
|
|
191
|
+
# differently-named single-attribute value object (`attribute
|
|
192
|
+
# :venue, VenueHandle; sets :venue`, not `reference_to Venue` —
|
|
193
|
+
# QualityControl BUG#121, `qa/stress_domains/generated_revalued_
|
|
194
|
+
# shape`'s own `Hangar.Repoint`) instead of naming the target's
|
|
195
|
+
# own identity field(s) directly. `identity_part`'s path walk
|
|
196
|
+
# above only ever matches an incoming shape that already uses the
|
|
197
|
+
# target's own field names (or is the target's own identity value
|
|
198
|
+
# object, `direct_identity_head`) — an ad hoc wrapper around a
|
|
199
|
+
# bare scalar under some other field name (`VenueHandle`'s own
|
|
200
|
+
# `value`, not `Venue`'s own `code`) fails every path lookup, and
|
|
201
|
+
# used to fall through to the unresolved `return value` above,
|
|
202
|
+
# storing the wrapped Value. That leaves one reference field on
|
|
203
|
+
# one aggregate holding two different shapes depending on which
|
|
204
|
+
# command last wrote it — `Open`'s own bare `reference_to Venue`
|
|
205
|
+
# argument was never wrapped in the first place (the top guard
|
|
206
|
+
# clause passes a bare scalar straight through), so it always
|
|
207
|
+
# stored the canonical bare identity this class's own header
|
|
208
|
+
# comment promises ("canonical target identities, not Ruby Value
|
|
209
|
+
# wrappers") — while `Repoint` silently kept the wrapper instead.
|
|
210
|
+
#
|
|
211
|
+
# Unambiguous only when both sides admit exactly one scalar: the
|
|
212
|
+
# target names exactly one identity path (`paths.one?` — a
|
|
213
|
+
# compound identity has no single field either side could stand
|
|
214
|
+
# in for) and the offered value is itself a single-attribute
|
|
215
|
+
# value object (`sole_attribute` — a multi-field VO has no one
|
|
216
|
+
# scalar to unwrap either). `materialize_unwrapped` recurses
|
|
217
|
+
# through any further single-field wrapping the same way it
|
|
218
|
+
# already does for `Value.materialize_unwrapped`'s other callers,
|
|
219
|
+
# landing on the bare scalar `Open`'s own path already produces
|
|
220
|
+
# for the identical target field.
|
|
221
|
+
def sole_scalar_identity(value, paths)
|
|
222
|
+
return nil unless value.is_a?(self) && paths.one?
|
|
223
|
+
return nil unless value.value_object.sole_attribute
|
|
224
|
+
|
|
225
|
+
materialize_unwrapped(value)
|
|
226
|
+
end
|
|
227
|
+
|
|
188
228
|
# Whether `value` is itself the target's own (single) identity
|
|
189
229
|
# value object — pure, self-contained: reads only `value` and
|
|
190
230
|
# `target`, decides nothing about any particular path.
|
|
@@ -235,14 +275,14 @@ module Hecks
|
|
|
235
275
|
|
|
236
276
|
# Vendored addition, not (yet) upstream hecks (migration
|
|
237
277
|
# plan task 5): a bare scalar auto-wraps into a single-field
|
|
238
|
-
# value object's sole attribute -- the
|
|
278
|
+
# value object's sole attribute -- the same shape
|
|
239
279
|
# #from_identifier already establishes for identity coercion
|
|
240
280
|
# (`build(value_object, { fields.first.name => identifier }) if
|
|
241
|
-
# fields.size == 1`), made consistent here for
|
|
281
|
+
# fields.size == 1`), made consistent here for mutation
|
|
242
282
|
# coercion too. Real, corpus-wide gap: a synthesised single-
|
|
243
283
|
# field wrapper (Part 3a's bare-primitive auto-synthesis, the
|
|
244
284
|
# norm for a VO-typed aggregate field) is exactly the shape
|
|
245
|
-
# #rewrap_arithmetic_result hands back a raw scalar
|
|
285
|
+
# #rewrap_arithmetic_result hands back a raw scalar result to
|
|
246
286
|
# -- without this, every phantom-field increment/multiply on a
|
|
247
287
|
# single-field-wrapped attribute refused with "pass its fields
|
|
248
288
|
# as an object, not <scalar>" the instant it tried to re-wrap
|
|
@@ -253,27 +293,27 @@ module Hecks
|
|
|
253
293
|
return { value_object.attributes.first.name => value } if value_object.attributes.size == 1
|
|
254
294
|
|
|
255
295
|
raise TypeMismatch,
|
|
256
|
-
RefusalWording.
|
|
257
|
-
|
|
258
|
-
|
|
296
|
+
RefusalWording.render_site("TypeMismatch", "value_object_shape",
|
|
297
|
+
name: name, type: value_object.hecks_name,
|
|
298
|
+
offered: Rendering.describe(value))
|
|
259
299
|
end
|
|
260
300
|
|
|
261
301
|
# `build`'s own recursive twin of `for_attribute`'s single-level
|
|
262
|
-
# normalization — a value object's
|
|
302
|
+
# normalization — a value object's own composite-typed fields
|
|
263
303
|
# (`Pizza.price_cents`, a `Price`) never otherwise pass back
|
|
264
304
|
# through `fields_for`, so a bare scalar or partial Hash for one
|
|
265
|
-
# of
|
|
305
|
+
# of those sails past the outer VO's own shape check (`Pizza`
|
|
266
306
|
# itself has two fields, so nothing unwraps there) and lands
|
|
267
307
|
# stored one field down exactly as handed in — found live: once
|
|
268
308
|
# the fuzzer actually generated the bare-scalar shape
|
|
269
|
-
# `fields_for` has accepted at the
|
|
309
|
+
# `fields_for` has accepted at the top level since 86727afd, a
|
|
270
310
|
# nested `Price` stored as a raw Integer broke every later
|
|
271
311
|
# dotted-path read (`pizza.price_cents.cents`) expecting one
|
|
272
312
|
# more level of Hash.
|
|
273
313
|
#
|
|
274
314
|
# Stays a plain Hash, never a nested `Value` — `Value#with`'s own
|
|
275
315
|
# header and `materialize_unwrapped`'s comment already depend on
|
|
276
|
-
# a value-object-typed field of
|
|
316
|
+
# a value-object-typed field of another value object staying a
|
|
277
317
|
# plain Hash once stored, and this does not change that; it only
|
|
278
318
|
# makes sure that Hash has the shape its own type declares.
|
|
279
319
|
# `aggregate` is the one thing `build` didn't used to need — a
|
|
@@ -281,12 +321,12 @@ module Hecks
|
|
|
281
321
|
# value_object(name)`, so callers with no aggregate in reach
|
|
282
322
|
# (`Value#with`, always re-setting an already-scalar arithmetic
|
|
283
323
|
# field) simply skip this and keep their prior behavior.
|
|
284
|
-
#
|
|
324
|
+
# Recurses into each nested field's own validation too, not only its
|
|
285
325
|
# shape — found live alongside the shape bug this method's header
|
|
286
326
|
# already describes: a nested `Price`/`Size` (a value-object-typed
|
|
287
|
-
# field of
|
|
327
|
+
# field of another value object, e.g. `Pizza.price_cents`,
|
|
288
328
|
# `Pizza.size`) had its Hash shape normalized here but never ran
|
|
289
|
-
# `validate!` — `build`, below, only ever validated the
|
|
329
|
+
# `validate!` — `build`, below, only ever validated the outer value
|
|
290
330
|
# object's own direct fields, so a negative `price_cents.cents` or an
|
|
291
331
|
# out-of-`one_of` `size.value` sailed through a `Pizza`-typed command
|
|
292
332
|
# argument untouched, while the exact same nested type declared as a
|
|
@@ -299,7 +339,23 @@ module Hecks
|
|
|
299
339
|
return fields unless aggregate.respond_to?(:value_object)
|
|
300
340
|
|
|
301
341
|
value_object.attributes.each do |attribute|
|
|
302
|
-
next
|
|
342
|
+
next unless fields.key?(attribute.name)
|
|
343
|
+
|
|
344
|
+
# A list member read back from the store hydrates like a top-level
|
|
345
|
+
# list — `list_of(Entity)` elements get their fields coerced,
|
|
346
|
+
# `list_of(ValueObject)` elements become Values — so a value object
|
|
347
|
+
# holding a list loads into the same shape the live dispatch that
|
|
348
|
+
# wrote it held. Found by PR A3 (every adapter through the state
|
|
349
|
+
# codec): chess-style `sets :positions, append: { pieces:
|
|
350
|
+
# state(:pieces) }` snapshots read back from Heki/Sqlite/Postgres
|
|
351
|
+
# (and now Memory's codec copy) with raw element hashes, so a
|
|
352
|
+
# `given` comparing a snapshot piece's `id` Value to a live one
|
|
353
|
+
# never matched after a restart. Load door only: an input list
|
|
354
|
+
# member is left exactly as before, refusals unchanged.
|
|
355
|
+
if attribute.list?
|
|
356
|
+
fields[attribute.name] = for_attribute(aggregate, attribute, fields[attribute.name]) if trusting_stored_state?
|
|
357
|
+
next
|
|
358
|
+
end
|
|
303
359
|
|
|
304
360
|
raw = fields[attribute.name]
|
|
305
361
|
next if raw.nil? || raw.is_a?(self)
|
|
@@ -322,20 +378,21 @@ module Hecks
|
|
|
322
378
|
end
|
|
323
379
|
end
|
|
324
380
|
|
|
325
|
-
#
|
|
381
|
+
# The full door a value object's own fields pass through — shared by
|
|
326
382
|
# `build` (the outer value object) and `normalize_composite_fields`
|
|
327
383
|
# (every nested one), so a nested `Price`/`Size` is refused exactly
|
|
328
384
|
# the same way, with exactly the same wording, as the identical type
|
|
329
385
|
# declared directly on a command.
|
|
330
386
|
def validate!(value_object, fields)
|
|
331
387
|
# C6.3 (docs/semantics/bluebook-semantics.md) — a value object is
|
|
332
|
-
# validated on
|
|
388
|
+
# validated on construction from input only; state read back from
|
|
333
389
|
# the store is trusted as it was written, so tightening an
|
|
334
390
|
# invariant never makes an old record unreadable (migration is
|
|
335
391
|
# the era system's job). `hydrate` — the one load door — sets
|
|
336
392
|
# the flag; every input door leaves it unset.
|
|
337
393
|
return if trusting_stored_state?
|
|
338
394
|
|
|
395
|
+
check_unknown_fields(value_object, fields)
|
|
339
396
|
check_required_fields(value_object, fields)
|
|
340
397
|
admit_member(value_object, fields)
|
|
341
398
|
check_admitted(value_object, fields)
|
|
@@ -346,9 +403,9 @@ module Hecks
|
|
|
346
403
|
next if Bluebook::Expression::Evaluator.call_rule(invariant, fields)
|
|
347
404
|
|
|
348
405
|
raise InvariantViolation,
|
|
349
|
-
RefusalWording.
|
|
350
|
-
|
|
351
|
-
|
|
406
|
+
RefusalWording.render_site("InvariantViolation", "value_object_invariant",
|
|
407
|
+
name: value_object.hecks_name, description: invariant.description,
|
|
408
|
+
offered: canonical_fields(fields))
|
|
352
409
|
end
|
|
353
410
|
end
|
|
354
411
|
|
|
@@ -359,10 +416,26 @@ module Hecks
|
|
|
359
416
|
new(value_object, fields)
|
|
360
417
|
end
|
|
361
418
|
|
|
419
|
+
# State arrives decoded or not at all (Phase 2, Track A, PR A4).
|
|
420
|
+
# Every persistence adapter reads through `Ports::Persistence::
|
|
421
|
+
# StateCodec.decode` (A3), which symbolizes every top-level key, and
|
|
422
|
+
# the runtime's own callers (entity elements, the remote dispatcher's
|
|
423
|
+
# `symbolize_names:` parse, Era's audit) build symbol-keyed state
|
|
424
|
+
# themselves. A String key here is an adapter or caller that skipped
|
|
425
|
+
# the codec, so it is refused by name rather than respelled: the
|
|
426
|
+
# silent `to_sym` that used to sit here is exactly what hid such a
|
|
427
|
+
# bypass. Always on, because it costs one `is_a?` per key, the same
|
|
428
|
+
# as the `to_sym` it replaced.
|
|
362
429
|
def hydrate(aggregate, state)
|
|
430
|
+
undecoded = state.keys.grep_v(Symbol)
|
|
431
|
+
unless undecoded.empty?
|
|
432
|
+
raise WiringError,
|
|
433
|
+
"#{aggregate.name} state reached hydration with non-Symbol keys #{undecoded.inspect} — " \
|
|
434
|
+
"decode stored state through Hecks::Ports::Persistence::StateCodec.decode first"
|
|
435
|
+
end
|
|
436
|
+
|
|
363
437
|
trusting_stored_state do
|
|
364
|
-
state.each_with_object({}) do |(
|
|
365
|
-
key = name.to_sym
|
|
438
|
+
state.each_with_object({}) do |(key, value), hydrated|
|
|
366
439
|
attribute = aggregate.attribute(key)
|
|
367
440
|
hydrated[key] = attribute ? for_attribute(aggregate, attribute, value) : value
|
|
368
441
|
end
|
|
@@ -381,9 +454,59 @@ module Hecks
|
|
|
381
454
|
|
|
382
455
|
def trusting_stored_state? = Thread.current[TRUSTED_LOAD_KEY] == true
|
|
383
456
|
|
|
457
|
+
# QualityControl BUG#125 — the one narrow door `check_scalar_shapes`
|
|
458
|
+
# keeps open, now that a non-string scalar is otherwise refused for a
|
|
459
|
+
# String-typed field. `MetaValidator::Judge#send_to` — the single
|
|
460
|
+
# choke point every one of the language's own self-hosted dispatches
|
|
461
|
+
# goes through while walking a bluebook's declarations into the
|
|
462
|
+
# "Bluebook" meta-domain — wraps itself in this, and nothing else
|
|
463
|
+
# does. `Judge#appends`' generic `POSITION` handling
|
|
464
|
+
# (judge.rb#appends) keys purely off a field being named "position",
|
|
465
|
+
# the convention every other append list actually uses it for
|
|
466
|
+
# (ValueObject::Member, ProcessManager::Handler, ... — all really
|
|
467
|
+
# `Position`/Integer-typed); `Normalise`'s own `NormalisationRule`
|
|
468
|
+
# happens to also name its own domain field "position"
|
|
469
|
+
# (bluebook.bluebook), but declares it `RuleText` (String) — so the
|
|
470
|
+
# same walk-index substitution (`Judge#v(index)`) hands it a raw
|
|
471
|
+
# Integer too, on every domain's very first boot (the language
|
|
472
|
+
# self-judges its own grammar via `MetaValidator.fresh_runtime`'s
|
|
473
|
+
# fixpoint). Confirmed (QualityControl BUG#125 investigation): the
|
|
474
|
+
# resulting value is never read back — `normalisations` is an
|
|
475
|
+
# `ELSEWHERE`/`derived` field spliced straight from
|
|
476
|
+
# `Expression::CanonicalForm.table` (assembly/contracts.rb), so the
|
|
477
|
+
# judged record holding the Integer is discarded whole — this is a
|
|
478
|
+
# walk-index/domain-field name collision inside `Judge#appends`, not
|
|
479
|
+
# a genuine semantic need for `position` to arrive numeric. Fixing
|
|
480
|
+
# that collision at its own root is a separate, larger change to
|
|
481
|
+
# self-hosted bootstrap mechanics that every domain's boot depends
|
|
482
|
+
# on; this flag only ever loosens scalar-shape checking for the
|
|
483
|
+
# meta-grammar's own value objects (RuleText, BluebookName, Position,
|
|
484
|
+
# …) that Judge itself constructs while walking a bluebook's
|
|
485
|
+
# declarations — never for a real domain's own declared value
|
|
486
|
+
# objects (PieceId, Money, …), which Judge never dispatches commands
|
|
487
|
+
# against. `offer` (judge.rb) already converts a `TypeMismatch` here
|
|
488
|
+
# into a recorded refusal rather than letting it propagate, but
|
|
489
|
+
# `MetaValidator.call` raises the instant `refusals` is non-empty
|
|
490
|
+
# (meta_validator.rb) — so, unexempted, this would fail every
|
|
491
|
+
# domain's boot, not just the language's own bootstrap. Composite
|
|
492
|
+
# shapes (Array/Hash) stay refused unconditionally, bootstrap or not
|
|
493
|
+
# — nothing Judge does ever legitimately needs those for a scalar
|
|
494
|
+
# field.
|
|
495
|
+
BOOTSTRAP_KEY = :hecks_judge_bootstrapping
|
|
496
|
+
|
|
497
|
+
def judge_bootstrapping
|
|
498
|
+
previous = Thread.current[BOOTSTRAP_KEY]
|
|
499
|
+
Thread.current[BOOTSTRAP_KEY] = true
|
|
500
|
+
yield
|
|
501
|
+
ensure
|
|
502
|
+
Thread.current[BOOTSTRAP_KEY] = previous
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
def judge_bootstrapping? = Thread.current[BOOTSTRAP_KEY] == true
|
|
506
|
+
|
|
384
507
|
# `Value.identifier` used to live here: hand it a one-field value object
|
|
385
508
|
# and it opened it, so `identified_by :number` could pass for an identity
|
|
386
|
-
# and the runtime would guess which field was meant.
|
|
509
|
+
# and the runtime would guess which field was meant. That guess is gone.
|
|
387
510
|
# An identity names its field — `identified_by :number` — and the
|
|
388
511
|
# path is what reaches the scalar. A declaration that names no field is
|
|
389
512
|
# refused when the bluebook loads, so nothing has to be unwrapped later.
|
|
@@ -392,12 +515,12 @@ module Hecks
|
|
|
392
515
|
# into a column or a message, where there is no path to consult.
|
|
393
516
|
|
|
394
517
|
# `Value.reference_id` lived here, opening a reference to find the id
|
|
395
|
-
# inside it. A reference
|
|
518
|
+
# inside it. A reference is the id now — refused at the payload gate if it
|
|
396
519
|
# arrives as anything else — so there is nothing left to open. The comment
|
|
397
|
-
# it carried said retiring it meant changing how references are
|
|
520
|
+
# it carried said retiring it meant changing how references are stored ;
|
|
398
521
|
# that is what happened.
|
|
399
522
|
|
|
400
|
-
# A
|
|
523
|
+
# A reference is an ID, so anything else is not one.
|
|
401
524
|
#
|
|
402
525
|
# Nothing coerces a reference — `for_attribute` misses on
|
|
403
526
|
# "Reference<Account>", which is no value object's name, and hands the
|
|
@@ -406,10 +529,10 @@ module Hecks
|
|
|
406
529
|
# refused, so whatever the first caller wrote became the shape.
|
|
407
530
|
#
|
|
408
531
|
# This is that place. It sits at the payload gate rather than inside
|
|
409
|
-
# coercion because the sentence names the
|
|
532
|
+
# coercion because the sentence names the command, and `for_attribute`
|
|
410
533
|
# never learns which command it is serving.
|
|
411
534
|
#
|
|
412
|
-
#
|
|
535
|
+
# Widened past the object shape by BUG#27 (QualityControl ledger,
|
|
413
536
|
# found live on `qa/stress_domains/referral_chain`'s `Member.Join`/
|
|
414
537
|
# `Referral.Issue`). A bare Boolean, Array, or `null` used to sail
|
|
415
538
|
# through here untouched — nothing but Hash/Value ever refused —
|
|
@@ -426,23 +549,23 @@ module Hecks
|
|
|
426
549
|
# (`normalize_args`, `Vocabulary::AggregateDispatchOrder`/
|
|
427
550
|
# `EntityDispatchOrder`), strictly before `resolve_references` ever
|
|
428
551
|
# receives a value to look up — so the two engines now agree on
|
|
429
|
-
#
|
|
552
|
+
# both kind and order, not just kind.
|
|
430
553
|
#
|
|
431
|
-
# `nil`
|
|
554
|
+
# `nil` stays legitimate for a `reference_to ..., optional: true`
|
|
432
555
|
# argument (`Improvement.Open`'s own `reference_to Angle, optional:
|
|
433
556
|
# true` — `qa/bluebook/quality_control.bluebook`): the caller
|
|
434
557
|
# genuinely may have nothing to name yet, and `nil_argument`
|
|
435
558
|
# (interpreting.rb) already passes an optional reference's `nil`
|
|
436
|
-
# through untouched. A
|
|
437
|
-
# caller leaving a required argument empty in every
|
|
438
|
-
# this runtime already refuses (C3.7) — refusing it
|
|
559
|
+
# through untouched. A required reference offered as `null` is a
|
|
560
|
+
# caller leaving a required argument empty in every other sense
|
|
561
|
+
# this runtime already refuses (C3.7) — refusing it here, rather
|
|
439
562
|
# than falling through to `resolve_references`' own nil-skip and
|
|
440
563
|
# then whatever the command's `given` happens to say, is what
|
|
441
564
|
# actually names the empty argument instead of something else.
|
|
442
565
|
#
|
|
443
566
|
# A `has_many` reference's own Array shape is still never refused
|
|
444
|
-
# by
|
|
445
|
-
# list's
|
|
567
|
+
# by its wrapper (`Array(value).find { ... }` only inspects the
|
|
568
|
+
# list's elements) — a reference is never a scalar list-of-lists
|
|
446
569
|
# today, and inventing a rule for a shape the language cannot
|
|
447
570
|
# declare is how decoration gets written. `reference_list` (below)
|
|
448
571
|
# already owns "not an Array at all" for that case.
|
|
@@ -460,13 +583,13 @@ module Hecks
|
|
|
460
583
|
end
|
|
461
584
|
|
|
462
585
|
raise TypeMismatch,
|
|
463
|
-
RefusalWording.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
586
|
+
RefusalWording.render_site("TypeMismatch", "reference_wrong_shape",
|
|
587
|
+
command: command.hecks_name, attribute: attribute.name,
|
|
588
|
+
offered: reference_shape_description(offered),
|
|
589
|
+
known_by: known_by(attribute))
|
|
467
590
|
end
|
|
468
591
|
|
|
469
|
-
# "an object" for the Hash/Value shape — the
|
|
592
|
+
# "an object" for the Hash/Value shape — the original wording this
|
|
470
593
|
# method always gave, pinned byte for byte by
|
|
471
594
|
# `spec/runtime/reference_shape_spec.rb`, kept unchanged by BUG#27's
|
|
472
595
|
# widening. `Rendering.describe` for everything else: `true`,
|
|
@@ -485,8 +608,8 @@ module Hecks
|
|
|
485
608
|
# article-choosing rule. Silent when the target is another chapter's,
|
|
486
609
|
# where this runtime cannot see what it is known by.
|
|
487
610
|
#
|
|
488
|
-
#
|
|
489
|
-
# `identified_by`, which is the
|
|
611
|
+
# Every head, because a caller has to pass every one. This read
|
|
612
|
+
# `identified_by`, which is the single head and is nil the moment an
|
|
490
613
|
# identity has two parts — so a composite target fell through the guard
|
|
491
614
|
# and the refusal went silent exactly where it had the most to say. A
|
|
492
615
|
# single-path target reads as it always did.
|
|
@@ -503,7 +626,7 @@ module Hecks
|
|
|
503
626
|
fields = value.to_h
|
|
504
627
|
return fields.values.first if fields.size == 1
|
|
505
628
|
|
|
506
|
-
raise TypeMismatch, RefusalWording.
|
|
629
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "multi_field_scalar", type: value.type_name)
|
|
507
630
|
end
|
|
508
631
|
|
|
509
632
|
def from_identifier(aggregate, attribute, identifier)
|
|
@@ -516,29 +639,29 @@ module Hecks
|
|
|
516
639
|
return build(value_object, { field.name => coerce_identifier(field, identifier) })
|
|
517
640
|
end
|
|
518
641
|
|
|
519
|
-
raise TypeMismatch, RefusalWording.
|
|
642
|
+
raise TypeMismatch, RefusalWording.render_site("TypeMismatch", "composite_identity", type: value_object.hecks_name)
|
|
520
643
|
end
|
|
521
644
|
|
|
522
645
|
# Vendored fix, not (yet) upstream hecks (migration plan
|
|
523
|
-
# task 9): `identifier` here is always the
|
|
524
|
-
#
|
|
646
|
+
# task 9): `identifier` here is always the derived identity
|
|
647
|
+
# string -- `Identity.of`/`Identity.from` intentionally return
|
|
525
648
|
# one (correct for naming a repository key), and
|
|
526
649
|
# `Runtime::Instance#materialize_identity!` calls `from_identifier`
|
|
527
650
|
# with exactly that string on every fresh hydration -- but when
|
|
528
|
-
# the identity field's
|
|
651
|
+
# the identity field's own declared type is Integer/Float (not
|
|
529
652
|
# the overwhelmingly common String), seeding it straight from
|
|
530
653
|
# that string round-trips a correctly-derived identity back in
|
|
531
|
-
# as the
|
|
654
|
+
# as the wrong Ruby type -- and #build's own
|
|
532
655
|
# `check_numeric_fields` (added specifically to catch a genuine
|
|
533
|
-
#
|
|
656
|
+
# caller mismatch) then refused the runtime's own internal
|
|
534
657
|
# identity seed instead, on every dispatch, valid input or not.
|
|
535
658
|
#
|
|
536
|
-
# Reuses
|
|
659
|
+
# Reuses this same file's own `NUMERIC` table (declared-type ->
|
|
537
660
|
# expected-Ruby-class, already read by `check_numeric_fields`)
|
|
538
|
-
# to decide
|
|
661
|
+
# to decide which declared types need converting, and
|
|
539
662
|
# Kernel#Integer/#Float to do the converting. A genuinely
|
|
540
663
|
# malformed identifier (should never happen, since an identity
|
|
541
|
-
# is always derived
|
|
664
|
+
# is always derived from a correctly-typed field in the first
|
|
542
665
|
# place, but this stays defensive rather than assume it) passes
|
|
543
666
|
# back unconverted, and `check_numeric_fields` refuses it
|
|
544
667
|
# exactly as it always has -- preserving its real job of
|
|
@@ -560,24 +683,27 @@ module Hecks
|
|
|
560
683
|
JSON.generate(fields.sort_by { |name, _| name.to_s }.to_h)
|
|
561
684
|
end
|
|
562
685
|
|
|
563
|
-
# A field declared Integer or Float must
|
|
686
|
+
# A field declared Integer or Float must arrive as one.
|
|
564
687
|
#
|
|
565
688
|
# Without this a String sails into a numeric field and the failure surfaces
|
|
566
689
|
# later, inside a predicate, as `positive? expects a number, got "three"` —
|
|
567
|
-
# an EvaluationError, which is
|
|
690
|
+
# an EvaluationError, which is not a domain refusal. So the runtime broke
|
|
568
691
|
# where the domain should have said no, and the run contract recorded the
|
|
569
692
|
# crash beside genuine refusals as though the domain had judged it.
|
|
570
693
|
#
|
|
571
694
|
# C3.8 — the boundary check for an attribute whose type is a bare
|
|
572
695
|
# primitive rather than a value object: `Integer`/`Float` by exact
|
|
573
|
-
# numeric class (`NUMERIC`), `String`/booleans by rejecting a
|
|
574
|
-
# composite shape (`COMPOSITE_SHAPES`) —
|
|
575
|
-
# `
|
|
576
|
-
#
|
|
577
|
-
#
|
|
578
|
-
# other scalar
|
|
579
|
-
#
|
|
580
|
-
#
|
|
696
|
+
# numeric class (`NUMERIC`), `String`/booleans by rejecting only a
|
|
697
|
+
# composite shape (`COMPOSITE_SHAPES`) — not the same as
|
|
698
|
+
# `check_scalar_shapes` holds a value object's own `String` field to
|
|
699
|
+
# any more (QualityControl BUG#125 tightened that one to also refuse
|
|
700
|
+
# a non-string scalar; a bare `String` argument here still admits
|
|
701
|
+
# any other scalar, left exactly as it was — a bare-primitive
|
|
702
|
+
# attribute was never part of BUG#125's own investigation or fix,
|
|
703
|
+
# and whether it needs the same tightening, and against what real
|
|
704
|
+
# Judge dependency if any, is still open); worded by the same
|
|
705
|
+
# template with the owning construct as `type`. The Rust boundary is
|
|
706
|
+
# stricter there too, recorded in the clause.
|
|
581
707
|
private def check_bare_primitive(owner, attribute, value)
|
|
582
708
|
type = attribute.type.to_s
|
|
583
709
|
expected = NUMERIC[type]
|
|
@@ -590,9 +716,9 @@ module Hecks
|
|
|
590
716
|
end
|
|
591
717
|
if mistyped
|
|
592
718
|
raise TypeMismatch,
|
|
593
|
-
RefusalWording.
|
|
594
|
-
|
|
595
|
-
|
|
719
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
720
|
+
type: owner.hecks_name, field: attribute.name,
|
|
721
|
+
expected: type, offered: Rendering.describe(value))
|
|
596
722
|
end
|
|
597
723
|
|
|
598
724
|
check_numeric_bounds(owner.hecks_name, attribute.name, value)
|
|
@@ -607,40 +733,90 @@ module Hecks
|
|
|
607
733
|
private def check_numeric_bounds(type_name, field_name, given)
|
|
608
734
|
if given.is_a?(Integer) && !INT64_RANGE.cover?(given)
|
|
609
735
|
raise TypeMismatch,
|
|
610
|
-
RefusalWording.
|
|
611
|
-
|
|
736
|
+
RefusalWording.render_site("TypeMismatch", "integer_range",
|
|
737
|
+
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
612
738
|
end
|
|
613
739
|
return unless given.is_a?(Float) && !given.finite?
|
|
614
740
|
|
|
615
741
|
raise TypeMismatch,
|
|
616
|
-
RefusalWording.
|
|
617
|
-
|
|
742
|
+
RefusalWording.render_site("TypeMismatch", "non_finite_field",
|
|
743
|
+
type: type_name, field: field_name, offered: Rendering.describe(given))
|
|
618
744
|
end
|
|
619
745
|
|
|
620
|
-
#
|
|
746
|
+
# QualityControl BUG#41 — a value object refuses a key it does not
|
|
747
|
+
# declare, the same way a command's own payload does
|
|
748
|
+
# (`CommandInterpreter::ArgumentGate#refuse_unknown_arguments`,
|
|
749
|
+
# argument_gate.rb) — reusing that method's exact refusal wording
|
|
750
|
+
# (`UnknownArgument unknown_args`, refusal_wording.rb:
|
|
751
|
+
# "{command} does not declare {unknown} — it takes {declared}")
|
|
752
|
+
# rather than inventing a new template, because every generated
|
|
753
|
+
# Rust value-object `from_json` already renders this refusal
|
|
754
|
+
# through that identical site: `rust/project/json_codec.rb`'s
|
|
755
|
+
# `emit_unknown_argument_check` (mirrored byte-for-byte in
|
|
756
|
+
# `rust/codegen/src/json_codec.rs`) emits `v.unknown_keys(&[...])`
|
|
757
|
+
# and the same "{name} does not declare {unknown} — it takes
|
|
758
|
+
# {declared}" format string for every value object's own
|
|
759
|
+
# `from_json` — `GameLabel::from_json`
|
|
760
|
+
# (rust/src/generated/chess/game.rs) is simply the first case
|
|
761
|
+
# this gap was reproduced against.
|
|
762
|
+
#
|
|
763
|
+
# Ruby never had an equivalent check anywhere in this `validate!`
|
|
764
|
+
# door before now — `fields[attribute.name]` reads only the
|
|
765
|
+
# declared attributes, so any other key a caller's Hash carried
|
|
766
|
+
# was silently ignored. `fields` here only ever holds what a
|
|
767
|
+
# caller (or `for_attribute`'s own recursive coercion) offered
|
|
768
|
+
# for this value object — built by `fields_for`'s plain
|
|
769
|
+
# key-symbolizing (never a Hash the runtime pads with bookkeeping
|
|
770
|
+
# keys of its own; confirmed by reading every call site that
|
|
771
|
+
# reaches `validate!`) — so there is nothing legitimate here to
|
|
772
|
+
# exempt.
|
|
773
|
+
#
|
|
774
|
+
# Checked first, before `check_required_fields` and everything
|
|
775
|
+
# after it — matching Rust's own `from_json`, which checks
|
|
776
|
+
# `unknown_keys` before reading a single declared field. So a
|
|
777
|
+
# Hash offering both an unrecognized key and a missing required
|
|
778
|
+
# one (BUG#41's own second demonstration case: `label: {extra:
|
|
779
|
+
# "bogus"}` — unknown and missing `value`) refuses the same
|
|
780
|
+
# UnknownArgument on both engines, not two different refusal
|
|
781
|
+
# kinds for one malformed call.
|
|
782
|
+
private def check_unknown_fields(value_object, fields)
|
|
783
|
+
known = value_object.attributes.map { |attribute| attribute.name.to_sym }
|
|
784
|
+
unknown = (fields.keys.map(&:to_sym) - known).sort
|
|
785
|
+
return if unknown.empty?
|
|
786
|
+
|
|
787
|
+
declared = value_object.attributes.map(&:name)
|
|
788
|
+
raise UnknownArgument,
|
|
789
|
+
RefusalWording.render_site("UnknownArgument", "unknown_args",
|
|
790
|
+
command: value_object.hecks_name, unknown: unknown,
|
|
791
|
+
declared: declared)
|
|
792
|
+
end
|
|
793
|
+
|
|
794
|
+
# C3.7 — a value object is a typed field product: every non-optional
|
|
621
795
|
# field arrives, or construction refuses. A missing field and a null
|
|
622
796
|
# one are the same absence (`fields[name]` reads nil for both), worded
|
|
623
797
|
# as the type mismatch it is — "{type}.{field} expects {expected}, got
|
|
624
798
|
# nil" — the identical string the Rust side's generated `from_json`
|
|
625
799
|
# gives the same input, so the corpus can pin it on both. Checked
|
|
626
|
-
#
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
#
|
|
630
|
-
#
|
|
800
|
+
# first among the field-content checks (after `check_unknown_fields`'s
|
|
801
|
+
# own structural gate above, BUG#41): an invariant reading a field
|
|
802
|
+
# that never arrived is exactly the thing that used to answer
|
|
803
|
+
# "invariant violated" (or nothing at all — `ToppingName`'s
|
|
804
|
+
# `{value: null}` used to be accepted and stored). A `default:` has
|
|
805
|
+
# already been filled in by `apply_defaults`; a list field's absence
|
|
806
|
+
# is an empty list, never a refusal.
|
|
631
807
|
private def check_required_fields(value_object, fields)
|
|
632
808
|
value_object.attributes.each do |attribute|
|
|
633
809
|
next if attribute.optional? || attribute.list?
|
|
634
810
|
next unless fields[attribute.name].nil?
|
|
635
811
|
|
|
636
812
|
raise TypeMismatch,
|
|
637
|
-
RefusalWording.
|
|
638
|
-
|
|
639
|
-
|
|
813
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
814
|
+
type: value_object.hecks_name, field: attribute.name,
|
|
815
|
+
expected: attribute.type, offered: "nil")
|
|
640
816
|
end
|
|
641
817
|
end
|
|
642
818
|
|
|
643
|
-
# Checked
|
|
819
|
+
# Checked before invariants, because an invariant reading a mistyped field
|
|
644
820
|
# is exactly the thing that used to explode.
|
|
645
821
|
NUMERIC = { "Integer" => Integer, "Float" => Numeric }.freeze
|
|
646
822
|
private def check_numeric_fields(value_object, fields)
|
|
@@ -653,9 +829,9 @@ module Hecks
|
|
|
653
829
|
|
|
654
830
|
unless given.is_a?(expected)
|
|
655
831
|
raise TypeMismatch,
|
|
656
|
-
RefusalWording.
|
|
657
|
-
|
|
658
|
-
|
|
832
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
833
|
+
type: value_object.hecks_name, field: attribute.name,
|
|
834
|
+
expected: attribute.type, offered: Rendering.describe(given))
|
|
659
835
|
end
|
|
660
836
|
|
|
661
837
|
# PRD 05 (numeric-boundary-coverage) — `given.is_a?(expected)`
|
|
@@ -673,8 +849,8 @@ module Hecks
|
|
|
673
849
|
# storage, where `JSON.generate`/`#to_json` raises
|
|
674
850
|
# `JSON::GeneratorError: NaN/Infinity not allowed in JSON` the
|
|
675
851
|
# moment anything tries to persist or replay it — again a raw
|
|
676
|
-
# crash, not a refusal. `-0.0` is deliberately
|
|
677
|
-
# here: it
|
|
852
|
+
# crash, not a refusal. `-0.0` is deliberately not refused
|
|
853
|
+
# here: it is finite, round-trips through JSON as `-0.0`
|
|
678
854
|
# cleanly (confirmed empirically), and is a legitimate,
|
|
679
855
|
# meaningful float value (a signed zero), not a corruption
|
|
680
856
|
# risk — only NaN and +/-Infinity are.
|
|
@@ -683,45 +859,60 @@ module Hecks
|
|
|
683
859
|
end
|
|
684
860
|
|
|
685
861
|
# A field declared `String` (or a boolean) must not arrive as a
|
|
686
|
-
#
|
|
687
|
-
# what has to be a leaf scalar.
|
|
862
|
+
# composite — an Array or a Hash (or a nested Value) standing in for
|
|
863
|
+
# what has to be a leaf scalar. A `String` field, further, must not
|
|
864
|
+
# arrive as any other non-composite scalar either (Integer, Float,
|
|
865
|
+
# true/false) — QualityControl BUG#125, matching Rust's generated
|
|
866
|
+
# `from_json`, which requires a JSON string node for a String-typed
|
|
867
|
+
# field unconditionally and refuses anything else, including a JSON
|
|
868
|
+
# number or boolean. Ruby used to tolerate exactly that (found live:
|
|
869
|
+
# `Chess::Piece.Capture`'s `PieceId`, String-typed, offered a bignum
|
|
870
|
+
# `id` — Ruby let it pass and failed later on an unrelated field,
|
|
871
|
+
# Rust refused on `id` itself, immediately) — no longer, except
|
|
872
|
+
# inside `judge_bootstrapping?` (above), the one caller genuinely
|
|
873
|
+
# relying on the old leniency; see that flag's own comment for why.
|
|
688
874
|
#
|
|
689
|
-
#
|
|
690
|
-
#
|
|
691
|
-
#
|
|
692
|
-
#
|
|
693
|
-
# a
|
|
694
|
-
#
|
|
695
|
-
#
|
|
696
|
-
#
|
|
697
|
-
#
|
|
698
|
-
#
|
|
699
|
-
#
|
|
700
|
-
#
|
|
701
|
-
#
|
|
702
|
-
#
|
|
703
|
-
#
|
|
704
|
-
#
|
|
875
|
+
# `TrueClass`/`FalseClass` stay laxer than `check_numeric_fields`
|
|
876
|
+
# above: for those two, this still only enforces that the shape
|
|
877
|
+
# isn't a collection, not the exact Ruby class — narrower than the
|
|
878
|
+
# `String` case above because BUG#125 investigated and fixed String
|
|
879
|
+
# specifically; a boolean field's own scalar-shape tolerance is a
|
|
880
|
+
# separate, uninvestigated question left exactly as it was. No
|
|
881
|
+
# scalar field, of any declared type, can ever legitimately be
|
|
882
|
+
# handed an Array or a Hash — that shape is always wrong, and always
|
|
883
|
+
# was: `InvalidValueGenerator#array_for_scalar`'s own corruption is
|
|
884
|
+
# deliberately built to be refused (see that file's header), and
|
|
885
|
+
# until this check existed it sailed straight through for a
|
|
886
|
+
# String/boolean field the way it never could for an Integer/Float
|
|
887
|
+
# one (`check_numeric_fields` above already catches an Array offered
|
|
888
|
+
# for those). Found live via bin/fuzz, seed 17 on the fixtures
|
|
889
|
+
# domain : an Array standing in for a single-field identity's
|
|
890
|
+
# declared `String`, `.to_s`'d into a record id downstream.
|
|
705
891
|
COMPOSITE_SHAPES = [Array, ::Hash].freeze
|
|
706
892
|
NON_NUMERIC_SCALARS = %w[String TrueClass FalseClass].freeze
|
|
707
893
|
private def check_scalar_shapes(value_object, fields)
|
|
708
894
|
value_object.attributes.each do |attribute|
|
|
709
|
-
|
|
895
|
+
type = attribute.type.to_s
|
|
896
|
+
next unless NON_NUMERIC_SCALARS.include?(type)
|
|
710
897
|
|
|
711
898
|
given = fields[attribute.name]
|
|
712
|
-
next if given.nil?
|
|
899
|
+
next if given.nil?
|
|
900
|
+
|
|
901
|
+
composite = COMPOSITE_SHAPES.any? { |shape| given.is_a?(shape) }
|
|
902
|
+
non_string_scalar = type == "String" && !composite && !given.is_a?(String) && !judge_bootstrapping?
|
|
903
|
+
next unless composite || non_string_scalar
|
|
713
904
|
|
|
714
905
|
raise TypeMismatch,
|
|
715
|
-
RefusalWording.
|
|
716
|
-
|
|
717
|
-
|
|
906
|
+
RefusalWording.render_site("TypeMismatch", "numeric_field",
|
|
907
|
+
type: value_object.hecks_name, field: attribute.name,
|
|
908
|
+
expected: attribute.type, offered: Rendering.describe(given))
|
|
718
909
|
end
|
|
719
910
|
end
|
|
720
911
|
|
|
721
|
-
# A field declared with a
|
|
912
|
+
# A field declared with a pattern must match it.
|
|
722
913
|
#
|
|
723
914
|
# Beside check_numeric_fields and for the same reason : a value that does
|
|
724
|
-
# not look like what it claims to be is the
|
|
915
|
+
# not look like what it claims to be is the domain saying no, and it should
|
|
725
916
|
# say so here rather than let the wrong shape travel on and surface as a
|
|
726
917
|
# broken predicate later.
|
|
727
918
|
#
|
|
@@ -738,9 +929,9 @@ module Hecks
|
|
|
738
929
|
next if given.is_a?(String) && Regexp.new(pattern).match?(given)
|
|
739
930
|
|
|
740
931
|
raise TypeMismatch,
|
|
741
|
-
RefusalWording.
|
|
742
|
-
|
|
743
|
-
|
|
932
|
+
RefusalWording.render_site("TypeMismatch", "pattern_mismatch",
|
|
933
|
+
type: value_object.hecks_name, field: attribute.name,
|
|
934
|
+
pattern: pattern, offered: Rendering.describe(given))
|
|
744
935
|
end
|
|
745
936
|
end
|
|
746
937
|
end
|